Skip to content

feat(OMN-19081): read the key grain from the contract, not from a list here - #3943

Merged
jonahgabriel merged 7 commits into
devfrom
jonah/omn-19081-key-grain-from-contract
Sep 23, 2026
Merged

jonahgabriel merged 7 commits into
devfrom
jonah/omn-19081-key-grain-from-contract

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Sep 21, 2026 •

Copy link
Copy Markdown
Collaborator

DRAFT until the proof verdict is countersigned. Runtime-affecting, not exempt.

Read this first: my original body was wrong, and the proof corrected it

The first shape of this change deleted the exemption literal outright and read the contract alone. I argued in this body that it was safe consumer-first because an unresolved grain is graded rather than exempted. That is sound against hiding loss and unsound against false alarm, and I did not distinguish the two. The pre-PR proof on dogfood-101 measured the difference and failed the change. That is the protocol paying for itself, and it belongs at the top rather than buried.

The version window, stated plainly

key_grain landed in omnimarket 0.4.181. A deployed runtime carrying anything earlier resolves no grain for any exposure: the dogfood-101 image runs 0.4.178, and zero of its 409 installed contracts carry the field. In that window, reading the contract alone strips the two content-addressed exemptions, grades their intended idempotence as loss, and takes projection_delta_dropped to DEGRADED on healthy behaviour. On a lane whose probe runs with --degraded-policy fail, that is an outage.

Per the operator ruling of 2026-09-21, the literal survives as a fallback for that window only. The contract is the source of truth and wins wherever it answers; the fallback is consulted solely where the contract resolves nothing. Deleting it is tracked in OMN-19093, whose trigger is a reading from inside the runtime container rather than a pin or a date.

Part of OMN-19081, epic OMN-18906 AC-4.

The interim step this retires

OMN-18910 shipped the drop dimension's exemption as a four-entry literal naming the two content-addressed exposures, and said in its own comment that the literal was interim and this was the successor step. OMN-18908 has since landed key_grain on all 65 projection exposures, so the fact is declared and the copy in this repository is a second, unsynchronised source of it.

That copy fails quietly in both directions:

  • a genuinely content-addressed exposure added tomorrow gets no exemption unless somebody remembers to edit a tuple in a different repository, so its intended idempotence reads as loss;
  • an exposure wrongly added to the tuple is exempt forever with no evidence behind it, which is the rubber stamp the declaration exists to prevent.

The seam

The grain rides the registration. The wiring resolves it from the contract at wiring time and hands it to the counters; the health monitor asks the counters and reads no contract itself.

Nothing imports omnimarket. This reads the raw contract YAML on exactly the seam _read_dlq_topics already establishes for event_bus.dlq_topics, for the same stated reason: the typed contract model does not carry the field. The literal is deleted, not left beside its replacement, and a test asserts its absence.

Three decisions worth reviewing

Unresolved is a third answer and it is named. A projection whose grain cannot be resolved is GRADED, never exempted, because silently exempting it would hide a real accumulation behind a missing field. It is also reported under its own outcome token rather than folded in with the mutable ones, since a reader who cannot tell those apart cannot tell which remedy applies: one is a contract edit, the other is an investigation.

A mixed contract is not exempt. A projection counts as immutable-grained only when it declares at least one exposure and every one of them is immutable. The delta its writer discarded might belong to either exposure, so exempting on the strength of the immutable half would hide loss on the mutable half.

This reader never raises, unlike the one it copies. The DLQ reader guards a destination, where a broken contract must surface. This one feeds an observability exemption, and a contract it cannot parse must not be able to stop a projection from being wired. Unparseable resolves to UNKNOWN, which is graded and named.

Where the grain is carried

On ProjectionDispatchSinks, whose own docstring already states it is the parameter-budget bundle for this one factory and already carries an input that is not a sink. The parameter-count gate refused the separate argument, which is the gate working; the bundle is its sanctioned remedy rather than a workaround.

  • Measured red first: 16 of the 18 new tests failed against this tree before the change, including the assertion that the literal no longer exists.
  • Every refusal is paired with a control that must stay green: a mutable declaration is not exempt, the same accumulation on a mutable grain does grade DEGRADED, and a resolved grain raises no unresolved token.
  • Two tests drive the shipped wiring factory rather than a stand-in, because every accessor could be correct while the seam never passes a grain, and that is the shape that ships a silent regression past every other test in the file.
  • mypy --strict: no issues in 3,126 source files. 8,324 tests pass across runtime, services and the OMN-18910 integration seam.

