Skip to content

feat(OMN-19038): no contract in the corpus populates requirements[].acceptance[] — the DoD serializer's input surface is empty - #2766

Merged
jonahgabriel merged 3 commits into
devfrom
jonah/omn-19038-transcribe-criterion-text
Sep 22, 2026
Merged

jonahgabriel merged 3 commits into
devfrom
jonah/omn-19038-transcribe-criterion-text

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Closes OMN-19038.

What is broken

The autobinder reads a ticket's body, splits it into acceptance-criterion units, hashes each one, and writes a binding naming the criterion's label and its digest. It holds the criterion's actual sentence in memory at that moment, in unit.text, and drops it.

Nothing downstream can recover it. binds_ac is labels only. ac_bindings pins a criterion_hash, which is a digest and not the words. So acceptance criteria live only in the tracker, which inverts the direction of authority the OMN-18270 serializer is built on: the contract is meant to be the model and the body a rendering of it.

Measured on onex_change_control origin/dev at 605ebfd729 on 2026-09-21:

contracts under contracts/ carrying binds_ac carrying requirements
9,300 370 1

370 live bindings reference criterion text that exists nowhere in the contract corpus, and onex-serialize-ac-section has essentially no input anywhere.

The fix

ModelTranscribedBinding gains a required statement, populated from the same unit the digest is taken over, and the two contract head templates gain a top-level requirements: block rendered from the same record list that renders binds_ac.

The load-bearing property is not that a requirements key appears. A block built from a second filter would satisfy that and would be worse than the absence it replaced, because the serializer refuses a contract binding a label its model does not enumerate (ac_section_unenumerated_binding) and the OMN-18333 rule ac_binding_criterion_unbound refuses the converse. A disagreement between the two would be invisible at mint time and permanent afterwards.

So both keys are rendered from one filtered list — label present, falsifier declared, and the consumer's reader resolves the label — which makes the declared set and the bound set equal by construction rather than by agreement between two pieces of code that will drift. TestDeclaredSetEqualsBoundSet asserts that as a set identity over the parsed contract, because two presence checks pass on any overlap at all.

statement is required with no default. A default of empty would let a binding render a model that declares a criterion with no content, which reads to the serializer as an enumerated criterion and is a worse failure than the missing key.

Wired on both producers, deliberately

OMN-18332 is the precedent and the reason: that transcription landed on the compute producer, was never wired into the born producer, and seven of the next nine companions minted with no bindings at all. A model emitted by one producer and not the other reproduces that exactly, one layer up, and a corpus census would show it only as "some contracts have requirements". test_both_producers_emit_the_identical_block compares the two rendered blocks for equality, so a second differently-spelled renderer fails too.

The repair-row renderer passes the empty string, by construction and not by omission: it keeps only the tail from the evidence marker onward, so anything in the head's preamble is discarded. Appending one evidence row to an existing contract does not re-author that contract's model.

RED, measured before the fix and not inherited

The new test file was run against the unmodified branch point d75f913c6:

10 failed, 2 passed

The two that passed are the regression guards — a ticket with no falsifiers must mint a byte-identical contract — which correctly pass before and after. Every defect test failed.

GREEN after: 134 passed across tests/unit/occ, and 584 passed, 5 skipped across the emitter, golden-fixture and yamlfmt-stability suites. mypy --strict clean on both changed modules. ruff format and ruff check clean.

Negative controls

Five, because a renderer that bought the headline by enumerating everything would pass a presence check:

  1. a criterion with a label and no falsifier appears in neither binds_ac nor requirements — this is the population the transcriber has always refused to bind, and the model must agree or it declares a criterion nothing claims;
  2. a label the consumer's reader cannot resolve — the measured zero-padded AC01 case, read verbatim here and canonicalised to AC1 there — appears in neither;
  3. no bindings renders no requirements key at all, on both producers, so the overwhelming majority of companions are byte-identical to what they minted before;
  4. the statement is asserted equal to criterion_units read independently from the same body, so writing the label, the falsifier or an empty string into it fails;
  5. the block renderer returns the empty string for an empty record list, which is the unit-level guard under the two byte-identity tests.

