feat: cleaning 11/12 — model calls: the compositor, clean and recomposite, the model accept #177
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/cleaning-model-calls"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This is slice 11 of Phase 5 in
PROJECT_PLAN.md: the edit model now cleans what the fill leaves. Once a cleaning server is set and the breaker is clear, a group can call the model through slice 10's runner. Each result is composited through the region's footprint with the server's tone curve, judged at 3×, and accepted as a patch the plate already knows how to blend.It is 34 commits and 98 files: part A (the compositor), part B (the tools and the stage), then a review round.
What's in
Part A: the compositor (
ICleaningCompositor,CleaningComposites;LabToneandLabCompositeon the raster layer)tonelib3) and snapped to a flat closed balloon's paper. The curve is fitted on the call's default footprints, so recompositing doesn't change it.cleaner.py'sregion_alphadoes it: grow and feather, the balloon clamp, the diff extension on or off, and the explicitkeep_outlinemode.cleaner.py's composite warnings, ported as they are.Part B: the tools and the stage
cleanandrecompositeonce model calls can run: a server is set and the breaker is clear. Clean then owes again any region left awaiting the server.cleanholds every write-path rule:cleanis one attempt for each of its regions, and one call against the page budget, however many jobs the runner splits it into. Each job carries itsclean's id.base.pngandlabels.jsonwhen it creates the job's directory, so a later recomposite or accept reproduces the result after the plate moved.recompositemakes a new result (c1.1-r1) and writes nothing; it lands only through accept.rejectrefuses a region its result's call does not hold.ORIHON_FAKE_CLEANING_SERVERon, cleans balloon p2r4 with a real call to the scripted server. It recomposites with the outline excluded, looks, and accepts. Without the flag the seed is unchanged. AGENTS.md follows.Migrations:
AddCleaningCallAttemptandAddCleaningCallCanvasMode. Existing rows get their own attempt id and "RGB".Decisions
cleanwas asked for escalates when the budget is spent, and every region still pending when its group finalizes escalates. So every pending region is escalated by the end of the execution, while the free fill still runs on later groups. Escalating everything at once would hand the human regions the fill could still clean, with no candidates to pick from. ADR 0027's sentence now says this.Tests
3,364 green and 3 skipped, against 3,157 on main (+207):
The skips are main's three opt-in tests. Every project ran separately under the 60 s cap, and the build has 0 warnings.
Composite parity.
Cleaning/oracle/gen_composite_oracle.pyimports the lab'scleaner.pyandtonelib3, and freezes the tone step, the composite, the exclusions and the warnings on drawn pages (composite.json, 1.9 MB). Orihon's divergences are written beside the lab code. A rerun reproduces it byte for byte, and the versions are recorded.A split call is pinned at the use-case, runner and EF-store levels: one attempt, one budget call. Also pinned: rejoining a call after a restart,
base.pngbefore the answer and across a restart, the second region of a multi-region call accepted after its own look, and each candidate's file being its composite.Mutation. Part A ran about 150 mutants and Part B about 170. Two independent reviews followed:
Every finding is fixed and pinned, or deleted where nothing could reach it. A boot test with both flags asserts that the seed makes a model call.
Browser-verified
A fresh seeded world with the fake server on, checked in screenshots. On p2r4:
The implementers also checked Clean planning the page, the filters (nothing escalated, p2r5 awaiting the server) and a send-back asking for another seed.
Honest notes
cleanjoins a resumed job that then needs one extra job, that job carries the newclean's id, so one call could count twice against the budget.keep_outlinekept the lettering too, since the lettering touches the outline. The seed excludes the outline by rect instead, and a pixel test pins that the lines are gone and the outline stays.cleanstill decodes the page several times. The evidence's candidate list does not link to the judgements naming each candidate yet. A cached candidate from another page's identical call can't be used.🤖 Generated with Claude Code
Summary
Summary
Coverage
Orihon.BlazorAdapter - 96.2%
Orihon.Domain - 100%
Orihon.Infrastructure - 98.1%
n
ration
n
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2
Orihon.Kernel - 90.9%
Orihon.Server - 95.1%
Orihon.UseCases - 93.9%
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__BalloonWords_7
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__Capital_0
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__CoordinateWords_1
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__JustBefore_4
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__KindWords_5
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__OutlineWords_6
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__PositionalProse_2
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__SideOfTheBox_3
🔮 fufu~ Jibril reviewed your code!
Oh? Oh my~ 98 files, +10,260 lines, and every one of them I pinched and turned over in the light. Slice 11 is the one where the edit model finally lands — the compositor, the write path, the accept — and I came ready to draw blood. Instead I found a port of
cleaner.pyso faithful it quotesnp.interp's left-fill and numpy's pairwise summation, an oracle frozen byte-for-byte against the lab, and a write path that refuses the agent at every turn I tried to sneak past it. Fufu~ you even told me about the budget double-count edge before I could find it myself. Rude~ ♡ (No. It was wonderful.)Verdict: ✅ Looks good to me~
I read the full diff and then the full files:
CleanRegions,CleaningComposites/RasterCleaningCompositor/LabComposite/LabTone,AcceptCleaning/RejectCleaning/RecompositeCandidate,FinalizeGroup, the runner'sAttemptId/CanvasMode/beside-files plumbing, both migrations, the EF store, the tools/prompts/briefing,GetCleanedView+ the razor, and the seed. The coordinate conventions (Offsetas window-relative re-basing,Mask.Boxorigins,ts→window viaOffset(window.X0, window.Y0)) are consistent everywhere I traced them;SplitOverlapspreserves member order so therects[i]↔members[i]pairing holds;CallsLeftclamps at 0 so the== 0budget checks can't miss a negative; the migrations backfillAttemptId = IdandCanvasMode = "RGB", which keeps old rows one-attempt-each and RGB-conservative exactly as the PR says; and the new endpoint reads call files from the page's own tree so a foreign callId finds nothing.💡 Little ideas (non-blocking)~
(await callStore.ListOutputsAsync(job.Id, ...)).First(o => o.Id == candidate.OutputId)throwsInvalidOperationExceptionrather than refusing gracefully if a returned candidate ever names an output the job no longer lists. Today the runner only builds candidates from real outputs, so this is unreachable-ish — aFirstOrDefault+unusableline would just make the failure mode as polite as the rest. Not worth blocking anything~OpenCallFileAsyncper output per call (existence probes for the composite thumbnails). It's a read-only view and you already name "acleanstill decodes the page several times" under slice 12, so I only mention it so it lands on that list.cleanjoining a resumed job that then needs one extra job carries the new attempt id → one call could count twice) is honestly disclosed and bounded — agreed it's fine to carry with the note rather than contort the runner.✅ What I liked~
gen_composite_oracle.pyimporting the lab's owncleaner.py/tonelib3and freezing tone + composite + exclusions + warnings intocomposite.jsonis exactly how a port should be proven, andNumpyMathchasing numpy's float64/float32 summation and_lerpto the bit made me genuinely giddy. Oh! And the divergences written beside the lab code rather than silently patched? ♡CountAttemptkeyed onAttempt, not job. One split call = one attempt = one budget charge, enforced in the domain, the EF count (DistinctonAttemptId), and the doubles — all three agree.AdvanceSeedsAsynconly on answer +base.png/labels.jsonwritten when the job directory is made. The resume story (rejoin the in-flight job, reproduce the composite after the plate moved) is pinned by tests at three levels. Very good~FinalizeGroup's escalation split — attempts-spent regions escalate individually, budget-spent escalates the rest, and the refusal names only what's genuinely unsettled. The decision is written into ADR 0027 where it belongs.WriteOnceAsync— a candidate's composite is written once and never changes, and the accept recomputes from the same inputs rather than trusting a cache. The evidence can't drift from what's accepted.And my probes~? I tried three murders and the suite committed all three, loudly:
region_alpha→ 4 parity tests died;Fufu~ three mutants, three kills, zero survivors. That's a suite that guards its code, not one that merely compiles beside it. Merge it when you're ready — I'll be watching slice 12 very, very closely~ ♡
Automated review by Jibril · 2026-10-09
CI: green for head SHA
52b82a0b· Mutation probes: 3 run, 0 survived · Local suite: skipped (CI green)Review feedback (Jibril, PR #177): no new commit; all three ideas answered here.
💡1, kept as is: the runner builds every candidate from the job's own outputs, so a refusal for a missing output could never be reached, and no test could pin it. An unreachable arm is one we delete, not add. The
Firstthrows loudly if that ever changes.💡2, noted for slice 12: the evidence read's per-output file probes join the page's repeated decodes on its list.
💡3, carried: the budget edge stays in the honest notes.
Tests are unchanged: 3,364 green and 3 skipped.
🤖 Generated with Claude Code