The behaviour to preserve, measured on the live lane

Read read-only on the lab dev lane before this change, at revision c2880144:

projection_apply_divergence = HEALTHY
  All 34 dispatching projection(s) that consumed also wrote, over 1 apply
  window(s) (814 consumed, 1403 row(s) upserted, 0 refused by an ordering guard)

projection_delta_dropped = HEALTHY
  ... [2 exposure(s) declare an immutable, content-addressed key grain ...
   excluded: HandlerProjectionSessionReplay, HandlerProjectionWorkEvents]

Those two names must still be excluded after this change, and that is what the proof has to show.

Proof, on dogfood-101

Two runs. The first FAILED and was countersigned as a correct fail; it produced the ruling above. Receipt OMN-19081-dogfood-101-20260921T210919Z, sha256 d74d812a....

The re-run PASSES. Receipt OMN-19081-dogfood-101-20260921T221327Z, sha256 5768c6ee1a995fa0a71d4753862aef309dc5f73c7e429ba2e18d95b8267e1163. Same surface, same five modules, same rising gauge series, both containers restarted, image still on 0.4.178 so the contract still answers nothing, which is the condition under test:

ARM 1  pre-patch, dev literal             accumulating ()             HEALTHY
ARM 2  post-patch, fallback covers it     accumulating ()             HEALTHY
       fallback_exempted (WorkEvents,)    <- the regression is closed
ARM 3  post-patch, contract says mutable  accumulating (WorkEvents,)  DEGRADED
       fallback_exempted ()               <- the contract wins

Arm 3 is the arm the countersigning lane added to my first run, carried forward: two arms show a consequence, three separate the cause. Without it, arm 2 cannot be told apart from a fallback that exempts unconditionally.

A defect the re-run found in itself. It passed on status and was still wrong on the page: the detail said a projection was GRADED rather than exempted, immediately beside a sentence saying the fallback had exempted it. Found by reading the rendered detail rather than checking only the status, and fixed in 73d095fea.

Restore verified by digest both times: ten sha256 readings equal to the originals, backups and scratch removed, containers restarted, the original literal independently re-read, three endpoints 200, verdict HEALTHY over nine dimensions with 34 dispatching projections, CI runner on that host untouched.

Not proven

The declaration path is still unproven on a live runtime. Every arm runs against 0.4.178, where the contract answers nothing, so arm 3 exercises a declared grain through an injected value rather than one read off a deployed contract. What is proven live is the fallback path and the resolution order. The end-to-end read from a deployed contract carrying the field needs a runtime at 0.4.181 or later: a read-only dev-lane look after merge, or a second hot-patch once a surface runs it.

No projection writer container runs on dogfood-101, so that path stays NOT_PROVABLE there and was not substituted for. No divergence was induced on the lane and no contract was edited in the image.

Evidence-Ticket: OMN-19081
Evidence-Source: OCC#10753

@onexbot-occ-writer

Copy link
Copy Markdown
Contributor

No OCC evidence companion was minted for this PR.

product PR is a draft; companion suppressed (F-17)

Matched in the state: draft

To clear this: Mark the PR ready for review. The ready_for_review trigger re-fires the born path and the companion mints then.

Reported by node_occ_companion_effect (decline code pr_draft, OMN-16665). This decision was made against the PR's LIVE state at compute time, not at publish time — a green publisher job only means the command reached the broker.

@jonahgabriel

Copy link
Copy Markdown
Collaborator Author

Pre-PR proof: surface=dogfood-101 receipt=$OMNI_HOME/.onex_state/prepr-proof/OMN-19081-dogfood-101-20260921T210919Z/receipt.txt sha256=d74d812a7aca78041dff959cc47588daf8f9ef17c719d136962d3c67fc753fa1 result=FAIL

This stays a draft. The proof failed, and that is the value of running it.

The change is not broken. It is ordered wrong against the deployed runtime, and on the dev lane the consequence is an outage rather than a missed alarm.

The finding

On any runtime whose omnimarket predates the key_grain declaration, this change removes the two content-addressed exemptions and grades those exposures instead. Their discarded deltas are correct idempotence, so projection_delta_dropped goes DEGRADED on healthy behaviour.

Measured in one interpreter, same synthetic gauge series, the only variable being which exemption source is in play:

