Skip to content

demand engine: a request under a different nature is a durable key conflict - #13611

Closed
briansrls wants to merge 2 commits into
mainfrom
demand-nature-conflict
Closed

briansrls wants to merge 2 commits into
mainfrom
demand-nature-conflict

Conversation

@briansrls

@briansrls briansrls commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Roadmap: native-memory-demand-nature (D15 census #10, gunbc.plans.demand_engine_program).

Defect

v2.std.demand_engine demand_engine_request decided what to do with an existing identity from existing.nature alone and ignored the requested nature:

  • a FreshEffect request attached to a PureComputation entry, including one already holding DemandAvailable;
  • PureComputation and IdempotentEffect both share, so one key served two different contracts;
  • a changed WorldRead staleness envelope was ignored.

Change

  • On an existing identity the request compares the complete stored and requested natures with the ladder's own std.materialization_ladder nature_eq before any attachment or re-production. Checking only the requested nature is not enough: Fresh→Pure would then attach.
  • An unequal pair marks the entry DemandBlocked { BlockedKeyConflict { stored, requested } }. The arm already existed with no producer; it now carries both natures.
  • The conflict is durable. demand_readiness previously preserved only settled and cycle-blocked states, so a bare blocked state would have been re-derived to ready. Now:
    • readiness carries the conflict;
    • seal does not overwrite it with a cycle verdict;
    • settlement of an in-flight production counts the production but keeps the conflict;
    • later requests under either nature leave it standing.
  • Direct dependents are re-derived at the conflict (out-degree, same as settlement), and read a conflicted prerequisite as BlockedByRefusedPrerequisite. They do not wait with no cause.

Running demands (second commit, after review at ca19fc4)

The first commit read the seat off the state, so a verdict that replaced DemandRunning moved the seat with it:

  • an own-key conflict on a running demand released its seat at the conflict, while the production was still running;
  • a running dependent whose prerequisite was then conflicted was re-derived to BlockedByRefusedPrerequisite without releasing its seat, and its later report overwrote that refusal with a value.

Now:

  • DemandEntry carries seat_held, and only the producer's own report (demand_engine_settle, via demand_entry_settled) moves it. in_flight is the delta of that bit, and its rebuild counts it.
  • A verdict never touches the seat. The report releases it.
  • A report does not replace a key conflict or a refused-prerequisite block (demand_state_stands_over_a_report); it still counts as a production.
  • Readiness does not re-derive a seat-holding demand to ready or waiting. Only a block lands on it.

Evidence

v2.test.demand_engine.demand_engine has ten new claims; all 37 claims in the module PASS (claim_batch, worktree-built binary, local, at 94b26f2).

Mutant Conflict claims (6) Positive controls (2)
old decision (stored nature only), new types all FAIL PASS
conflict without readiness preservation a_key_conflict_survives_seal_settle_and_rerequest_holds FAILS PASS
requested nature's sharing only all FAIL PASS

Running-state claims, against the engine at ca19fc4 and one mutant:

Engine a_conflict_on_a_running_demand_keeps_its_seat_until_it_reports_holds a_running_dependent_keeps_its_seat_and_refusal_when_a_prerequisite_conflicts_holds existing in-flight and durability controls
ca19fc4 (seat read off the state) FAIL FAIL PASS
seat fixed, report overwrites a refused-prerequisite block PASS FAIL —
this PR PASS PASS PASS

The original engine source does not resolve against the new claims (BlockedKeyConflict has no stored/requested), which is why the first row keeps the new types.

Witnesses lane (local, claim_executor --required-ci --required-lane witnesses, 94b26f2): parse and resolve clean; all ten new claims planned-and-passed; 16 wet refusals, none in this change's closure: the 15 known local host refusals (mtcollins1_kvm_observer_protocol_wet_witness ×7, allocation_client_execution_wet_witness ×8) and v41_source_patch_converge_witness red5, which also refuses on #13577's head and whose cause is unresolved. The first lane run caught a // annotation inside a declaration body that claim_batch had accepted; fixed by moving it to module grain.

Not in this PR (still open on the roadmap node)

  • Repeated same-key FreshEffect. Two requests leave one runnable entry while productions counts both. With the seat now held on the entry, a second Fresh request while the first runs leaves the entry waiting until that first production reports, and the report then answers both. Neither the old behavior (re-offered while running) nor this one is right; it stays with that task. The existing fresh_effect_is_never_attached_holds checks counters only. Closing it needs a control that requests twice, admits and settles once, then requires a second distinct execution or an explicit refusal.
  • No native effect is suppressed today. The native planner's four contracts (native_demand_plan_module) are all PureComputation. This fixes a generic engine defect; it does not repair an observed native effect suppression.

🤖 Generated with Claude Code

…nflict

demand_engine_request decided sharing from the stored entry's nature alone, so a
FreshEffect request could attach to a PureComputation entry (including one already
holding an available result), and Pure/Idempotent or a changed WorldRead envelope
were treated as one demand.

The request now compares the complete stored and requested natures with
std.materialization_ladder nature_eq before any attachment or re-production. An
unequal pair marks the entry DemandBlocked { BlockedKeyConflict { stored, requested } }
(the arm existed with no producer; it now carries both natures). The conflict is
durable: readiness, seal and settlement carry it rather than re-deriving over it,
later requests leave it standing, and direct dependents read it as a refused
prerequisite instead of waiting with no cause.

Witnesses: Pure/Fresh in both orders, Pure/Idempotent in both orders, WorldRead
envelope both directions, conflict after an Available result (dependent leaves the
ready order), conflict surviving seal/settle/re-request and never admitted; positive
controls that equal natures still attach and share one execution. Against a mutant
keeping the stored-nature decision the six conflict claims fail and the two controls
hold; against a mutant without readiness preservation the durability claim fails.

Out of scope and still open (roadmap native-memory-demand-nature): two same-key
FreshEffect requests leave one runnable entry while counting productions; distinct
executions are not yet demonstrated. The native planner's contracts are all
PureComputation, so no suppressed native effect is claimed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-09T04:01:48.427515Z ca19fc4 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ca19fc47be

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/v2/std/demand_engine.dag Outdated
dependents_of: e.dependents_of,
prerequisites_of: e.prerequisites_of,
ready: demand_ready_remove(ready: e.ready, id: id),
in_flight: e.in_flight + demand_in_flight_delta(before: demand_entry_state(entry: existing), after: conflict),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep conflicted running work in the in-flight count

When the conflicting entry is DemandRunning, this delta decrements in_flight immediately even though its producer is still executing. Its eventual settlement preserves the conflict and computes a zero delta from conflict to conflict, so the seat is never held during the remaining execution and demand_engine_next can admit work beyond the offered capacity. Keep the seat occupied until the running production actually settles.

Useful? React with 👍 / 👎.

}
let direct = demand_engine_dependents(e: marked, id: id)
DemandEngine {
entries: demand_engine_reevaluate_only(e: marked, ids: direct),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve running dependents until their production settles

If a dependent is already DemandRunning when its previously available prerequisite receives a conflicting request, this reevaluation overwrites the dependent with BlockedByRefusedPrerequisite while leaving in_flight unchanged. When that production later settles, its prior state is no longer running, so no decrement occurs and the seat remains counted forever, preventing further admissions at that capacity. Preserve the running lifecycle or otherwise retain enough state to release its seat on completion.

Useful? React with 👍 / 👎.

…ves the report

The seat is now carried on the entry and moved only by the producer's own report. A key conflict on
a running demand no longer releases its seat early, and a running dependent whose prerequisite is
then conflicted keeps its seat until it reports, and keeps its refusal when it does. Readiness no
longer re-derives a seat-holding demand to ready.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Superseded by #13641 at 9fc502c: folded into integration/v1-closeout. Branch kept for archaeology. — sent from neat-wolf-604

@gunbai-bot gunbai-bot Bot closed this Oct 9, 2026
@gunbai-bot gunbai-bot Bot mentioned this pull request Oct 10, 2026
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