Formatter safety

Criterion text is prose and routinely long. Each statement renders through render_check_value_field, which emits a literal block scalar for any value the formatter would refold — a plain scalar there gets the formatter's sentinel injected into the value and the OMN-15479 contamination ratchet then rejects the file, correctly.

Proven against the real binary rather than argued: a contract rendered from a two-criterion body with realistic long prose, formatted with yamlfmt v0.21.0 under the actual onex_change_control config, is byte-identical before and after (sha256 prefix 7403ed9028d879c0 both readings), the statements parse back intact, and no sentinel appears anywhere in the file.

canonical_criterion_text collapses whitespace runs, so a statement is always single-line, which is what the literal-block rendering assumes. That property is pinned by a test here rather than left to be discovered when a multi-line criterion breaks the block.

Honest limits

This populates the model for contracts minted from now on. The 370 existing bound contracts are untouched and are separate work under OMN-19046.

It also does not let the serializer render the tickets that already exist. _has_unmanaged_ac_heading refuses any body carrying an acceptance-criteria heading outside its sentinels, and on a 14-ticket even spread across the bound corpus all 14 carry one and none carries the sentinel. That is filed as OMN-19116 and is not attempted here.

Evidence-Ticket: OMN-19038
Evidence-Source: OCC#10754

…s into the contract model

The transcriber splits a ticket body into criterion units, hashes each one,
and writes a binding naming the label and the digest. It holds the criterion
sentence in unit.text at that moment and drops it, so binds_ac carries labels,
ac_bindings carries digests, and the words survive nowhere.

Measured on onex_change_control origin/dev at 605ebfd729: 9,300 contracts, 370
carrying a binds_ac entry, 1 carrying requirements. The OMN-18270 serializer
renders requirements[].acceptance[] into the ticket body and treats the
contract as the model, and had almost no input anywhere in the corpus.

ModelTranscribedBinding gains a required statement taken from the same unit
the digest is computed over, and both contract head templates gain a top-level
requirements block rendered from the same record list that renders binds_ac.
One filtered list, two renderings, so the declared set and the bound set are
equal by construction rather than by agreement between two renderers that
drift. Wired on both producers because OMN-18332 landed on one, missed the
other, and seven of the next nine companions minted unbound.

An empty record list renders no key at all, so a ticket that declared no
falsifiers mints the contract it always minted.

RED measured against the unmodified branch point: 10 failed, 2 passed, the two
being the byte-identity guards that must pass on both sides.

Onex-Lane: omn19038-requirements-producer
Onex-Session: c8ec7a9acf01451eb44da687c35b27ba
@github-actions

Copy link
Copy Markdown

✅ Architectural Review — PASSED

Errors: 0
Warnings: 0

What this checks