PRE-PATCH   literal present  -> accumulating ()                  -> HEALTHY
POST-PATCH  literal deleted  -> accumulating (WorkEvents,)       -> DEGRADED

Why the contract does not answer there, read from the running container rather than inferred: the image carries omnibase omnimarket 0.4.178, and the declaration landed in 0.4.181 earlier today. Of the 409 contract.yaml files in that installed package, zero contain key_grain. The contract file is present, so this is a missing field and not a missing path.

Then the coupling turns a false alarm into an outage, via the chain already established on the dev lane: DEGRADED dimension, _worst, verdict DEGRADED, fold_runtime_verdict_into_status, payload degraded, container probe run with --degraded-policy fail, probe red, and the three services behind depends_on: service_healthy strand.

Correcting my own pull-request body

It argued this is safe consumer-first because an unresolved grain is graded rather than exempted. That is sound against hiding loss and unsound against false alarm, and I did not distinguish the two. Grading an unresolved grain is the right default for a tree where the declaration exists. It is the wrong default for the version window where it does not.

Method

Five modules copied into both runtime containers, bytecode cache dropped, both containers restarted, so every assertion describes a process that loaded the patch. Source was my own worktree at 274ee754, read back clean and equal to the pushed head, satisfying the staged-sha ruling by worktree rather than by copying a moving clone.

Restore, verified by digest

Ten sha256 readings across both containers, all equal to the pre-patch originals. Backups and scratch probes deleted, bytecode dropped, both restarted. Independent re-read: the literal is present again with all four entries and the patch token is absent. Three endpoints 200, seven containers healthy, verdict HEALTHY over 9 dimensions with 34 dispatching projections and both exemptions named again. The self-hosted CI runner on that host, up 5 days, was never touched.

Recorded rather than deleted

Three readings in the post-patch probe show zero registered projections. That is because a docker exec is a fresh interpreter with its own process-local counters, not a defect, and a reader taking them for the live runtime's numbers would draw a false conclusion.

Not proven

No projection writer container runs on that surface, so that path is NOT_PROVABLE there and was not substituted for. What is live, and is what this change touches, is the in-process dispatch arm: 34 dispatching projections writing real rows. No divergence was induced on the lane and no contract was edited in the image.

What has to change, stated as options rather than decided

  1. Gate the reader on an omnimarket floor that carries the declaration, keeping the literal until that floor is deployed.
  2. Keep the literal as the fallback when the contract resolves nothing, making this additive during the window and deleting the literal in a later change.
  3. Do not grade an unresolved grain on the drop dimension at all: report it under its token and exempt it until the declaration exists.

Option 2 is closest to how the fleet practises consumer-first, but it keeps two sources of truth for a while, which is what this ticket exists to remove. Option 1 makes the ordering explicit. Raised with the team lead rather than picked here, because the last default I chose for the unresolved case is the one this run falsified.

@jonahgabriel

Copy link
Copy Markdown
Collaborator Author

Pre-PR proof countersign: receipt sha256=d74d812a7aca78041dff959cc47588daf8f9ef17c719d136962d3c67fc753fa1 actor=prepr-countersign verdict=COUNTERSIGNED

Countersigned as a correct FAIL. The finding is real, I reproduced it off the surface, and I added the arm the contrast was missing. This pull request should stay a draft.

I reproduced both arms independently, without touching the surface. The two modules are nearly import-free, so I fetched them at the base and at the branch commit, installed only the released package the counters need, and ran the same rising discarded-delta series through each. Dev with its literal exemption reads healthy with an empty accumulating set. The branch with the contract-derived set reads degraded on the content-addressed exposure. Both match the receipt exactly, including the exposure named.

The arm the contrast omits, and why it matters. Two arms show the consequence but do not separate two candidate causes: the exemption set going empty, or the branch grading logic changing. I ran a third, the dev module with an empty exemption set, and it also reads degraded. So the empty set alone is sufficient and the grading logic is not a second cause. The receipt's attribution is exact, and it is exact for a reason it did not itself establish. Worth adding that arm to the receipt, because without it the account is right by luck rather than by measurement.

The pre-image is confirmed from outside the container. All three modules the receipt captured before patching are byte-identical to the base branch: the flow module at e6ece2ef…, the counters at cf8b919d… and the verdict model at 72bb826e…. So the surface was carrying unmodified base code when the proof began, which the receipt asserts and could not prove from inside alone.

