feat: Kagaku.Flow — the scheduler runs a run (Phase 2 · slice 6c) #29
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/kagaku-flow-engine"
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 6 of Phase 2, part c of four. It builds on 6a (#23) and 6b (#28), both merged.
FlowEnginenow runs a run end to end:6d (cancel, retry, recovery and the loop) follows. Nothing in
src/Tsumugi.*changes; the only other file touched is the plan's slice-6 line.What
Kagaku.Flow.SchedulingFlowEngine.StartRunAsync(RunRequest)returns aRunStartResult, which is either the run or aRunStartRefusal.OwnerHasLiveRun,SnapshotDoesNotLoad,SnapshotInvalidandSnapshotEmpty.ConsumesConcurrencySlotneeds a free slot. Plain rows never take one.IConcurrencyLimitSource, read in each pass's scope; the default is 3.TransientFailureException.invalid-config/rule-faileddoes this; see decision 3.RunStarted,NodeStarted,NodeDelta,NodeSettled(which covers exclusions and give-ups) andRunSettled.RunSettledfires once per transition to a terminal status.Kagaku.Flow.Extensions:AddKagakuFlow()builds the registry and the engine from the host's descriptors, rules, executors, ledger, sink, logger, clock and options.Kagaku.Flow.Testing:FlowEngineDrivergives hostsTickAsyncandRunUntilQuiescentAsync(maxPasses)to drive the engine in tests.RunUntilQuiescentAsyncdrains before it throws once the pass budget is spent.RecordingRunEventSinkrecords the events it receives.Why
Decisions to confirm
The rest fill gaps and need no amendment:
""as their path at the top level. Run events carry no node and no path.RunStartedon revival, andNodeSettledfor orphan resets, requeues and cancelled rows, arrive with 6d.BackgroundServicein the core; 11a hosts the engine.IRunEventSinkis required, with no silent default.RunRequest.RunId: an id that is already used makes the ledger throw, which is a host bug, not a refusal.Departures from the brief
FlowEngineStartTests; the recovery tests that combine it with orphans and retry stay in 6d.Nudge()yet. With no loop there is nothing to wake, so 6d adds it.OperationCanceledExceptionis an attempt failure.Passes_never_overlaplands in 6c. It needs onlyTickAsync, and the gate (P2) is 6c's.A_limit_of_zero_or_less_holds_every_slot_row_and_lets_plain_rows_run.…the_options_default_applies,…_executors_rules_and_extensionsand…_and_the_rest_keep_declared_orderreword the brief's names.…ledger_sink_logger_clock_and_optionsnames what the test now checks.A_budgeted_consumers_merge_sink_whose_first_arm_is_excluded_in_the_same_pass_is_known_by_its_later_arm.Known, out of scope:
ValidationCodes.cshas carried atsumugi.context-without-consumerexample in a doc comment since 5a. It is a word, not a reference, so the isolation test passes, but it should be reworded some day.Out of scope
RunSettled, retry, the orphan sweep and recovery, the loop, dispose and the log narration. These are 6d.Tests
5db7933)The Release build has 0 warnings under
TreatWarningsAsErrors. The property category passed 3 extra runs with random seeds.FlowEngineStartTests,…BudgetTests,…AttemptTests,…RenderTests,…ExclusionTests,…EventTests,AddKagakuFlowTestsandFlowEngineDriverTests, plus theEngineWorldharness.DOTNET_PROCESSOR_COUNT=1, and 6 as concurrent processes. No test waits on real time, apart from the hang guards and one bounded wait in a test sink, both of which the brief allows.Coverage. Measured with coverlet per changed file, lines and branches (
branch="True"), over facts and theories only. Every new or changed file is at 100%:Scheduling/FlowEngine.csScheduling/FlowEngine.Attempts.csScheduling/FlowEngineOptions.csScheduling/RunRequest.csScheduling/RunStartResult.csExtensions/ServiceCollectionExtensions.csRuns/RunStatuses.csTesting/FlowEngineDriver.csTesting/RecordingRunEventSink.csIConcurrencyLimitSourceandRunStartRefusalhave no executable lines.Mutation probes. Each was run on a copy, then restored and
touched, with every test run under a time limit.§12's [C] probes, 54 mutants, all killed: 28a/b, 29, 30, 31a–f, 32a/b, 33, 34a/b, 35, 36a–c, 37a–c, 38a/b, 39a/b, 40a/b, 41a–c, 42a–d, 43a/b, 44a/b, 50–55, 56a–c, 58a–c, 64a/b and 65. Three notes:
IRunLedgerlists only live runs.StartRunAsync, which is the intended failure;The verifier's 101 mutants:
Verification round (before opening)
An independent verifier found no blockers. It raised 12 findings, 6 should-fix and 6 nits, and all 12 are applied in the one commit:
🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagaku.Flow - 99.7%
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__ColorToken_2
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__Socket_1
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__TypeId_0
Kagaku.Flow.OpenRouter - 100%
Kagaku.Flow.Testing - 100%
Tsumugi.BlazorAdapter - 96.3%
Tsumugi.Domain - 95.5%
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
Tsumugi.Infrastructure - 97.4%
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
on
tion
n
Tsumugi.Kernel - 90.9%
Tsumugi.Server - 100%
Tsumugi.UseCases - 98.2%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! The scheduler finally runs a run~ Four thousand nine hundred lines and the engine breathes: gate, passes, budget, attempts, deltas, settles — and every single interleaving pinned with parks instead of sleeps. I read the full diff, every changed file, and then I got to play~ ♡
Verdict: ✅ Looks good to me~
What I did, mechanically, in a clone at
fd881f4:TreatWarningsAsErrors. Full suite: 2082/2082 — Flow 1000, OpenRouter 71, Domain 328, UseCases 54, Integration 305, BlazorAdapter 324 — matching your table to the digit.FlowEngine.cs131/131+50/50 across both partials,RunStatuses12+20, options/request/result/extensions/driver/sink all covered. Zero unvisited sequence or branch points.free -= …dropped → limit test red); E8's retry boundary (<→<=→ attempt-count test red); E9's uncancellable settle token (CancellationToken.None→token→ theCanBeCanceledpin red); the delta gate's true timing mutant (early close removed, single late close after the announce → the parked-sink gate test red — my first two attempts at this mutant were near-equivalents, which says something about how tightly that window is pinned~); the give-up idempotence guard; the in-flight skip via the settle-window pin (FailAttemptalready shows Pending — that test is the one that actually dies, lovely); view eviction; and the E7 log helper (rethrow restored → 7/7 of the logger theory red at once).Two things I chased and cleared:
RuleFailedis deliberately not inResumeCodes, andGraphValidationcatches the rule's throw itself (line 305), so a rule added since the start can't brick a resumed run. Pinned by yourthrowing host ruletheory row. Also confirmed §12's unconstructible mutant 43:ListLiveRunsreally is live-only, so "refuse a settled owner run too" cannot be written. Correct~💡 Little ideas (non-blocking)~
tests/…/Support/SignallingClock.cs—DueTimes,LiveTimersandWaitForLiveTimerAsynchave zero callers in this PR; the engine creates no timers until 6d's loop. The doc comment names its consumer, so this is scaffolding one slice early rather than rot — same call I made for #28'sClock-until-slice-8, and I'd leave it to Björn's taste: keep (6d lands next) or strip until then.✅ What I liked~
Park/Gate/SinkPark/ScopeProbelet every test place itself inside an interleaving — between the settle and the scope disposal, inside an announcement, inside the gate — with no thread sleeps anywhere. 20s hang guards instead of infinite waits. This is how concurrency gets tested, fufu~WhenIdleAsynccompletes after removal, soRunUntilQuiescentcan't lie.A_resumed_run_must_pass_every_core_code_but_invalid_config_and_rule_failedreflection sweep even pinsResumeCodesagainst futureValidationCodesadditions — sneaky and wonderful~ ♡6d's cancel, retry, orphan sweep and the loop — I'll be waiting. Fufufu~ ♪
Automated review by Jibril · 2026-09-25
CI/CD: absent for head
fd881f4(no bot comment) · Local checks: build 0w/0e, suite 2082/2082, coverage 100% L+B on changed files, 8/8 mutation probes killed, tree pristine