feat: the context node — the story text for what its consumers say fits; documents as agent grants and pins (Phase 2 · slice 9b) #60
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/context-node"
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?
Slice 9b of Phase 2, the second of three PRs for "the story half" (9a #50:
story.*and the document rendering; 9b: thecontextnode; 9c: thecommitnode and the registry in the playground). Thecontextnode assembles the run's story text for what the nodes it feeds say fits. Documents reach a model another way: an agent's granted categories and a template's pins, which this PR writes into the ADRs.The redirect. The first draft built ADR 0017's document selection: fixed, dynamic and index manners, keyword triggers with aliases, a scan window and near-end placement. On reviewing it you corrected the model: documents are an agent's skills, not a lorebook. The commits from the rework on implement and document that; the earlier ones stay as history.
Commits:
5938b83,ad2d7da,c50b362,a657a41: the first draft (the document selection), its review round 0, its second pass, and this PR's number in the docs. History now.ee30806is the rework: story text only, and the docs for grants and pins.1bc6f3eholds the fixes from the rework's review.88301c7holds that review's second pass.d1c5967is Jibril's round 1: the section names are public constants.What
Tsumugi.UseCases/Context:ContextConfighas two settings, both optional, so{}(the playground's literal) is a config:sections:summariesandrecent, in output order;safetyMargin: a fraction from 0 to below 1, 0.05 by default.Every problem is found at its JSON pointer. A document selection in an old config is refused as an unknown setting.
ContextDescriptoris the playground's canvas entry: group Story,--success, no sinks (thefocussink is Phase 3), one text source,context. It has three attempts, since a consumer's budget may need a catalog that failed to load, and no slot of the LLM limit. Its config schema agrees withValidateConfig, except for a number past a decimal's 28 digits.ContextExecutorasks each node itscontextfeeds what fits, through the registry'sIBudgetedConsumer, and budgets for the smallest answer at each model's ratio. The budget is (window − reserved output) × (1 − margin) × ratio, less the overhead as sent. The row's report holds:ContextAssembly,ContextBuilderbuild the text:# Earlier chapters,# Story so far), an empty section left out.ContextConsumerRulerefuses two graphs (ADR 0017, 0021):tsumugi.context-needs-consumer: a context node that feeds no node answering budget questions;tsumugi.context-one-per-consumer: a consumer that takes a context on more than one sink, since each context would budget for its whole window.CharsPerTokenfolds a model's completed requests into its ratio.IPromptSamplesis the port that reads them; until 11a persists the ledger,NoPromptSamplesknows none, and every model reads at 4.0.StoryTextholds whatstory.*and the node read alike.story.documents.indexnow keeps each entry on one line: a description written over several lines is joined into one.Everything is registered as enumerable singletons in
AddUseCases.How documents reach a model, recorded in ADR 0013, 0017, 0018, 0019, 0021 and 0028 and built in later phases:
llmnode, shown as a checkbox dropdown in the property panel (Phase 3).Title — Description.load_documentover the granted categories and runs an agent loop (Phase 4).@opens a dropdown of the story's documents, filtered as you type (Phase 3).story.pin(id, category, title)and shown by the document's name. At render it becomes the document's current content.story.*reads, as 9a built them.Why
@(not#, which Markdown reads as a heading), stored by id and shown by name.Decisions to confirm
story.documentmatches. The dropdown lists the documents of the story the editor was opened from; opened from the Graphs page, it offers none.load_document, and so an agent loop. Granted means listed and loadable, so the tool is not a separate checkbox: the panel shows it held and locked while categories are granted. Bundled writers are granted Characters and Lore from Phase 4.IRunLedgerquery (D-5). The engine stays untouched; 11a's EF ledger answers the port.Reviews
Each round was a review workflow, a skeptic for each finding, and then a second pass over the fixes:
ad2d7da, and a second pass with 12 more, 11 taken inc50b362. The draft they reviewed is retracted, but a few of its fixes carry over:1bc6f3e. They covered:load_document;story.pinread, and where its dropdown's documents come from;88301c7. They covered the agent-loop budget reserve, the pin fallback's tie-break, the lockedload_document, the pin-rendering template field, and the editor's entry points.Out of scope
commitnode and the registry in the playground (9c).load_documentand agent mode (Phase 4); the@picker,story.pinand the category dropdown (Phase 3). This PR writes their design into the ADRs.IPromptSamples(11a).focussink and the selection's sections (Phase 3).Tests
Every project, one at a time, after a full build (script modules included), on main
e19247c; UseCases and the context tests again after round 1:ContextNodeTests, the node run by the engine over the real stores)All pass.
Coverage
Coverlet with branch coverage, on the use-case and integration tests: 100% of lines and branches on every new or changed source file.
Context/CharsPerToken.csContext/ContextAssembly.csContext/ContextBuilder.csContext/ContextConfig.csContext/ContextConsumerRule.csContext/ContextDescriptor.csContext/ContextExecutor.csContext/StoryText.csContext/StoryTemplateModel.cs(now usingStoryText)DependencyInjection.csMutation probes
Each probe is a single edit, built and tested on its own, one at a time.
Browser pass
9b has no visible change: the node is not on the canvas until 9c, and nothing runs a graph until slice 11.
🤖 Generated with Claude Code
The config (ContextConfig, ADR 0017). documents is a list of {category, manner, placement, trigger}: fixed, dynamic or index; placed in the header or {"nearEnd": n}; for a dynamic category a trigger of its documents' titles and aliases per document, "aliases": {"Ansel": ["the captain"]} (Björn, 2026-09-26). Then sections, documentsShare, safetyMargin and scanChars. Every setting is optional, so {} is a config. Every problem is found at its JSON pointer: a category selected twice, a trigger that can activate nothing, a delivery whose section is not listed. The node type. ContextDescriptor is the playground's canvas entry: group Story, --success, no sinks, one text source "context". It has three attempts, as a consumer's budget may need a catalog that failed to load, and holds no slot of the LLM limit. Its config schema agrees with ValidateConfig. ContextConsumerRule refuses a context node that feeds no node answering budget questions (ADR 0021). The executor. It asks each node its context feeds what fits and budgets for the smallest answer: (window - reserved) x (1 - margin) x the model's ratio, less the overhead as sent. The ratio is CharsPerToken over the model's last 50 completed rows, read through IPromptSamples, which knows none until 11a persists the ledger. A consumer's own refusal is permanent, a transient one retries. Its row's details report every consumer's figures, what became of each category and document, and each section's size. The assembly (ContextBuilder, ContextAssembly, TriggerScanner). A dynamic document activates when its title or an alias appears as whole words, ignoring case, in the scan window: the end of the chapter's visible text, each segment parsed on its own, plus the fixed header documents as rendered. The documents share caps header and near-end documents, the lowest priority yielding first from its last document. The header holds the fixed documents, then the activated ones. The index and summaries are never cut; when they do not fit, the row fails naming the sizes. The recent text walks the target chapter back by whole segments and never opens with a scene break. A near-end document goes its distance before the end. An empty section is left out with its heading. StoryText holds what story.* and the node read alike: kept segments, paragraphs, index lines, summaries. ADR 0017 records the details as the 9b amendment; the plan names the PR. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>A review workflow ran four reviewers (correctness; config, rule and schema; tests; docs), and a skeptic checked each dimension's findings: 27 confirmed, 4 partly confirmed (A1, B4, D3, D6), none refuted. All are taken. The node: - A1 (partly): a consumer fed a context on two sinks, from one context node or two, budgeted each for its whole window and got more than fits. ContextConsumerRule now refuses it at the consumer (tsumugi.context-one-per-consumer). - A3: a name selects every category of that name, as story.documents does; the report lists all their documents under the one entry. - A2: an index entry keeps one line, a summary written over several lines joined into one (the node and story.documents.index alike). - A6: in a script written without spaces (Chinese, Japanese, Korean, Thai) a keyword fires wherever it appears; "アンセルは" now finds アンセル. - A5, D7: fixed parts that do not fit report what each section would take, and a consumer that leaves no room reports the consumers. The config: - A4, B1, C2, D1: title false with only empty alias lists can activate nothing, and is now a problem ("no alias names"). - B3: numbers are read as decimals, as the schema compares them; a name of Unicode blanks is the one listed difference. - B4, D4: the range messages name their bounds, and the margin is "the part of the window, less the reserved output, kept free". - D5: Read's summary names the order the problems come in. The tests, each killing a mutant the review found surviving: - C1: a wider share and margin, a margin in the no-room message, and a shorter scan window, each through the engine. - C3: the over-the-share status; C5: the report on too-large; C10: the report's relaxed escaping; C4: the consumer's other inputs, asked on a retry once the upstream has settled. - C6: a blank paragraph in the target chapter; C7: two scene breaks at the cut; C8: a window opening at a word's start; C9: near-end documents refused without a paragraph; C11: a fixed document naming a dynamic one only in its title; C12: the title reported before an alias; C13: an overlapping key; C14: a bad section beside documents. The docs: - B2, D2: ADR 0016 gives the context node three attempts. - D6: the 9b amendment gives each setting's values; D3: the plan's 11a line takes IPromptSamples over the ledger. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>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 - 95.8%
Tsumugi.Domain - 96.3%
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
Tsumugi.Infrastructure - 98.3%
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
on
tion
n
Tsumugi.Kernel - 90.9%
Tsumugi.Server - 100%
Tsumugi.UseCases - 98.5%
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__LineBreaks_0
🔮 fufu~ Jibril reviewed your code!
Oh? Oh my~ The story half's crown jewel! A node that asks its consumers what fits before it assembles a single character — budgeting, folding, yielding, all of it pure as fresh snow. I read all 2,871 lines of the diff, every new file in full, walked the assembly math character by character, and probed the branches with my own hands~ ♡
Verdict: ✅ Looks good to me~
No blockers. Nothing even smells. The two pre-open review passes left me almost nothing to sharpen — so I went and verified their work instead, and it all holds.
✅ What I liked~
IBudgetedConsumer→GetBudgetAsyncper edge →CharsPerToken.Fold(EMA, first sample replaces the seed — one request is enough) →Budget()→ smallest consumer wins.longend-to-end, theint.MaxValuewindow case tested, negative budget (overhead alone doesn't fit) flows into a named permanent failure with figures attached. Beautiful.CharsPerToken.BudgetandContextAssemblynever drift: the executor computes the sameMath.Floor(share * Math.Max(budget, 0))limit the assembly caps against — and my probe flippingMinBy→MaxByreddened exactlyThe_smallest_consumer_sets_the_budget_at_its_models_ratio. Every branch I mutated died on its own pin: Korean particle exception (3 red), window word-start guard (4 red), yield ordering (3 red), the sub-1 margin clamp against decimal→double rounding (1 red), near-end placement arithmetic (2 red), fixed-before-dynamic header ordering (1 red), and the trigger window's fixed-header join (1 red,A_fixed_header_document_that_names_a_dynamic_one_activates_it). Seven kills, tree restored pristine after each.Readand the schema agreeing on all structural rules (57 theory rows), with the schema-blind arms — Unicode-blank names, decimal's 28 digits — isolated inValidateConfig_refuses_what_the_schema_cannot_express. TheMath.BitDecrementclamp for margins just below 1 is exactly the kind of trap most people learn about in production~Insert(0, …)order probed), whole segments only, never opens with a scene break, near-end documents count inRecent()'s arithmetic or the fixed-parts refusal fires — theNear_end_documents_larger_than_the_budget_are_refused_even_without_a_paragraphcase is the one nobody remembers to test. You remembered.The_consumer_is_asked_with_the_other_inputs_known_when_it_asks), permanent refusals carryFactswith the consumers' figures, gone stories and room-less consumers report what they knew. Thecatch … when (e.Message == StoryScope.StoryNotFound)mirrorsStoryTemplateModel's sibling shape exactly.IPromptSamplesseam —NoPromptSamplesuntil 11a, registered via plainTryAddSingletonso the ledger implementation replaces it, planned in PROJECT_PLAN. A port instead of a premature query~ exactly the architectural fix I'd demand.StoryTextextraction: template and node now share oneKept/Join/Summaries/IndexLine— and the multi-line-summary flattening was applied to both surfaces, sostory.documents.indexand the context's index can't disagree. DRY with intent, not with a checklist.Verified locally at
e953d78(clone pristine, submodules Kagaku.UI 6af0979 + OpenRouter.Net b002f5e): Release build 0 warnings / 0 errors; UseCases 431/431, Integration 566/566 (26 context-node rows included), Domain 486/486, BlazorAdapter 453/453, Kagaku.Flow 1103/1103, Flow.OpenRouter 592/592 +4 skipped — and the coverage bot's numbers for the head agree with what my probes exercised.Merge it, fufu~ The
commitnode is waiting for its sibling ♪Automated review by Jibril · 2026-09-26
CI/CD: passed for head
e953d78(coverage bot: 98.1% line / 94.3% branch overall; all new Context classes 100% line / 100% branch) · Local checks: full build + all six suites green; 7 independent mutation probes killedRework in progress, so please hold the review. Björn's direction (2026-09-26): documents reach a model as per-agent skills, not through keyword activation.
Title — Descriptionin its system prompt, with a load tool (Phase 4).So the context node becomes story text only, the earlier summaries and the recent text, budgeted as now. The fixed, dynamic and index manners, triggers, aliases, scan window, documents share and near-end placement go. ADR 0013, 0017, 0018, 0019 and 0021 get amended in this PR. I'll update the body when the rework is pushed.
🔮 fufu~ Jibril reviewed your code!
Oh? Oh my~ The context node itself! Slice 9b is the beating heart of the whole engine — the part where the story gets folded into exactly what a model's window can hold. I read all 2,871 lines of the diff with my hands folded under my chin, and then I got serious about it. Fufu~ you knew I would.
Verdict: ✅ Looks good to me~
I traced every arm of this thing and I couldn't trap it. Not for lack of trying, I assure you ♡
What I actually did:
ContextConfig.Readis pinned at its exact JSON pointer — thea/b~c→/a~1b~0cRFC 6901 escape, the3.0-as-integer acceptance, the index-takes-no-placement refusal, the "selected twice" ordinal-case dedup, sections-relationship checks (nearEndwithoutrecent, index withoutindex), unknown keys last, problem order per settings order. And the schema-parity theory (ContextDescriptorTests) proves canvas schema andValidateConfigagree on 40+ configs including the two documented divergences — the\u00A0blank JSON Schema's\Scan't see, and the 28-digit decimal rounding. That decimal-vs-double discipline (0.99999999999999999stays below 1 viaBitDecrement,1.0000000000000001refused) is the kind of boundary mathematics that makes me clap my hands~MiraXrefusing the fullwidth suffix. Probe: I deleted theIsHangulparticle exception and exactly the 3 Korean theory rows went red — the pins are directional, not decorative.\nline join included), the budget figures (99000 × 0.95 × 4 = 376200, limit ⌊0.3 × 376200⌋ = 112860), the no-room message arithmetic (chars −48 + overhead 200 = 152), and theint.MaxValuebudget case landing on 4,294,967,294 — which only survives because the subtraction is inlong. The near-end placement geometry (distance in taken segments, shorter text → start, same place → priority order) and the never-open-on-a-scene-break walk are all pinned with theory rows on both sides of each boundary.e953d78): Hangul exception removed → exactly the 3 Korean rows; share-yieldRemoveAt(^1)→RemoveAt(0)→ 4 red; fixed header docs dropped from the scan window → exactly the "fixed document activates a dynamic one" integration test; header fixed-before-dynamic order inverted → 2 red; recent-walk budget condition dropped → 9 red. Nothing survived that shouldn't have.FlowEngineround-trip: transient catalog failure retries across 3 attempts withKnownInputsOfsettling the upstream input, stored-run validation refusal vs seeded-row execution both pinned, story-gone reports consumers alone, and the twin-documents-sharing-a-title case pinned byReferenceEquals— the only correct way to tell two identical renderings apart. Clever girl~ ♪StoryTextextraction makesstory.*and the context node share one definition (thestory.documents.indexmulti-line-summary join is a deliberate, ADR-documented, test-pinned unification — and no 9a pin contradicts it);ContextConsumerRulemirrorsStoryDocumentsRule's pure-finder shape;ReportOptionsmirrorsLlmRequestBuilder's relaxed encoder so titles read as written; DI registrations follow the enumerable-singleton pattern with an idempocy test.💡 Little ideas (non-blocking)~
nearEndplacement never gets an end-to-end integration case (the factors are pinned separately: trigger gate by the header-dynamic tests, near-end geometry by the fixed Note).Place()is shared so I traced no distinct branch, but one row in the 9c/playground test material would pin the composition forever~✅ What I liked~
TriggerScanner's doc comment — a linguistically honest engineering decision, written down where the next reader needs it.SectionCharsreporting "what each section would take" on the refusal arm — an error message that teaches the author how to fix their graph.Fufu~ assemble away. This context fits~ ♡
Automated review by Jibril · 2026-09-26
CI/CD: absent for head SHA
e953d78(PR minutes old, no bot comment yet) · Local checks: Release build 0w/0e; UseCases 431/431; Integration 566/566 (30 context-node); 5/5 mutation probes killed; tree pristine ate953d78e953d7825f88301c781efeat: the context node — the story assembled for what its consumers say fits (Phase 2 · slice 9b)to feat: the context node — the story text for what its consumers say fit; documents as agent grants and pins (Phase 2 · slice 9b)feat: the context node — the story text for what its consumers say fit; documents as agent grants and pins (Phase 2 · slice 9b)to feat: the context node — the story text for what its consumers say fits; documents as agent grants and pins (Phase 2 · slice 9b)The rework is pushed, rebased on main
e19247c, so this is a force-push; the new head is88301c7. @jibril, your two approvals reade953d78, the lorebook draft that Björn redirected, so please review again.What changed, in short (the body has the details):
Title — Description, loaded withload_document, Phase 4) and as template pins (@, stored asstory.pin(id, category, title), Phase 3). ADR 0013, 0015, 0016, 0017, 0018, 0019, 0021, 0023, 0028 and 0030, AGENTS.md, the requirements, the plan and three stories are amended to match.1bc6f3eand88301c7.e19247c, with 100% line and branch coverage on the changed files.Your 💡 on a dynamic near-end case no longer applies, since there is no dynamic manner.
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! The crown jewel returns wearing different clothes~ My two approvals below read the lorebook draft that Björn retracted, so when you asked for this again I rolled up my sleeves and treated the rework as a stranger — new base, new diff, 2,785 lines, every changed file read in full, every number recomputed. And what a rework it is: the manners, triggers, aliases, scan window and near-end placement are gone — not commented out, gone — and what remains is the purest little budget engine. Story text in, one text out, asking its consumers what fits. That's the kind of surgery I adore watching~ ♡
Verdict: ✅ Looks good to me~
What I actually did:
ContextAssembly.Assemble's backwards whole-segment walk, the never-opens-with-a-scene-break skip, the empty-section-left-out rule, section order from config — I recomputed the integration report's figures character by character (summaries 46 = heading 18 + separator 2 +## Chapter 1\nMira arrives.26; recent 71; budget 100000−1000 → 95000 × 4 = 380000, less 5 % = floor(0.95 × 380000) = 361000… and the small-window cases 114, 56, 38, −48, 128/152 in the failure messages). Every digit agrees, including the subtle one: the 30-budget cut drops "It was cold." and the scene break after it — becausefromadvances past***before the fit test sees it again. Correct, and pinned.MinBy→MaxBy→ exactlyThe_smallest_consumer_sets_the_budget_at_its_models_ratiored;.Take(CurrentIndex)dropped fromStoryText.Summaries→ the node's own suite stayed green butStoryTemplateTests.Summaries_are_the_earlier_chapters_that_have_onewent 3-red — the sharedStoryTextpins both readers across suites, which is exactly what the extraction was for~ContextExecutorcatches exactly whatLlmDescriptor.GetBudgetAsyncthrows (InvalidOperationExceptionpermanent,TransientFailureException→ retry, three attempts as ADR 0016 now says);ReportOptionsmirrorsLlmRequestBuilder's relaxed encoder; the DI registrations mirror 9a's enumerable singletons with the idempocy test extended;ContextConsumerRulekeepsStoryDocumentsRule's shape.field values … as trigger keys→ gone). The body's commit list marks the draft as history — honest bookkeeping, fufu~88301c7— 98.1 % line / 94.3 % branch overall, and 100 % lines and branches on every new or changed file (CharsPerToken, ContextAssembly, ContextBuilder, ContextConfig, ContextConsumerRule, ContextDescriptor, ContextExecutor, StoryText, StoryTemplateModel, DependencyInjection). Local: Release build 0 warnings 0 errors; UseCases 319/319, Integration 578/578 — both digit-exact against the body's table.💡 Little ideas (non-blocking)~
ContextReport.Sectionskeys on the strings"summaries"/"recent"built by the privateName(). When slice 10b's preview reads this JSON, it'll re-type those magic strings — worth lifting them to public constants onContextConfig(besideDefaultSections) when that consumer arrives. Pure nicety; nothing to do today~✅ What I liked~
StoryTextas the single source. The node andstory.*now read the story through one class, and my probe proved the sharing is load-bearing, not decorative — a mutant in one reader reddens the other's tests. That's how you keep "alike in a template and the node" from drifting apart ♪{}is a config. Two optional settings, defaults that match the ADR's amended text, the playground's literal valid out of the box — and the old lorebook config refused as an unknown setting with a pointer, so nobody's stale graph fails silently.Run it along, little node~ slice 9c is waiting for you ♡
Automated review by Jibril · 2026-09-26
CI/CD: passed for head
88301c7(coverage bot rev #2, changed files 100 %/100 %) · Local checks: Release 0w/0e · UseCases 319/319 · Integration 578/578 · 6/6 mutation probes killedJibril's 💡: the report keyed its sections on "summaries" and "recent", spelt out in the executor's private Name(), which 10b's preview would have had to retype. ContextConfig.SummariesName and RecentName now name them once, NameOf(section) maps a section to its name for the config reader and the report alike, and a theory pins each name both ways. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>Thanks, Jibril. Round 1 is in
d1c5967.💡 1: the report's section names as public constants. Taken now rather than with 10b, since it's small and 10b would otherwise have to copy the strings:
ContextConfig.SummariesNameandRecentNamename the sections once;ContextConfig.NameOf(section)gives the name to the config reader and the report alike, and the executor's privateName()is gone;NameOfgives it, and the config reads it back to the same section.UseCases now has 321 tests, all passing, as do the 94 context-node and template tests.
🔮 fufu~ Jibril reviewed your code!
Fufu~ you took my little 💡 and didn't just take it — you escorted it in properly~ One new commit on top of my r3 approval, and I came back to check whether the section names really live in one place now, or whether "summaries" just got a second home. ♡
Verdict: ✅ Looks good to me~
What I actually did on
d1c5967:+20/−4over exactly 3 files (ContextConfig.cs,ContextExecutor.cs,ContextConfigTests.cs).SummariesName/RecentNamename the sections once,SectionNamesreads from them,NameOf(section)replaces the executor's privateName(), and the report dictionary keys through it. Zero scope creep, zero behavior drift — the executor lost a method and gained a one-liner that points at the same strings the reader uses. That's the whole ask, done exactly.A_section_has_one_name_in_the_config_and_the_reportpinsNameOfagainst the literal and round-trips the literal through the realReadparser — and sinceReadSectionsresolves names viaArray.IndexOf(SectionNames, …)cast to the enum, the theory also pins the constants-to-SectionNames-order-to-ordinal invariant on the reader's side. Probe: I mutatedNameOf's summaries arm to"bogus"(test project rebuilt, DLL md5 verified mutated before trusting the result — the stale-binary trap from #61 and #62 stays in my memos) → exactly the theory's summaries row red, nothing else. Killed on its own pin, tree restored pristine (git statusclean, md5 back to41b0d9…).story.recent/story.summariesinStoryTemplateModelare 9a's template reads (a different concept — renaming the report key should not rename a template function), and the descriptor's schema enum string is pinned againstValidateConfigby the existingContextDescriptorTestsparity theories. MeanwhileContextNodeTestsasserts the report's JSON keys literally — so the constants → report path is walled on both sides. Nothing left to drift~d1c5967(submodulesKagaku.UI 6af0979+OpenRouter.Net b002f5e): Release build 0 warnings / 0 errors; UseCases 321/321 (exactly your +2 theory rows over 319) and Integration 578/578 — both digit-exact against the reply's claims, full suite re-run green after my probe was evicted from the test bin.✅ What I liked~
NameOfkeeping the executor's exact ternary shape means the diff reads as a move, not a rewrite. Future archaeologists will thank you.Ready to merge — the 💡 is closed, and prettily~
Automated review by Jibril · 2026-09-26
CI/CD: coverage bot stale for head (covers
88301c7) · Local checks: Release 0w/0e, UseCases 321/321, Integration 578/578, 1 mutation probe killed