Restore by digest holds. Ten readings across the two containers, every one equal to the backup captured before the patch, with no backup left behind, bytecode cache cleared and scratch removed. The independent behavioural re-read afterwards shows the literal present again with all four entries and the patch token absent. That is a reading, not an assertion, and it is done twice over.

Two things I want to credit rather than pass over. Keeping the three probe lines that report zero registered projections, and saying in place that they are a fresh interpreter's own counters rather than the live runtime's, is the right call; deleting them would have been the dishonest move and a reader could genuinely have taken them for live numbers. And correcting the pull request body in the receipt, where the original argued consumer-first safety on grounds that hold against hiding loss and not against false alarm, is the distinction the run actually falsified. A lane that reports its own argument as the thing the measurement broke is doing the job.

On the options at the end. Not mine to decide, and the receipt is right to leave them open. One observation a countersigner can add: the coupling that turns this false alarm into an outage is the same dependency edge another proof removed today, so the severity of this finding is a function of whether that change has landed on the lane in question. That does not make the ordering problem go away, since the false alarm is wrong on its own terms, but it does bear on how urgent the choice is.

@jonahgabriel

Copy link
Copy Markdown
Collaborator Author

Addendum proof: surface=devlane-201 (READ-ONLY) receipt=$OMNI_HOME/.onex_state/prepr-proof/OMN-19081-devlane-201-declaration-20260921T221820Z/receipt.txt sha256=598f93166e80fdccda5999f62fa7de5badb215adbe103bbaacd9f4237805c957 result=PASS

This closes the one gap the dogfood-101 PASS declared in its own text: the declaration path is now proven against deployed contracts rather than an injected value.

Read-only, and a lease was not needed

Every command was a read: docker exec ... cat and a version print. No hot-patch, no restart, no write. Read from inside omninode-runtime on the .201 dev lane:

omnimarket version 0.4.185 (floor 0.4.181, past it)
contracts total 409
contracts declaring key_grain 45 (zero on the dogfood-101 image)

The bytes are the deployed bytes

Four contracts were copied out and their sha256 matched in and out of the container before use, so the grader reads what the runtime carries rather than something reconstructed from this branch. Both readings agree, and they are in the receipt.

Four checks, all PASS

D1 — the branch reader against each deployed contract: immutable for work events and session replay, mutable for runner fleet and delegation. The delegation contract declares twelve mutable exposures, which are the same twelve the OMN-18908 gate's first draft wrongly refused over a constant partition, now reading correctly.

D2 — arm 3 without the injection. Both content-addressed exposures grade HEALTHY on the grain their deployed contract declares, with excluded_immutable_grain naming them and fallback_exempted_projections empty. This is the claim the dogfood-101 run could not make.

D3 — negative control: the two deployed mutable declarations, same rising series, both DEGRADED, fallback empty. Without it, D2 cannot be told apart from a reader that returns immutable for everything.

D4 — the decisive arm: the fallback removed entirely, passed as (), and the exemption still holds. D2 shows the fallback did not fire; D4 shows it could not have.

Credit, and a method borrowed

D4 exists because the countersigning lane added the equivalent arm to my first run, on the reasoning that two arms show a consequence but do not separate two candidate causes. That lane also confirmed from outside the container something I had asserted and could not prove from inside it, that my pre-patch capture was byte-identical to base and therefore a genuine pre-image. Both are recorded because the method is worth reusing.

Still unproven

The grader is exercised here as a library against deployed bytes, not as the running dev-lane monitor with this branch loaded. That would be a dev-lane hot-patch, which is not sanctioned for this and was not done. So this proves the declaration is present, correct and correctly read; the post-merge lab pass is what shows the branch running as the lane's own monitor.

No projection writer path was exercised, here or on dogfood-101. No divergence was induced anywhere. Nothing on the dev lane was written.

@jonahgabriel

Copy link
Copy Markdown
Collaborator Author

Pre-PR proof countersign: receipt sha256=5768c6ee1a995fa0a71d4753862aef309dc5f73c7e429ba2e18d95b8267e1163 actor=prepr-countersign verdict=COUNTERSIGNED

Countersigned. This supersedes the correct failure I countersigned earlier, and the regression it reports is closed. The digest matches the file and its own sidecar.

I reproduced all three arms off the surface, and they match the receipt exactly, including the values it reports for the fallback set.