Rule Description
ARCH-TOPIC-001 No hardcoded Kafka topic strings in handler code
ARCH-DI-001 No event_bus=None bypass
ARCH-DI-002 No direct Handler instantiation outside workflow_runner/adapters
ARCH-DI-003 No reinvented DI containers
ARCH-TOPIC-002 contract.yaml topics follow `onex.{cmd

No architectural violations found.

Static architectural lint — no model inference (OMN-14176).

@github-actions

Copy link
Copy Markdown

✅ Hostile Reviewer — PASSED

Blocking findings (critical/error): 0
Total findings: 14
Models succeeded: qwen3-review,qwen3-review-b


Gate semantics

Verdict Meaning Blocks merge?
passed >=2 models succeeded, no critical/error findings No
blocked CRITICAL or ERROR findings found Yes
degraded Fewer than 2 models succeeded (infra unavailable/timeout) Yes (OMN-15110)

Powered by omniintelligence.review_pairing.cli_review — node-based adversarial review via HandlerLlmCliSubprocess (OMN-8524)

@jonahgabriel

Copy link
Copy Markdown
Contributor Author

Pre-PR proof: surface=mac-scratch:omn19038-requirements-producer receipt=/Users/jonah/Code/omni_home/.onex_state/prepr-proof/OMN-19038-mac-scratch-20260921T223344Z/receipt.txt sha256=84de0c94bda165f51ea82b9ec4fcff76e5f8506cf435e3f52d189268d3beb8e8 result=PASS

Three isolated environments, no shared site-packages, every read a separate process: the branch build at 4bec87098, unmodified origin/dev at d75f913c6 as the control, and a consumer holding omnibase-core 0.47.20 resolved from the package index.

The same body and the same emit script under both builds mint the identical bindings, AC1 and AC2. A top-level key diff over the two parsed contracts returns exactly one line, > requirements, so the legs below are attributable to this change and not to a second difference.

leg branch control (dev)
released ModelTicketContract validates, 2 acceptance criteria validates, 0
onex-serialize-ac-section, body with no criteria heading exit 0, CHANGED with AC1, AC2 exit 1, ac_section_unenumerated_binding

The control does not merely do less; it refuses by name for the reason the ticket was filed.

Two things the receipt records that this summary would otherwise flatter. Against a realistic body — one that already carries a hand-written criteria section — both sides still refuse, and only the rule discriminates: the branch has moved on from "the model is missing" to ac_section_unmanaged_section, which is OMN-19116 and is not attempted here. And onex-change-control is not published to any index, so the serializer half of the consumer was built from OCC at its merged origin/dev 29e0024b6c rather than from a released wheel. It is code this lane did not write or modify, but it is a deviation from the runbook and is named rather than glossed.

Separately measured: yamlfmt v0.21.0 under the real onex_change_control config leaves a contract rendered from long criterion prose byte-identical, with no formatter sentinel in the file.

PR stays DRAFT and unarmed.

@jonahgabriel
jonahgabriel marked this pull request as ready for review September 21, 2026 22:38
onexbot-occ-writer Bot added a commit to OmniNode-ai/onex_change_control that referenced this pull request Sep 21, 2026
…nimarket#2766

The declared checks were executed for real in the OmniNode-ai/omnimarket checkout at the PR head by the product-repo OCC receipt runner, and the results written append-only into this companion. Producer-minted PENDING receipts are superseded by a net-new record; no existing receipt was edited.

Ticket: OMN-16859 (AC3b)
Onex-Workflow: OCC Receipt Runner
Onex-Run: https://github.com/OmniNode-ai/omnimarket/actions/runs/35663713047
@jonahgabriel
jonahgabriel marked this pull request as draft September 21, 2026 23:57
jonahgabriel pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Sep 22, 2026
…imarket#2766 (#10754)

* evidence(OMN-19038): author OCC companion for OmniNode-ai/omnimarket#2766

OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head 4bec87098d2187d1ded1f73e874bf45084e9843c.

* evidence(OMN-19038): self-bind OCC#10754 + rebind contract_sha256

* evidence(OMN-16859): executed test_passes receipts for OmniNode-ai/omnimarket#2766

The declared checks were executed for real in the OmniNode-ai/omnimarket checkout at the PR head by the product-repo OCC receipt runner, and the results written append-only into this companion. Producer-minted PENDING receipts are superseded by a net-new record; no existing receipt was edited.

Ticket: OMN-16859 (AC3b)
Onex-Workflow: OCC Receipt Runner
Onex-Run: https://github.com/OmniNode-ai/omnimarket/actions/runs/35663713047

---------

Co-authored-by: omnimarket-bot <bot@omninode.ai>
Co-authored-by: onexbot-occ-writer[bot] <307849072+onexbot-occ-writer[bot]@users.noreply.github.com>
@jonahgabriel
jonahgabriel marked this pull request as ready for review September 22, 2026 22:38
@jonahgabriel
jonahgabriel enabled auto-merge (squash) September 22, 2026 22:39
@jonahgabriel
jonahgabriel merged commit 7d97c9f into dev Sep 22, 2026
136 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-19038-transcribe-criterion-text branch September 22, 2026 22:51
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