Skip to content

fix(iris_production_item): save the production class, not only the live config - #413

Open
PYDuquesnoy wants to merge 1 commit into
masterfrom
fix/408-production-mutations-save-the-class
Open

PYDuquesnoy wants to merge 1 commit into
masterfrom
fix/408-production-mutations-save-the-class

Conversation

@PYDuquesnoy

Copy link
Copy Markdown
Contributor

Closes #408.

add, remove, set_settings, enable and disable all ended in tProd.%Save(), which writes the
extent (Ens_Config.Item) — the live configuration — and then optionally
Ens.Director.UpdateProduction to apply it. None called SaveToClass(), the step the Management
Portal performs, so the class's XData ProductionDefinition kept the old item list. SaveToClass
appeared 0 times in crates/ (control: iris_production_item appeared 66, so the search works).

Staleness turned out to be the mild half

Reproduced on a disposable IRIS. The XData block is what a compile of the production class
replays into the extent, so the stale class is not merely out of date — it is authoritative the moment
anyone compiles it:

start                          extent [Added, First]            XData 2 items
add w/o SaveToClass            extent [Added, First, Ghost]     XData 2 items
recompile the stale class      extent [Added, First]            XData 2 items

Zz408.Ghost is gone. No error, no warning, and the tool that added it had already reported
success: true.

So the impact is worse than the issue states. It is not only that the plugin's [IIS-DRIFT] guidance
writes a stale class to src/ — any later iris_compile of a production class silently undoes an
iris_production_item mutation
, and compiling a production class is a normal step in this workflow.

The fix, on the same object, in the same run:

step Ens_Config.Item (live) XData ProductionDefinition
baseline 1 item 1 item
Items.Insert + %Save() 2 items 1 item
SaveToClass() 2 items 2 items

The third row is also the control the second needs: the same reader that reported 1 reports 2
immediately afterwards, so "the XData still has one item" is a measurement rather than a broken
instrument.

Measured API facts

Name=SaveToClass  ClassMethod=false  FormalSpec=pItem:Ens.Config.Item=$$$NULLOREF  ReturnType=%Library.Status

An instance method with an optional single item, so it is called on the object the codegen already
has open — no re-open. It returned success, and left the extent untouched. It is emitted after
%Save() succeeded and before UpdateProduction, which is the Portal's order.

A failed class save is a third state, not a failure

By the time SaveToClass runs, %Save() has succeeded and the change may already be live. Reporting
failure would tell the caller to retry a mutation that has happened. Swallowing it is the same defect
in new clothes — this repo's recurring shape. So the action still reports OK and carries
class_saved: false, class_error, and a warning that names the consequence (a recompile reverts
this) and the trap: the obvious next step, iris_doc(mode=get) then writing the class to src/,
is exactly what makes the stale class authoritative.

The CLASS_NOT_SAVED marker rides on its own line after the OK line. A %Status chain's text is
multi-line; a marker written first would push the OK line past where the parser looks, which is the
truncation #347 fixed elsewhere. With it last, an arbitrarily long reason cannot corrupt the
production name. read_class_save_marker returns None only when the class WAS saved, and an empty
reason still returns Some — decaying to None would report a class that was not saved as saved.

build_set_enabled_code

enable/disable was the one mutating action whose codegen was an inline format! rather than a
named function, so it was the one a parity assertion over the builders structurally could not see. It
is now a named function like its four siblings, and
every_mutating_action_saves_the_production_class iterates all five. #409, in this same file, was
exactly a guard that one sibling had and the other did not; five arms is five chances to repeat it.

Verification

  • fmt --check rc=0
  • this file 13/13
  • cargo clippy --workspace --all-targets -- -D warnings rc=0
  • cargo build --workspace rc=0
  • cargo test --workspace with the IRIS env unset rc=0 — 1855 passed, 0 failed, 69 ignored across
    66 binaries
    (66 test result: lines, so the total is a measurement and not an empty grep)

Mutation check: 15 mutants over the 13 assertions, 15/15 killed by the predicted assertion, file
restored byte-identical (sha checked) and the post-restore baseline green. Two of the fifteen are the
ones worth naming, because they are what a weaker test would have missed:

  • SaveToClass called but its %Status discarded. A contains("tProd.SaveToClass()") assertion
    passes here while every failure is lost — the class stays stale and the envelope still says
    class_saved: true. The assertion also requires $$$ISERR(tSCC), so the verdict must be examined.
    This was added before the first run, from the lesson in fix(iris_interop_query): pin search_table to the extent's document class #412: its parity assertion passed with its
    subject deleted, because it matched a string that also appeared in the projection.
  • The verdict written before the OK line. Caught by
    the_class_verdict_is_reported_after_the_ok_line, which asserts position rather than presence.

The new assertions live in their own file rather than being appended to the #[cfg(test)] mod in
interop.rs: measured over 13 PRs in this repo, every cross-branch conflict was an appended test
module, and several open PRs touch this file.

Scope note

The e2e tests that would exercise this against a live production are ignored without IRIS env set,
per the gate. The live evidence above was gathered by driving Ens.Config.Production directly on a
disposable instance rather than through the tool, so what is proven is the mechanism and the API
contract, not the assembled tool path. The five codegen sites and the four response arms are covered
by the unit assertions.

🤖 Generated with Claude Code

https://claude.ai/code/session_01N7fLbq3ftb82ub45QCpsPB

…ve config

add/remove/set_settings/enable/disable all ended in tProd.%Save(), which writes the
extent (Ens_Config.Item) and is the LIVE configuration, then optionally
Ens.Director.UpdateProduction to apply it. None called SaveToClass(), the step the
Portal performs, so the class's XData ProductionDefinition kept the old item list.
SaveToClass appeared 0 times in crates/ (control: iris_production_item appeared 66).

Measured on a disposable IRIS, staleness turns out to be the mild half. The XData is
what a COMPILE of the production class replays into the extent:

  after Items.Insert + %Save()   extent [Added, First, Ghost]   XData 2 items
  recompile the stale class      extent [Added, First]          XData 2 items

Zz408.Ghost is gone. No error, and the tool that added it already reported
success: true. So any later iris_compile of a production class silently undoes an
iris_production_item mutation — and the skills plugin's drift guidance ("iris_doc get
the production class and write it to src/") writes that stale class to disk first,
making it authoritative.

A failed class save must not fail the action: by the time it runs the live change has
landed, so reporting failure would tell the caller to retry a mutation that already
happened. Swallowing it is the same defect in new clothes. Third state — the action
reports OK with class_saved: false, the reason, and a warning naming the consequence
(a recompile reverts this) and the way out. The marker rides on its own line AFTER the
OK line, because a %Status chain's text is multi-line and a marker written first would
displace the production name, which is the truncation #347 fixed elsewhere.

enable/disable's codegen is extracted into build_set_enabled_code. It was the one
mutating action whose code was an inline format! rather than a named function, so it
was the one a parity assertion over the builders structurally could not see — and
#409, in this same file, was exactly a guard one sibling had and the other did not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7fLbq3ftb82ub45QCpsPB
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.

[mcp:iris_production_item] add and set_settings do not save the production class, so iris_doc get returns stale XData

1 participant