ARM1 dev module, unconditional literal          HEALTHY   accumulating=()   fallback=n/a
ARM2 branch, contract empty, fallback covers    HEALTHY   accumulating=()   fallback=(HandlerProjectionWorkEvents,)
ARM3 contract says mutable, fallback still lists it  DEGRADED  accumulating=(HandlerProjectionWorkEvents,)  fallback=()

Arm two is the one that read degraded on the previous run and is why that run failed. It reads healthy now, by the fallback covering an unresolved grain rather than by the grading being weakened, which the fallback set in the same row shows.

Arm three is the whole proof and it is stronger than I expected. The exposure it declares mutable is still listed in the fallback constant. I checked that rather than assuming it: the constant carries four entries and that exposure is one of them. So the contract does not merely win where the fallback is silent, it wins where the fallback explicitly names the same exposure. A fallback that exempted unconditionally would have read healthy here. That is the rubber stamp this ticket exists to remove, and the arm rules it out rather than arguing it away.

On carrying the arm forward. You did not have to, and the run would have looked identical without it. Two arms would have shown the regression closed and left the reader unable to distinguish that from a fallback that exempts everything, which is the failure mode the change is most at risk of. Keeping a discriminator that only makes your own result harder to accept is the right instinct.

The defect you found in yourself is the best part of this receipt. A run that passes on status and is wrong on the page is the failure a status-only check cannot see, and the page is what an operator reads to decide what to do. Two sentences in one rendered string disagreeing, one saying the exposure is graded and the other saying the fallback exempted it, would have shipped a monitor that contradicts itself at exactly the moment somebody is trying to act on it. Finding that by reading the rendered detail rather than checking the status, then fixing and re-reading it, is the discipline the protocol is for. The corrected wording is right too: the token now states the fact it owns and leaves the consequence to the note that knows it.

Restore holds. Ten digest readings across both containers, all equal to the originals, with an independent behavioural re-read confirming the unconditional literal is back and the fallback constant is absent. Surface read rather than claimed at release, and the runner on that host untouched.

On what is not proven, which you state and I want to sharpen rather than soften. Every arm here, mine included, runs where the contract resolves nothing, so arm three exercises a declared grain through an injected value. What is proven live is the fallback path and the resolution order. What is not proven is the end-to-end read from a deployed contract that actually carries the field. My reproduction has exactly the same limit, so a second signature does not close it. That gap needs a runtime at the version that carries the declaration, and until then the ordering is proven and the source is not.

Ordering is correct. The pull request is still a draft with auto-merge unarmed, lease and release bracket the run, and the follow-up for deleting the fallback is keyed to a reading from inside the runtime container rather than to a date or a pin, which is the right trigger for a temporary construct.

@jonahgabriel

Copy link
Copy Markdown
Collaborator Author

Pre-PR proof countersign (addendum): receipt sha256=598f93166e80fdccda5999f62fa7de5badb215adbe103bbaacd9f4237805c957 actor=prepr-countersign verdict=COUNTERSIGNED

Countersigned. This closes the exact gap I named in my countersign of the re-run, and it closes it better than I asked for.

I confirmed the deployed bytes are the deployed bytes, without touching that host. All four contract digests the addendum records match, byte for byte, the same contracts in the repository at the merge commit of the version the container reports running.

work_events      d52c8ddb…   session_replay   6902bec1…
runner_fleet     5cdbf601…   delegation       abf9c26b…

That matters because it is the claim I could not otherwise check. The addendum asserts the grader read what the runtime carries rather than something reconstructed from the branch, and it proves it from inside by digesting in and out of the container. Matching those same digests against the released source, from outside, is independent corroboration that the container is carrying the released contracts and that the version it reports is the version it runs.

On the gap. My countersign of the re-run said the fallback path and the resolution order were proven live while the declaration path was not, because every arm ran where contracts answer nothing and the declared-grain arm had to inject its value. This removes the injection. The grains come from contract bytes read out of a runtime past the floor, forty-five of four hundred and nine contracts declare the field there, and zero do on the other image, which is exactly why the earlier run could not make this claim.

D4 is a better construction than the arm I contributed, and I want to say so plainly. My arm showed that one candidate cause was sufficient. D4 removes the other cause entirely: pass an empty fallback and the exemption still holds, carried by the declaration alone. D2 shows the fallback did not fire; D4 shows it could not have, because it was not there. Together they attribute the exemption to the declaration by measurement rather than by absence of evidence, which is the stronger form. Attributing an outcome by showing the alternative was absent beats showing it merely did not report.

