Skip to content

fix(iris_production_item): add/enable/disable/set_settings could not find items of a production that never ran - #381

Open
PYDuquesnoy wants to merge 4 commits into
masterfrom
fix/item-lookup-without-runtime-index
Open

PYDuquesnoy wants to merge 4 commits into
masterfrom
fix/item-lookup-without-runtime-index

Conversation

@PYDuquesnoy

@PYDuquesnoy PYDuquesnoy commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Closes #379

(Link added after the fact: this PR implements #379 — it replaces FindItemByConfigName with an explicit For zfi=1:1:tProd.Items.Count() walk in the four arms that issue names, and item_lookup_without_runtime_index.rs covers it — but nothing in the PR referenced the issue, so merging would have left it open. Verified against the diff before linking.)


Issue 379. The same defect #372 fixed for get_settings, at the three remaining ObjectScript sites —
and one of them fails in the opposite, worse direction.

Measured, driven through the real tool

IRIS 2026.1, a production configured, compiled and never started:

tProd.Items.Count() -> 1, the name present
tProd.FindItemByConfigName("Dup.BO.Target") -> NO object, ERROR #00: (no error description)
^Ens.Runtime("DispatchName") -> does not exist, 0 entries

enable on an item that exists -> ITEM_NOT_FOUND, and the message then lists that item
set_settings on an item that exists -> the same
add of an item that ALREADY exists -> success: true, and Ens_Config.Item then held TWO rows

That last line is the reason all three move together. The duplicate guard is an EXISTENCE test, so
the same broken lookup fails in the OPPOSITE direction: finding nothing reads as "no duplicate" and
the write proceeds. It corrupts the production config rather than refusing, and a fix applied to two
of the three would leave it looking more trustworthy than it is.

FindItemByConfigName resolves through the runtime dispatch index, which is only populated once a
production has been started or updated. This fork's workflow is to configure items and then run
tests, so the items of a production being BUILT are exactly the ones it cannot find.

The fix

All three inline the same config walk, and ITEM_WALK_MARKER names it so a test can prove they all do
and none has drifted. remove already walked the config correctly — that pattern pre-existed; these
three simply did not use it.

After the fix, against the same never-started production: disable succeeds, set_settings succeeds,
add REFUSES with ITEM_EXISTS and the item count stays 1, and enable on a genuinely absent item still
reports ITEM_NOT_FOUND. Positive and negative control both.

Also corrected: the MISSING_PARAMETER text said "nothing was sent to FindItemByConfigName", which
named a method nothing calls any more.

One mutation survived twice, on a false witness each time

Changing the walk's comparison to .Name="" — which makes it match nothing, reproducing the defect —
survived two versions of the assertion:

  1. code.contains(".Name=") is still true for .Name="".
  2. code.contains(".Name=\"My.Item\"") is satisfied by add's own Set tItem.Name="My.Item", the
    line that CREATES the item. The needle matched an assignment, not the comparison.

It is now asserted on the walk LINE, one per item asked for. Red at the third attempt.

The population check needed narrowing too: it flagged the caller-facing message that mentions the
method in prose. tProd. is what makes an occurrence a call.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com

PYDuquesnoy and others added 4 commits September 23, 2026 23:04
… trip

Item 3 of issue 327. Measured over 31,009 parsed tool_use blocks: the tool arrives in bursts
(53 runs of 2, 14 of 3, up to 8) and get_settings is 296 of the 513 action invocations inside
them — the bursts are mostly reading several items' settings one at a time. 174 calls go away.

## One program, whatever the count

`get_settings` now builds one framed program for one item or twenty. Not two paths: a second
copy of the read is the #105 shape, where the copy nobody exercises keeps the old behaviour and
looks more trustworthy for having a working sibling. The RESPONSE shape still differs — a call
that passes no list reports exactly what it always did, so no existing caller changes.

## The framing, and why it is not just newline-separated

The single-item program wrote bare `Name=Value` lines. With more than one item those cannot be
attributed, and a value is free text. Read from a live production for the fixtures
(`Comun.Produccion`, IRIS 2026.1, read-only over Atelier — 10 items, 65 settings):

* `ReplyCodeActions` is `:?R=F,:?E=F,:~=S,:?A=C,:*=S,:I?=W,:T?=C`, so splitting on every `=`
  truncates a real setting at `:?R`.
* 23 of the 65 settings are Adapter-targeted, and `Setting.Target` is not in the output.

So each section declares how many settings to expect — the mechanism #347 used to make a short
`%Status` chain detectable — and a line matching no marker is appended to the previous value
rather than dropped.

## What a caller can now tell apart

* `Missing` — the production holds no such item.
* `Unreadable` — the section carried neither a count nor a marker. Neither of these is an item
  with no settings; reporting them that way answers a failure with a fact (#310).
* `settings_incomplete` — fewer (or more) settings arrived than IRIS declared: the output was cut.
* `duplicate_setting_names` — the rows that DID arrive collapsed in the map, because the program
  drops `Setting.Target`. Pre-existing, now counted instead of silent.

`success` stays about the CALL; `all_found` and `missing` are about the answer, and a single
short section is enough to make `all_found` false. Every name absent returns the same
ITEM_NOT_FOUND envelope a single-item call has always given, built by the one reader that has
always built it.

## No Quit in the missing branch

Measured on IRIS 2026.1: a `Quit` inside an `If` block returns from the METHOD. One missing item
would have ended the run, and every later item would have been absent from the output —
silently, since absence is how a caller reads "no settings". That is why `item_not_found_block`,
which ends in `Quit` by design, is not reused per item; its candidate block is emitted once.

## A defect this change had to fix in the existing guard

`ACTIONS_NEEDING_AN_ITEM`'s refusal read `params.item` alone. A caller passing only
`items: ["A","B"]` — the shape the new parameter invites — was told "nothing was sent to
FindItemByConfigName" while having named two things: a true sentence about the wrong parameter,
which is the failure mode this issue is about. The guard now asks whether ANY spelling named
something, and a list of exactly one name is filled into `item` so every action accepts it.

Only `get_settings` reads a list of several. On every other action a list of more than one is
refused by name rather than acted on for the first and dropped for the rest.

## Verification

fmt, clippy -D warnings, cargo build --workspace, and the gate with IRIS env unset: 5 result
lines, 0 FAILED. 26 new tests, fixtures read from a live production rather than invented. 14
mutations applied one at a time — including "only the first item is written into the program",
"the missing branch Quits", "the value is split on every equals sign" and "the handler reads
`item` alone again" — each red on its named tests, restored green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…l spelling

CI caught this: `all_four_sites_and_all_four_readers_use_the_shared_definitions` went red on the
previous commit, and it was right to. That test is the sibling-defect guard for ITEM_NOT_FOUND,
and the batch read changed the POPULATION it counts.

`get_settings` no longer interpolates `{not_found}`. It cannot: the shared block ends in `Quit`,
which returns from the METHOD, so one missing item would end the whole program and every later
item would be absent from the output. It emits the same declared-count candidate protocol
itself, once, after the last item.

So the instrument now counts what the claim is about — every generator that can report a missing
item emits a declared candidate count, and every reader routes through the one envelope builder —
rather than one particular call spelling. Both halves are still 4; the fourth member changed.

The reader count needed a real counter, not a substring match. `text.matches("item_not_found(")`
reads 5, because `interop.rs` has a test called `production_item_error_mapping_item_not_found` —
an identifier that merely ENDS with the name, which is the commonest false witness in a
source-reading guard. `call_sites` skips declarations and requires a non-identifier character
before the name, with three controls: a declaration counts 0, that test's name counts 0, and a
real call counts 1 — so the two negatives cannot be satisfied by counting nothing at all.

Also the reason this reached CI at all: the local gate ran the targets I expected to matter
rather than the targets that read what the change touched. The target list is now derived —
grep the tests directory for the symbols and files the diff moves — which finds this one and
nine others.

Mutations, both red on the guard and restored: get_settings stops emitting the protocol inline;
the batch reader stops routing through the shared envelope builder.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… that never ran

Ran the batch read against a real production for the first time. Every get_settings call — the new
batch path AND the pre-existing single-item path — reported ITEM_NOT_FOUND for items the production
demonstrably has. The refusal even contradicted itself:

  Item not found: Probe.BS.Feed
  The production has 3 item(s): Probe.BS.Feed, Probe.BO.Out, Probe.BO.NoSettings

## Measured, on IRIS 2026.1 against a configured-but-never-started production

  %OpenId("IOProbe.Produccion")               -> object, no error
  tProd.Items.Count()                         -> 3, all three names present
  tProd.FindItemByConfigName("Probe.BS.Feed") -> NO object, ERROR #00: (no error description)
  ^Ens.Runtime("DispatchName")                -> does not exist, 0 entries
  Ens.Director.GetProductionStatus            -> running production '', state 2
  walking tProd.Items and matching .Name      -> resolves all three, settings and Targets intact

FindItemByConfigName resolves through the RUNTIME dispatch index, which is only populated once a
production has been started or updated. This fork's whole workflow is to configure items and then run
tests, so the items of a production being BUILT are exactly the ones it cannot find — and the %Status
it sets carries no text, so there was nothing to report.

The batch program now walks tProd.Items and matches .Name, which is config-level, needs no runtime
index, and is what build_list_items_code already did successfully.

NOT FIXED HERE: enable, disable, set_settings and add use the same call and fail the same way on a
never-started production. Filed separately rather than widened into this PR.

## Two more defects the live run found

* The wire marker leaked. `synthesise_not_found_payload` built "ERROR:ITEM_NOT_FOUND:Item not found:
  …", and `item_not_found` takes its message from line 0 — so the caller's `error` began with the
  marker. The single-item arm strips it before handing the payload over; the synthesised one must not
  add it back. Same leak #358 fixed for INTEROP_ERROR.
* The response SHAPE regressed for every existing caller. `items` non-empty selects the batch
  payload, and `item_names_arg` folds a single `item` into that same vector — so a plain `item=` call
  came back as `results[]` instead of `{item, settings}`. Now keyed on `list_parameter_given`, which
  asks whether a LIST key was supplied. The pure-function test could not see this: it sets the flag
  by hand, and the WIRING computed it wrongly.

## One mutation survived, and it was the actual defect

Reverting the codegen to FindItemByConfigName passed all 25 tests in the file, because they feed the
PARSER hand-written marker text and cannot see the ObjectScript — the third time this week that
blindness hid a wrong premise about what IRIS does. The new program-level assertion is red against
it.

Its first control was also wrong — an exact count of GetAt references, which failed on correct code
because the trailing candidate block has one of its own. Replaced with a count of per-item walks.

Verified end to end after the fix: the 3-item batch returns 4, 3 and 0 settings with all_found true;
one-real-one-missing gives all_found false with the miss named; all-missing gives ITEM_NOT_FOUND with
no wire marker; `item=` returns the legacy shape and `items=[one]` the list shape.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…find items of a production that never ran

Issue 379. The same defect #372 fixed for get_settings, at the three remaining ObjectScript sites —
and one of them fails in the opposite, worse direction.

## Measured, driven through the real tool

IRIS 2026.1, a production configured, compiled and never started:

  tProd.Items.Count()                         -> 1, the name present
  tProd.FindItemByConfigName("Dup.BO.Target")  -> NO object, ERROR #00: (no error description)
  ^Ens.Runtime("DispatchName")                -> does not exist, 0 entries

  enable on an item that exists         -> ITEM_NOT_FOUND, and the message then lists that item
  set_settings on an item that exists   -> the same
  add of an item that ALREADY exists    -> success: true, and Ens_Config.Item then held TWO rows

That last line is the reason all three move together. The duplicate guard is an EXISTENCE test, so
the same broken lookup fails in the OPPOSITE direction: finding nothing reads as "no duplicate" and
the write proceeds. It corrupts the production config rather than refusing, and a fix applied to two
of the three would leave it looking more trustworthy than it is.

FindItemByConfigName resolves through the runtime dispatch index, which is only populated once a
production has been started or updated. This fork's workflow is to configure items and then run
tests, so the items of a production being BUILT are exactly the ones it cannot find.

## The fix

All three inline the same config walk, and ITEM_WALK_MARKER names it so a test can prove they all do
and none has drifted. `remove` already walked the config correctly — that pattern pre-existed; these
three simply did not use it.

After the fix, against the same never-started production: disable succeeds, set_settings succeeds,
add REFUSES with ITEM_EXISTS and the item count stays 1, and enable on a genuinely absent item still
reports ITEM_NOT_FOUND. Positive and negative control both.

Also corrected: the MISSING_PARAMETER text said "nothing was sent to FindItemByConfigName", which
named a method nothing calls any more.

## One mutation survived twice, on a false witness each time

Changing the walk's comparison to `.Name=""` — which makes it match nothing, reproducing the defect —
survived two versions of the assertion:

1. `code.contains(".Name=")` is still true for `.Name=""`.
2. `code.contains(".Name=\"My.Item\"")` is satisfied by `add`'s own `Set tItem.Name="My.Item"`, the
   line that CREATES the item. The needle matched an assignment, not the comparison.

It is now asserted on the walk LINE, one per item asked for. Red at the third attempt.

The population check needed narrowing too: it flagged the caller-facing message that mentions the
method in prose. `tProd.` is what makes an occurrence a call.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

enable/disable/set_settings/add cannot find items of a production that never ran — FindItemByConfigName uses the runtime dispatch index

1 participant