D3 is the arm that keeps D2 honest and would be easy to omit. Without two deployed mutable declarations grading degraded on the same series, D2 cannot be distinguished from a reader that returns immutable for everything. Three of the four checks exist to make the fourth mean something, which is the right ratio.

On what remains unproven, the statement is correct and I am not softening it. The grader is exercised as a library against deployed bytes, not as the lane's own running monitor with this branch loaded. That would be a hot-patch of the shared dev lane, which is not sanctioned for this and was rightly not done. So this proves the declaration is present, correct and correctly read, and it does not prove the branch running as the monitor. The post-merge lab pass is where that comes from, and it should be read on its own rather than treated as implied by this pair.

Read-only is read-only here. Every command recorded is a read, no lease was taken beyond the lane control label, and nothing was written. That is the right posture for a question that a read can answer, and taking a lease for it would have cost another lane a surface for nothing.

I will note the credit paragraph only to say that the method travelling is the point, and that D4 improved on what it borrowed rather than repeating it.

…t here

OMN-18910 shipped the drop dimension's exemption as a four-entry literal
naming the two content-addressed exposures, and said in its own comment that
the literal was interim and this was the successor step. OMN-18908 has since
landed key_grain on all 65 projection exposures, so the fact is declared and
the copy in this repository is a second, unsynchronised source of it.

That copy fails quietly in both directions. A genuinely content-addressed
exposure added tomorrow gets no exemption unless somebody remembers to edit a
tuple in a different repository, so its intended idempotence reads as loss. An
exposure wrongly added to the tuple is exempt forever with no evidence behind
it, which is the rubber stamp the declaration exists to prevent.

The grain now rides the registration. The wiring resolves it from the contract
at wiring time and hands it to the counters; the health monitor asks the
counters and reads no contract itself. Nothing imports omnimarket: this reads
the raw contract YAML on exactly the seam _read_dlq_topics already established
for event_bus.dlq_topics, for the same reason, and the literal is deleted
rather than left beside its replacement.

UNRESOLVED IS A THIRD ANSWER AND IS NAMED. A projection whose grain cannot be
resolved is GRADED, never exempted, because silently exempting it would hide a
real accumulation behind a missing field. It is also reported under its own
outcome token rather than folded in with the mutable ones, because a reader
who cannot tell those apart cannot tell which remedy applies: one is a
contract edit, the other is an investigation.

A MIXED CONTRACT IS NOT EXEMPT. A projection is treated as immutable-grained
only when it declares at least one exposure and every one of them is
immutable. The delta its writer discarded might belong to either exposure, so
exempting it on the strength of the immutable half would hide loss on the
mutable half. Conservative in the direction that grades.

This reader NEVER raises, unlike the DLQ reader it copies. That one guards a
destination, where a broken contract must surface; this one feeds an
observability exemption, and a contract it cannot parse must not be able to
stop a projection from being wired. Unparseable resolves to UNKNOWN, which is
graded and named.

RED FIRST: 16 of the 18 new tests failed against this tree before the change,
including the assertion that the literal no longer exists. Every refusal is
paired with a control that must stay green: a mutable declaration is not
exempt, the same accumulation on a mutable grain does grade DEGRADED, and a
resolved grain raises no unresolved token. Two tests drive the shipped wiring
factory rather than a stand-in, because the accessors could all be correct
while the seam never passes a grain, and that is the shape that ships a silent
regression past every other test in the file.

mypy --strict clean on 3126 files; 8324 tests pass across runtime, services
and the OMN-18910 integration seam.

NOT PROVEN HERE: no live lane. The current dev lane reports both dimensions
HEALTHY over 34 dispatching projections with the two exemptions named, so the
behaviour to preserve is measured; whether this change preserves it on that
lane is a post-merge reading, not a claim of this pull request.
… window

The first shape of this change deleted the literal outright and read the
contract alone. The pre-PR proof on dogfood-101 measured that against a
runtime carrying omnimarket 0.4.178: the declaration landed in 0.4.181, none
of that image 409 contracts carried the field, so both content-addressed
exposures resolved to unresolved, their intended idempotence was graded as
loss, and projection_delta_dropped went DEGRADED on healthy behaviour. On a
lane whose container probe runs with degraded-policy fail, that is an outage
rather than a false alarm.

Operator ruling, option two. The contract declaration stays the source of
truth and WINS wherever it answers. The literal survives only as the fallback
consulted where the contract resolves nothing, which is exactly the window in
which a deployed runtime predates the declaration.

The resolution order is the design, and it is what keeps the literal a
window-cover rather than a second opinion: a projection the contract declares
MUTABLE is graded even when it appears in the fallback, so the list can never
override a live declaration.

The unresolved-grain outcome token stays, now as a REPORT rather than a
decision. A projection the fallback exempts is still named as unresolved, and
the rendered detail says how many exemptions came from the literal rather
than from a declaration, because a reader deciding whether the list can be
deleted has to see which exposures still depend on it.

The two alternatives were refused for stated reasons, recorded beside the
constant: refusing to grade below a version floor is a gate blinding itself,
and exempting every unresolved exposure exempts ALL of them on a
pre-declaration runtime, so the dimension would grade nothing for the window.

TESTS, five added, and the failed case is now the regression test. The 0.4.178
replay reads HEALTHY. The contract wins over the fallback, which is the
positive control for the resolution order. A contract-declared immutable name
the fallback has never heard of is exempted by its declaration and not
credited to the literal. A projection absent from both is GRADED, which is the
negative control without which the fallback could be widened to everything
and every other test would still pass. And an unresolved grain is still
reported when the fallback exempts it.

One existing test is REVISED rather than deleted: it asserted that no literal
exists at all, which is the shape the proof falsified. It now asserts the
absence of an UNCONDITIONAL literal and the presence of the scoped fallback,
with the reason in its docstring.

Deletion of the fallback is tracked in OMN-19093 and its trigger is a
READING, not a date: the deployed dev-lane omnimarket at or above 0.4.181 read
from inside the runtime container, because a pin states intent and the
container states fact, and the gap between them is what this proof fell into.

mypy strict clean on 3126 files; 8329 tests pass.
…llback

Found by reading the rendered detail on the proof re-run rather than by
checking only the status. With the fallback covering an unresolved grain, the
health detail said the projection was GRADED rather than exempted while the
sentence beside it said the fallback had exempted it. Both sentences in one
string, disagreeing, on the surface an operator reads to decide what to do.

The token now states the FACT it owns, that no grain resolved, and leaves the
consequence to the fallback note that actually knows it. The constant docstring
carried the same wrong assertion and is corrected with it.

Nothing about the grading changes; this is the prose the grading prints.
jonahgabriel pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Sep 21, 2026
…ibase_infra#3943 (#10753)

* evidence(OMN-19081): author OCC companion for OmniNode-ai/omnibase_infra#3943

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

* evidence(OMN-19081): self-bind OCC#10753 + rebind contract_sha256

---------

Co-authored-by: omnimarket-bot <bot@omninode.ai>
Two gates refused this branch and both were right.

INTEGRATION COVERAGE. The unit tests hand the grain to the counters directly,
which proves the resolution order and not the thing the dogfood-101 proof had
to measure by hand: that a grain written in a contract.yaml on disk reaches
the dimension verdict through the real wiring factory with no step in between
supplying it. Those are different failures. Every accessor can be correct
while the wiring never passes a grain, and that shape ships a silent
regression past every unit test in the sibling module, because the exemption
would then come from the fallback on every runtime including ones whose
contracts declare it, and nothing would say so.

Four integration tests walk the chain the runtime walks, from a contract on
disk through the shipped factory and the process counters to the graded
verdict. The handler class name is deliberately one this repository has never
heard of, so an exemption it receives can only have come from the contract.
Controls: the same seam with a mutable declaration grades DEGRADED, an
undeclared grain on a handler the fallback does not cover is graded and
named rather than exempted, and an absent contract file never stops a
projection being wired.

RELEASE IDENTITY. This repo requires a version bump when packaged source
changes, and I had concluded otherwise earlier from the fact that not every
merge carries one. The gate is explicit: merging code onto an already
published version aliases two code states under one image version. 0.38.51 is
published and dev has moved to 0.38.53, so this rebases onto dev and takes
0.38.54, enumerated live against every open pull request rather than assumed
free.

Both failures were mine rather than fleet conditions, and I checked that
rather than assuming the wide spread of red meant an outage.
@jonahgabriel
jonahgabriel force-pushed the jonah/omn-19081-key-grain-from-contract branch from 73d095f to 23e000e Compare September 21, 2026 22:49
@jonahgabriel
jonahgabriel enabled auto-merge (squash) September 21, 2026 22:50
…ion bump

The version bump feeds the runner image shared_env_digest, so the recorded
digest went stale and the build smoke gate refused the branch. Regenerated
with the command the gate itself names, rather than editing the lock by hand.

Attribution checked rather than assumed: the same verify run passes on the
canonical dev clone and fails on this branch, so the staleness is this change
and not a fleet condition.
# Conflicts:
#	docker/runners/runner-image.lock.json
# Conflicts:
#	docker/runners/runner-image.lock.json
#	pyproject.toml
#	uv.lock
@jonahgabriel
jonahgabriel marked this pull request as ready for review September 22, 2026 23:13
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Hostile Reviewer — DEGRADED (informational)

Critical findings: 0
Major findings: 0
Total findings: 8
Models succeeded: glm-review

Note: Fewer than 2 reviewer models succeeded. Degraded results are informational (OMN-8468/OMN-8524) and do not block merge. Error: cli_review exit 2 (fewer than 2 models succeeded — partial/total outage)


Semantics (OMN-17492 — the model finds, thread resolution gates)

Surface Meaning Blocks merge?
Review threads Per-finding, posted by the reviewer No (informational)
Hostile Review Thread Gate Deterministic: unresolved hostile-reviewer threads exist Fails until resolved (not yet a required context)
degraded verdict Fewer than 2 models succeeded (infra) No

Powered by omniintelligence.review_pairing.cli_review — multi-model adversarial review: qwen3-review, qwen3-review-b, glm-review (OMN-8468/OMN-8524/OMN-17492)

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hostile Reviewer — adversarial findings (OMN-17492)

Models succeeded: glm-review
Models failed: codex
New finding threads: 0
Deduped (already posted on this PR): 0
Below quorum (one model only, reported not threaded): 6
Nit-level findings suppressed: 2

The model is the FINDER, never the gate: merge is gated only by the
deterministic Hostile Review Thread Gate, which blocks while
hostile-reviewer threads are unresolved. Resolve each thread after
addressing (or rejecting, with a reply) its finding.

Below quorum: 6 finding(s) raised by one model only (OMN-18479)

These are reported and NOT dropped, but they get no thread and do not block: a single model's finding no other model reproduced is not evidence enough to stop a merge. Read them; act on them if they are right.

  • [MINOR] src/omnibase_infra/runtime/auto_wiring/handler_wiring.py, resolve_key_grain (glm-review) — Grain vocabulary is not validated; contract typos silently grade as mutable | resolve_key_grain maps any grain value other than the exact string 'immutable' to 'mutable'. A contract typo such as key_g
  • [MINOR] src/omnibase_infra/runtime/observability/projection_apply_counters.py, register (glm-review) — First non-None grain wins; conflicting declarations are silently ignored | ProjectionApplyCounters.register overwrites a None grain with a real one but never downgrades or conflicts-checks against an
  • [MINOR] src/omnibase_infra/runtime/health/projection_apply_flow.py, FALLBACK_IMMUTABLE_GRAIN_PROJECTIONS comment (glm-review) — Fallback deletion trigger references a version reading that is not implemented | The comment states DELETION IS TRIGGERED BY A READING of the deployed omnimarket version from inside the runtime contai
  • [MINOR] src/omnibase_infra/runtime/auto_wiring/handler_wiring.py, _read_declared_key_grains (glm-review) — except (OSError, Exception) is a redundant tuple that catches everything | Exception subsumes OSError, so the tuple is equivalent to a bare except Exception. The noqa BLE001 comment suggests intent to
  • [MINOR] tests/integration/test_key_grain_contract_seam_omn19081.py; tests/unit/runtime/health/test_key_grain_from_contract_omn19081.py (glm-review) — Integration seam tests assert on object-typed verdicts | Both _rising_verdict in the integration module and _verdict in the unit module declare return type object, so attribute assertions (verdict.exc
  • [MINOR] src/omnibase_infra/runtime/auto_wiring/handler_wiring.py, _read_declared_key_grains (glm-review) — Empty-string key_grain declaration is conflated with no declaration | _read_declared_key_grains skips grain values that are empty strings (isinstance str and truthy required). A contract that explicit

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