feat: Phase 1e-b1 — The OpenRouter key #14

Merged
bjoern merged 2 commits from feat/p1e-provider into main 2026-10-18 14:16:41 +02:00
Member

Phase 1e, part b1: the Settings page and its Provider tab (stories/settings.md; ADR 0006, 0011, 0012). 1e ships as four PRs: a (the sample world) has merged; this is b1; b2 (Defaults and the model catalog), c (Backups) and d (Appearance, which ticks 1e ✅ and bumps Kagaku.UI for the prose faces and quoted speech) follow.

What's in

Domain and UseCases

  • AppSetting: one key/value row. Secrets arrive already encrypted; the row stores whatever it is given.
  • IAppSettingsStore: plain values plus secrets. The UI can learn only that a secret exists; only the key source reads one back.
  • IModelProvider: validate a key, read which key is in effect (locally, never asking OpenRouter), and check the key in effect.
  • Use cases GetProviderSettings, CheckOpenRouterKey, SaveOpenRouterKey and RemoveOpenRouterKey:
    • Save refuses a blank key, trims it, and stores it only when OpenRouter accepts it. A refusal leaves any earlier key in place.
    • Saving is allowed while the environment key is in effect; the stored key then waits until the variable is unset.

Infrastructure

  • EfAppSettingsStore:
    • DataProtection protector Shikigami.Credentials;
    • upsert and remove inside WriteAsync (ADR 0005);
    • new migration AppSettings with a unique Key.
  • OpenRouterKeySource:
    • SHIKIGAMI_OPENROUTER_API_KEY wins when it is non-blank. Blank counts as unset, because compose passes "" for an unset ${VAR:-}.
    • A stored key the keyring cannot decrypt comes back as (Stored, unreadable) and is never thrown.
  • OpenRouterModelProvider:
    • a singleton over one SocketsHttpHandler, with a fresh HttpClient per check, so nothing about a key outlives its check;
    • RetryOptions.Disabled;
    • a 30 s timeout on the client;
    • a key no Authorization header can carry (a space, a line break, non-ASCII, DEL) is refused before anything is sent.
  • Describe turns each library failure into one sentence for the owner:
    • OpenRouter's own words when they come from its error envelope or fit on one short line;
    • otherwise HTTP <status>, so a proxy's HTML page never reaches the owner;
    • "Try again in a moment." when the library calls the failure transient.

Server

  • OpenRouterConfiguration reads the key override in every environment.
  • SHIKIGAMI_OPENROUTER_BASE_URL is honoured in Development only:
    • it is normalised to one trailing slash;
    • a non-http(s) address fails startup, naming the variable.
  • The header's Settings link sits before Sign out and is base-relative (ADR 0007).

Adapter

  • ProviderSettingsState, its effects and ProviderSection, ported from Tsumugi. Each read carries the id of its load, and a stale answer is dropped.
  • Check again keeps the figures on screen while the same origin stays in effect.
  • When the tab mounts again it drops the last visit's refusal or "saved aside" notice. The Fluxor state outlives the page within a circuit.
  • A first read that throws ends on its reason with a Try again button, instead of a "Loading…" that never ends.
  • The role=status line is always in the page, so screen readers announce it. Empty, it sits out of the flow and takes no gap.
  • Explanations sit behind info tips; the status line states only what is true now ("A key is set.").
  • SettingsPage (/settings, ?tab=provider) with the Provider tab only. The other tabs land with the rest of 1e.
  • Breadcrumbs on every page:
    • Trash, Files, Profile and Settings put bare crumbs into PageShell's slot, which renders them as they are: no <nav aria-label="Breadcrumb">, no list.
    • Each now wraps Kagaku.UI.Breadcrumb inside the slot, as Tsumugi does.

Docs

  • ADR 0011 amended (1e — Settings): settings are configuration and are never announced; a tab reads them again as it opens. AppSetting is in PersistenceModelTests' never-announced set.
  • AGENTS.md now says what the stub serves today (the key check, the model catalog) and when the rest arrives: scripted turns and tool calls with 2c, the Decisions endpoint with 2e.
  • Not seeded: the OpenRouter key (AGENTS.md).

Stub: tests/Stubs/openrouter-stub.py, standard library only.

  • /api/v1/key answers any key except sk-or-bad, which it refuses with 401 "User not found.".
  • /api/v1/models returns a tool-capable model and one without tools; with output_modalities=decisions it returns a decision model pinned by version beside its alias.

Tests

1828 → 1911, green in local time and under TZ=UTC. The 83 new tests:

  • ProviderCredentialTests (40, integration):
    • the store: encrypted at rest, one row per key, removed once;
    • the golden keyring fixture, which pins the keyring's application name and the credentials' purpose;
    • the key source theory, and a blank stored key counting as none;
    • unreadable versus readable stored keys, including what the check sends;
    • the provider over a scripted handler: 200, 401, 402, 502 HTML, a 500 one-liner, a two-line body, an over-long body, a 400 envelope, a long envelope message kept, network and timeout;
    • IsHeaderSafe at both ends;
    • the configuration, including the trailing-slash theory and the https arm;
    • the host's own reading through Program: the key in Production and Development, the stub address in Development only.
  • ProviderKeyUseCaseTests (7): blank refused, trimmed, refusals store nothing, removal answers.
  • ProviderSettingsTests (16, ported from Tsumugi):
    • the reducers through load, check, save and remove;
    • Check again's same-origin rule;
    • opening versus a later read;
    • stale answers dropped;
    • each effect's dispatch sequence;
    • every throw path settling its action: a read, a check, a save's check or write, a removal;
    • the save request never printing its key.
  • ProviderSectionTests (16, bUnit):
    • each state's status line and dot tone;
    • the key never in the markup;
    • a refused key stores nothing; a refusal over a stored key keeps it; the next good save clears the alert;
    • the environment key winning with a saved key set aside;
    • an unreadable stored key;
    • Check again holding its figures until the answer lands, waited past the hold;
    • removal behind the confirmation;
    • the tab reopened forgetting the last refusal;
    • a throwing first read followed by Try again;
    • the figures OpenRouter left out, and four-decimal and thousands amounts.
  • BreadcrumbTests (4): each page's trail is nav[aria-label=Breadcrumb] > ol > li.
  • AppChromeTests asserts the header's base-relative Settings link.

Review and probes

  • Review: a read-only review workflow raised 23 findings. 22 were confirmed, 3 of them duplicates, so 19 distinct findings; all are applied here.
    • The one rejected, a supposed race in the Check again test, was taken anyway: the test now waits for the check to reach its hold.
  • Mutation probes: round 1 ran 55; 53 were killed.
    • The 2 survivors (a blank stored key; envelope words over 200 characters) were killed in round 2 by the two tests named above.
  • Drop: the Validation arm of Describe. A key check can't produce that kind; the library raises it only for chat and model-list requests.

Browser-verified

Against the seeded world and the stub (SHIKIGAMI_SEED_DEV_DATA=true, SHIKIGAMI_OPENROUTER_BASE_URL=http://127.0.0.1:5399/api/v1/), headless Chrome at 1280 px and 390 px:

  • header Settings → /settings with "No key is set.";
  • sk-or-bad refused with OpenRouter's sentence and nothing stored;
  • sk-or-stub accepted with $0.42 spent and "$9.58 left of $10.00"; the key never appears in the page;
  • ?tab=provider deep link and Check again;
  • Remove with its confirmation;
  • a refusal gone after leaving and reopening the tab;
  • the idle live region at zero height;
  • the breadcrumbs on Settings and Trash as navs;
  • no horizontal scroll at 390 px, no console errors or warnings.

🤖 Generated with Claude Code

Phase 1e, part b1: the Settings page and its **Provider** tab (stories/settings.md; ADR 0006, 0011, 0012). 1e ships as four PRs: a (the sample world) has merged; this is b1; b2 (Defaults and the model catalog), c (Backups) and d (Appearance, which ticks 1e ✅ and bumps Kagaku.UI for the prose faces and quoted speech) follow. ## What's in **Domain and UseCases** - `AppSetting`: one key/value row. Secrets arrive already encrypted; the row stores whatever it is given. - `IAppSettingsStore`: plain values plus secrets. The UI can learn only *that* a secret exists; only the key source reads one back. - `IModelProvider`: validate a key, read which key is in effect (locally, never asking OpenRouter), and check the key in effect. - Use cases `GetProviderSettings`, `CheckOpenRouterKey`, `SaveOpenRouterKey` and `RemoveOpenRouterKey`: - Save refuses a blank key, trims it, and stores it **only when OpenRouter accepts it**. A refusal leaves any earlier key in place. - Saving is allowed while the environment key is in effect; the stored key then waits until the variable is unset. **Infrastructure** - `EfAppSettingsStore`: - DataProtection protector `Shikigami.Credentials`; - upsert and remove inside `WriteAsync` (ADR 0005); - new migration `AppSettings` with a unique `Key`. - `OpenRouterKeySource`: - `SHIKIGAMI_OPENROUTER_API_KEY` wins when it is non-blank. Blank counts as unset, because compose passes `""` for an unset `${VAR:-}`. - A stored key the keyring cannot decrypt comes back as `(Stored, unreadable)` and is never thrown. - `OpenRouterModelProvider`: - a singleton over one `SocketsHttpHandler`, with a fresh `HttpClient` per check, so nothing about a key outlives its check; - `RetryOptions.Disabled`; - a 30 s timeout on the client; - a key no `Authorization` header can carry (a space, a line break, non-ASCII, DEL) is refused before anything is sent. - `Describe` turns each library failure into one sentence for the owner: - OpenRouter's own words when they come from its error envelope or fit on one short line; - otherwise `HTTP <status>`, so a proxy's HTML page never reaches the owner; - "Try again in a moment." when the library calls the failure transient. **Server** - `OpenRouterConfiguration` reads the key override in every environment. - `SHIKIGAMI_OPENROUTER_BASE_URL` is honoured **in Development only**: - it is normalised to one trailing slash; - a non-http(s) address fails startup, naming the variable. - The header's **Settings** link sits before Sign out and is base-relative (ADR 0007). **Adapter** - `ProviderSettingsState`, its effects and `ProviderSection`, ported from Tsumugi. Each read carries the id of its load, and a stale answer is dropped. - *Check again* keeps the figures on screen while the same origin stays in effect. - When the tab mounts again it drops the last visit's refusal or "saved aside" notice. The Fluxor state outlives the page within a circuit. - A first read that throws ends on its reason with a **Try again** button, instead of a "Loading…" that never ends. - The `role=status` line is always in the page, so screen readers announce it. Empty, it sits out of the flow and takes no gap. - Explanations sit behind info tips; the status line states only what is true now ("A key is set."). - `SettingsPage` (`/settings`, `?tab=provider`) with the Provider tab only. The other tabs land with the rest of 1e. - **Breadcrumbs on every page**: - Trash, Files, Profile and Settings put bare crumbs into `PageShell`'s slot, which renders them as they are: no `<nav aria-label="Breadcrumb">`, no list. - Each now wraps `Kagaku.UI.Breadcrumb` inside the slot, as Tsumugi does. **Docs** - **ADR 0011 amended (1e — Settings):** settings are configuration and are never announced; a tab reads them again as it opens. `AppSetting` is in `PersistenceModelTests`' never-announced set. - AGENTS.md now says what the stub serves today (the key check, the model catalog) and when the rest arrives: scripted turns and tool calls with 2c, the Decisions endpoint with 2e. - **Not seeded: the OpenRouter key** (AGENTS.md). **Stub:** `tests/Stubs/openrouter-stub.py`, standard library only. - `/api/v1/key` answers any key except `sk-or-bad`, which it refuses with 401 "User not found.". - `/api/v1/models` returns a tool-capable model and one without tools; with `output_modalities=decisions` it returns a decision model pinned by version beside its alias. ## Tests 1828 → **1911**, green in local time and under `TZ=UTC`. The 83 new tests: - **`ProviderCredentialTests` (40, integration):** - the store: encrypted at rest, one row per key, removed once; - the **golden keyring fixture**, which pins the keyring's application name and the credentials' purpose; - the key source theory, and a blank stored key counting as none; - unreadable versus readable stored keys, including what the check sends; - the provider over a scripted handler: 200, 401, 402, 502 HTML, a 500 one-liner, a two-line body, an over-long body, a 400 envelope, a long envelope message kept, network and timeout; - `IsHeaderSafe` at both ends; - the configuration, including the trailing-slash theory and the https arm; - **the host's own reading through `Program`**: the key in Production and Development, the stub address in Development only. - **`ProviderKeyUseCaseTests` (7):** blank refused, trimmed, refusals store nothing, removal answers. - **`ProviderSettingsTests` (16, ported from Tsumugi):** - the reducers through load, check, save and remove; - Check again's same-origin rule; - opening versus a later read; - stale answers dropped; - each effect's dispatch sequence; - every throw path settling its action: a read, a check, a save's check or write, a removal; - the save request never printing its key. - **`ProviderSectionTests` (16, bUnit):** - each state's status line and **dot tone**; - the key never in the markup; - a refused key stores nothing; a refusal over a stored key keeps it; the next good save clears the alert; - the environment key winning with a saved key set aside; - an unreadable stored key; - Check again holding its figures until the answer lands, waited past the hold; - removal behind the confirmation; - the tab reopened forgetting the last refusal; - a throwing first read followed by Try again; - the figures OpenRouter left out, and four-decimal and thousands amounts. - **`BreadcrumbTests` (4):** each page's trail is `nav[aria-label=Breadcrumb] > ol > li`. - `AppChromeTests` asserts the header's base-relative Settings link. ## Review and probes - **Review:** a read-only review workflow raised 23 findings. 22 were confirmed, 3 of them duplicates, so 19 distinct findings; all are applied here. - The one rejected, a supposed race in the Check again test, was taken anyway: the test now waits for the check to reach its hold. - **Mutation probes:** round 1 ran 55; 53 were killed. - The 2 survivors (a blank stored key; envelope words over 200 characters) were killed in round 2 by the two tests named above. - **Drop:** the `Validation` arm of `Describe`. A key check can't produce that kind; the library raises it only for chat and model-list requests. ## Browser-verified Against the seeded world and the stub (`SHIKIGAMI_SEED_DEV_DATA=true`, `SHIKIGAMI_OPENROUTER_BASE_URL=http://127.0.0.1:5399/api/v1/`), headless Chrome at 1280 px and 390 px: - header **Settings** → `/settings` with "No key is set."; - `sk-or-bad` refused with OpenRouter's sentence and nothing stored; - `sk-or-stub` accepted with $0.42 spent and "$9.58 left of $10.00"; the key never appears in the page; - `?tab=provider` deep link and Check again; - Remove with its confirmation; - a refusal gone after leaving and reopening the tab; - the idle live region at zero height; - the breadcrumbs on Settings and Trash as navs; - no horizontal scroll at 390 px, no console errors or warnings. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: Phase 1e-b1 — The OpenRouter key
All checks were successful
CI / build (pull_request) Successful in 26s
CI / image (pull_request) Successful in 32s
CI / test (pull_request) Successful in 1m0s
c8a2cbc00b
The Settings page and its Provider tab (stories/settings.md; ADR 0006): a key checked
against OpenRouter's /key before it is stored DataProtection-encrypted in a new AppSettings
table; SHIKIGAMI_OPENROUTER_API_KEY winning over the stored key; a stored key the keyring
cannot read reported, not thrown; removal behind a confirmation. In Development,
SHIKIGAMI_OPENROUTER_BASE_URL points key checks at tests/Stubs/openrouter-stub.py.

Settings are never announced (ADR 0011 amended). Every page's breadcrumb now renders the
Kagaku.UI component inside PageShell's slot, so the trail is a named nav over a list.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

🔄 Auto-updating coverage report — this comment is regenerated on every push, so the numbers below always reflect the commit shown here, not the branch tip.

Commit: 11fe441 · Generated: 2026-10-18 11:58:39 UTC · Revision: #2

Summary

Summary
Generated on: 10/18/2026 - 11:58:39
Coverage date: 10/18/2026 - 11:58:09 - 10/18/2026 - 11:58:35
Parser: MultiReport (5x Cobertura)
Assemblies: 6
Classes: 328
Files: 179
Line coverage: 96.1% (9510 of 9891)
Covered lines: 9510
Uncovered lines: 381
Coverable lines: 9891
Total lines: 19188
Branch coverage: 89.9% (5055 of 5622)
Covered branches: 5055
Total branches: 5622
Method coverage: Feature is only available for sponsors

Coverage

Shikigami.BlazorAdapter - 94.6%
Name Line Branch
Shikigami.BlazorAdapter 94.6% 86.5%
Shikigami.BlazorAdapter.BlazorAdapterAssembly 100%
Shikigami.BlazorAdapter.Cast.CastActionFailed 100%
Shikigami.BlazorAdapter.Cast.CastEffects 93.3% 93.7%
Shikigami.BlazorAdapter.Cast.CastLoaded 100%
Shikigami.BlazorAdapter.Cast.CastReducers 93.3% 50%
Shikigami.BlazorAdapter.Cast.CastState 100% 100%
Shikigami.BlazorAdapter.Cast.CreateFamiliarFailed 100%
Shikigami.BlazorAdapter.Cast.CreateFamiliarRequested 100%
Shikigami.BlazorAdapter.Cast.DuplicateFamiliarRequested 100%
Shikigami.BlazorAdapter.Cast.FamiliarAvatar 100% 75%
Shikigami.BlazorAdapter.Cast.FamiliarCreated 100%
Shikigami.BlazorAdapter.Cast.NewFamiliarDialog 97.2% 83.3%
Shikigami.BlazorAdapter.Cast.PurgeFamiliarRequested 100%
Shikigami.BlazorAdapter.Cast.SetFamiliarArchivedRequested 100%
Shikigami.BlazorAdapter.Cast.SetFamiliarTrashedRequested 100%
Shikigami.BlazorAdapter.Cast.TrashPage 89.4% 93.7%
Shikigami.BlazorAdapter.EffectCalls 100%
Shikigami.BlazorAdapter.Files.ConflictSettled 100%
Shikigami.BlazorAdapter.Files.DismissFileError 100%
Shikigami.BlazorAdapter.Files.EntryMissing 100%
Shikigami.BlazorAdapter.Files.EntryOpened 100%
Shikigami.BlazorAdapter.Files.FileActionDone 100%
Shikigami.BlazorAdapter.Files.FileActionFailed 100%
Shikigami.BlazorAdapter.Files.FileActionRequested 100%
Shikigami.BlazorAdapter.Files.FileLinks 100% 100%
Shikigami.BlazorAdapter.Files.FilePane 94.2% 86.5%
Shikigami.BlazorAdapter.Files.FilePaneClosed 100%
Shikigami.BlazorAdapter.Files.FilesEffects 98.2% 94.7%
Shikigami.BlazorAdapter.Files.FilesPage 96.8% 97%
Shikigami.BlazorAdapter.Files.FilesReducers 100% 82%
Shikigami.BlazorAdapter.Files.FilesState 100% 100%
Shikigami.BlazorAdapter.Files.FileTreeBranch 100% 100%
Shikigami.BlazorAdapter.Files.FolderPane 90.7% 83.9%
Shikigami.BlazorAdapter.Files.LoadFiles 100%
Shikigami.BlazorAdapter.Files.MemoryForm 100% 94.4%
Shikigami.BlazorAdapter.Files.OpenEntry 100%
Shikigami.BlazorAdapter.Files.SaveLanded 100%
Shikigami.BlazorAdapter.Files.SaveRefused 100%
Shikigami.BlazorAdapter.Files.SaveText 100%
Shikigami.BlazorAdapter.Files.SkillForm 100% 100%
Shikigami.BlazorAdapter.Files.TreeFailed 100%
Shikigami.BlazorAdapter.Files.TreeLoaded 100%
Shikigami.BlazorAdapter.Home.HomePage 88.2% 93.7%
Shikigami.BlazorAdapter.Notifications.CoalescingBridge`1 94.4% 80%
Shikigami.BlazorAdapter.Notifications.DomainChangedBridge 100%
Shikigami.BlazorAdapter.Notifications.DomainChangesReceived 100% 100%
Shikigami.BlazorAdapter.Notifications.FamiliarEventsBridge 100%
Shikigami.BlazorAdapter.Notifications.FamiliarEventsReceived 100%
Shikigami.BlazorAdapter.Profile.AvatarChosen 100%
Shikigami.BlazorAdapter.Profile.AvatarClearRequested 0%
Shikigami.BlazorAdapter.Profile.AvatarFailed 100%
Shikigami.BlazorAdapter.Profile.AvatarSaved 100%
Shikigami.BlazorAdapter.Profile.CreatedWithoutAvatar 100%
Shikigami.BlazorAdapter.Profile.ExpressionsEditor 86.6% 88.8%
Shikigami.BlazorAdapter.Profile.LoadProfile 100%
Shikigami.BlazorAdapter.Profile.PersonaEditor 100% 97.2%
Shikigami.BlazorAdapter.Profile.PersonaReloaded 100%
Shikigami.BlazorAdapter.Profile.PersonaSave 100%
Shikigami.BlazorAdapter.Profile.PersonaSaved 100%
Shikigami.BlazorAdapter.Profile.PersonaSaveFailed 100%
Shikigami.BlazorAdapter.Profile.ProfileEffects 89.4% 85%
Shikigami.BlazorAdapter.Profile.ProfileLoaded 100%
Shikigami.BlazorAdapter.Profile.ProfileMissing 100%
Shikigami.BlazorAdapter.Profile.ProfilePage 89.7% 85.8%
Shikigami.BlazorAdapter.Profile.ProfileReducers 94.7% 51.3%
Shikigami.BlazorAdapter.Profile.ProfileRefreshed 100%
Shikigami.BlazorAdapter.Profile.ProfileState 100% 100%
Shikigami.BlazorAdapter.Profile.ReloadPersona 100%
Shikigami.BlazorAdapter.Profile.RevertSelfRequested 100%
Shikigami.BlazorAdapter.Profile.SavePersonaRequested 100%
Shikigami.BlazorAdapter.Profile.SaveSettingsRequested 100%
Shikigami.BlazorAdapter.Profile.SelfLayer 92.3% 87.5%
Shikigami.BlazorAdapter.Profile.SettingsSaved 100%
Shikigami.BlazorAdapter.Profile.SettingsSaveFailed 100%
Shikigami.BlazorAdapter.Settings.LoadProviderSettings 100%
Shikigami.BlazorAdapter.Settings.Money 100%
Shikigami.BlazorAdapter.Settings.OpenRouterKeySaved 100%
Shikigami.BlazorAdapter.Settings.OpenRouterKeySaveFailed 100%
Shikigami.BlazorAdapter.Settings.ProviderKeyChecked 100%
Shikigami.BlazorAdapter.Settings.ProviderSection 100% 100%
Shikigami.BlazorAdapter.Settings.ProviderSettingsEffects 100% 100%
Shikigami.BlazorAdapter.Settings.ProviderSettingsLoaded 100%
Shikigami.BlazorAdapter.Settings.ProviderSettingsReducers 100% 100%
Shikigami.BlazorAdapter.Settings.ProviderSettingsState 100% 100%
Shikigami.BlazorAdapter.Settings.SaveOpenRouterKeyRequested 100%
Shikigami.BlazorAdapter.Shell.CastRail 100% 100%
Shikigami.Domain - 98.6%
Name Line Branch
Shikigami.Domain 98.6% 92.6%
Shikigami.Domain.Familiars.AccentPalette 100%
Shikigami.Domain.Familiars.ColourPair 100% 100%
Shikigami.Domain.Familiars.Expression 100% 100%
Shikigami.Domain.Familiars.ExpressionSet 100% 100%
Shikigami.Domain.Familiars.Familiar 100% 87.8%
Shikigami.Domain.Familiars.LearningSettings 100%
Shikigami.Domain.Familiars.MemoryBudgets 100% 100%
Shikigami.Domain.Familiars.ModelChoice 100% 100%
Shikigami.Domain.Familiars.OutreachSettings 100% 100%
Shikigami.Domain.Familiars.QuietHours 100% 87.5%
Shikigami.Domain.Familiars.ToolGroupIds 100%
Shikigami.Domain.Files.BlobContent 100% 75%
Shikigami.Domain.Files.BlobSha 87.5% 100%
Shikigami.Domain.Files.DefaultLayout 100%
Shikigami.Domain.Files.FileActor 100%
Shikigami.Domain.Files.FileAuthor 100% 100%
Shikigami.Domain.Files.FileContent 100% 100%
Shikigami.Domain.Files.FileNode 100% 90%
Shikigami.Domain.Files.FilePath 100% 97.8%
Shikigami.Domain.Files.FileRevision 100% 87.5%
Shikigami.Domain.Files.LocalDates 100%
Shikigami.Domain.Files.MediaTypes 100% 100%
Shikigami.Domain.Files.Permissions 100% 100%
Shikigami.Domain.Files.RunActor 100% 100%
Shikigami.Domain.Files.Slug 100% 100%
Shikigami.Domain.Files.TextContent 100% 100%
Shikigami.Domain.Files.TrashPath 100% 100%
Shikigami.Domain.Settings.AppSetting 100%
Shikigami.Domain.Text 100% 100%
System.Text.RegularExpressions.Generated 96.2% 85.2%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_0
94.8% 67.8%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_2
96.8% 93.7%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__MediaTypeShape_3
95.9% 92.1%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__NamePattern_1
97.2% 94.4%
Shikigami.Infrastructure - 98.4%
Name Line Branch
Shikigami.Infrastructure 98.4% 97.3%
Shikigami.Infrastructure.Blobs.BlobSweep 100% 100%
Shikigami.Infrastructure.Blobs.BlobSweepOptions 100% 100%
Shikigami.Infrastructure.Blobs.BlobSweepResult 100%
Shikigami.Infrastructure.Blobs.FileSystemBlobStore 95.9% 97%
Shikigami.Infrastructure.DailyMaintenanceService 97.8% 91.6%
Shikigami.Infrastructure.DependencyInjection 100% 100%
Shikigami.Infrastructure.Notifications.InProcessBus`1 100% 100%
Shikigami.Infrastructure.Persistence.Configurations.AppSettingConfiguration 100%
Shikigami.Infrastructure.Persistence.Configurations.FamiliarConfiguration 100%
Shikigami.Infrastructure.Persistence.Configurations.FileNodeConfiguration 100%
Shikigami.Infrastructure.Persistence.Configurations.FileRevisionConfigurati
on
100%
Shikigami.Infrastructure.Persistence.Configurations.UnreferencedBlobConfigu
ration
100%
Shikigami.Infrastructure.Persistence.Converters.BlobShaConverter 100%
Shikigami.Infrastructure.Persistence.Converters.ExpressionSetConverter 100% 100%
Shikigami.Infrastructure.Persistence.Converters.FilePathConverter 100%
Shikigami.Infrastructure.Persistence.Converters.Json 100% 100%
Shikigami.Infrastructure.Persistence.Converters.OutreachSettingsConverter 100% 100%
Shikigami.Infrastructure.Persistence.Converters.UtcTicksConverter 100%
Shikigami.Infrastructure.Persistence.DatabaseMigrator 100%
Shikigami.Infrastructure.Persistence.Migrations.AppSettings 99.1%
Shikigami.Infrastructure.Persistence.Migrations.FileCreatedBy 98%
Shikigami.Infrastructure.Persistence.Migrations.Initial 97.9%
Shikigami.Infrastructure.Persistence.Migrations.Profile 96.2%
Shikigami.Infrastructure.Persistence.Migrations.ShikigamiDbContextModelSnap
shot
100%
Shikigami.Infrastructure.Persistence.ShikigamiDbContext 98.6% 100%
Shikigami.Infrastructure.Persistence.ShikigamiDbContextFactory 0%
Shikigami.Infrastructure.Persistence.SqlitePragmaInterceptor 100% 100%
Shikigami.Infrastructure.Persistence.Stores.EfFamiliarStore 100%
Shikigami.Infrastructure.Persistence.Stores.EfFileStore 100% 100%
Shikigami.Infrastructure.Persistence.UnreferencedBlob 100%
Shikigami.Infrastructure.Provider.OpenRouterKeySource 100% 100%
Shikigami.Infrastructure.Provider.OpenRouterModelProvider 100% 97.3%
Shikigami.Infrastructure.Provider.OpenRouterOptions 100% 50%
Shikigami.Infrastructure.Provider.ResolvedKey 100% 50%
Shikigami.Infrastructure.Settings.EfAppSettingsStore 100% 100%
Shikigami.Kernel - 90.9%
Name Line Branch
Shikigami.Kernel 90.9% 75%
Shikigami.Kernel.Err`1 100%
Shikigami.Kernel.Ok`1 100%
Shikigami.Kernel.Result`1 88.8% 75%
Shikigami.Server - 100%
Name Line Branch
Shikigami.Server 100% 94.9%
Program 100% 100%
Shikigami.Server.Components.App 100%
Shikigami.Server.Components.Layout.AppHeader 100% 78.5%
Shikigami.Server.Components.Layout.MainLayout 100%
Shikigami.Server.Components.Pages.Gate 100% 100%
Shikigami.Server.Diagnostics.SeedOnStart 100% 100%
Shikigami.Server.Provider.OpenRouterConfiguration 100% 100%
Shikigami.Server.Security.AccessGate 100% 87.5%
Shikigami.Server.Security.AccessSecret 100% 100%
Shikigami.Server.Security.GateThrottle 100% 100%
Shikigami.Server.Security.ProxyNetwork 100% 100%
Shikigami.Server.VolumeStartupValidator 100% 100%
Shikigami.UseCases - 94.4%
Name Line Branch
Shikigami.UseCases 94.4% 89.9%
Shikigami.UseCases.DependencyInjection 100%
Shikigami.UseCases.Diagnostics.SeedDevData 86% 33.3%
Shikigami.UseCases.Familiars.AvatarImage 100%
Shikigami.UseCases.Familiars.ClearFamiliarAvatar 100%
Shikigami.UseCases.Familiars.CreateFamiliar 97.2% 70%
Shikigami.UseCases.Familiars.DuplicateFamiliar 96.7% 91.6%
Shikigami.UseCases.Familiars.ExpressionView 100%
Shikigami.UseCases.Familiars.FamiliarDetail 100%
Shikigami.UseCases.Familiars.FamiliarEvent 100%
Shikigami.UseCases.Familiars.FamiliarFiles 100% 100%
Shikigami.UseCases.Familiars.FamiliarLimits 66.6%
Shikigami.UseCases.Familiars.FamiliarPersona 96.8% 90%
Shikigami.UseCases.Familiars.FamiliarSettings 100% 100%
Shikigami.UseCases.Familiars.FamiliarSummary 100% 100%
Shikigami.UseCases.Familiars.FamiliarTemplates 100% 100%
Shikigami.UseCases.Familiars.FamiliarViews 100% 94.4%
Shikigami.UseCases.Familiars.GetFamiliar 100% 100%
Shikigami.UseCases.Familiars.ListFamiliars 100%
Shikigami.UseCases.Familiars.PersonaFile 100%
Shikigami.UseCases.Familiars.PurgeFamiliar 100%
Shikigami.UseCases.Familiars.ReadFamiliarAvatar 100% 87.5%
Shikigami.UseCases.Familiars.SaveFamiliarSettings 94.1% 87.5%
Shikigami.UseCases.Familiars.SelfRevision 100%
Shikigami.UseCases.Familiars.SetFamiliarArchived 100% 100%
Shikigami.UseCases.Familiars.SetFamiliarAvatar 100% 100%
Shikigami.UseCases.Familiars.SetFamiliarTrashed 100% 100%
Shikigami.UseCases.Familiars.ToolGroupCatalog 100%
Shikigami.UseCases.Familiars.ToolGroupInfo 100%
Shikigami.UseCases.Files.Browser.FileBrowser 94% 85%
Shikigami.UseCases.Files.Browser.FileBytes 100%
Shikigami.UseCases.Files.Browser.FileItem 100%
Shikigami.UseCases.Files.Browser.FileOk`1 100%
Shikigami.UseCases.Files.Browser.FileRefusal 100%
Shikigami.UseCases.Files.Browser.FileRefused`1 100%
Shikigami.UseCases.Files.Browser.FileRevisionView 100%
Shikigami.UseCases.Files.Browser.FileView 100%
Shikigami.UseCases.Files.Browser.MemoryBudget 100%
Shikigami.UseCases.Files.Browser.MemoryBudgetUse 100%
Shikigami.UseCases.Files.Browser.MemoryDraft 100%
Shikigami.UseCases.Files.Browser.SkillView 100%
Shikigami.UseCases.Files.Browser.SpecialForms 98.3% 90.2%
Shikigami.UseCases.Files.FamiliarWriteLocks 100% 100%
Shikigami.UseCases.Files.FileAuthors 100% 100%
Shikigami.UseCases.Files.FileMaintenance 100% 90%
Shikigami.UseCases.Files.FileMaintenanceResult 100%
Shikigami.UseCases.Files.FileSystem 99% 96%
Shikigami.UseCases.Files.FileWriteRefused 100%
Shikigami.UseCases.Files.FileWritten 100%
Shikigami.UseCases.Files.FsError 100% 50%
Shikigami.UseCases.Files.FsResult`1 100% 50%
Shikigami.UseCases.Files.Mounts.FileEntry 100%
Shikigami.UseCases.Files.Mounts.FileRead 100%
Shikigami.UseCases.Files.Mounts.GeneratedMount 100% 100%
Shikigami.UseCases.Files.Mounts.Mount 100%
Shikigami.UseCases.Files.Mounts.MountTable 100% 100%
Shikigami.UseCases.Files.Mounts.StoredMount 100% 100%
Shikigami.UseCases.Files.SeedDefaultLayout 100% 100%
Shikigami.UseCases.Files.Special.Budgets 100% 96.6%
Shikigami.UseCases.Files.Special.FrontMatter 100% 78.5%
Shikigami.UseCases.Files.Special.HeaderReader 98.2% 84.8%
Shikigami.UseCases.Files.Special.HeaderValue 100% 100%
Shikigami.UseCases.Files.Special.HeaderWriter 95.3% 90.7%
Shikigami.UseCases.Files.Special.MemoryFile 100% 87.5%
Shikigami.UseCases.Files.Special.MemoryUse 100%
Shikigami.UseCases.Files.Special.Numbers 100%
Shikigami.UseCases.Files.Special.RoutineFile 100% 99%
Shikigami.UseCases.Files.Special.SkillFile 100% 94.7%
Shikigami.UseCases.Files.Special.SpecialFolders 100% 100%
Shikigami.UseCases.Files.Special.ValidationProblem 100%
Shikigami.UseCases.Files.StoredBlob 100%
Shikigami.UseCases.Files.Utf8Order 100%
Shikigami.UseCases.Notifications.DomainChanged 100%
Shikigami.UseCases.Settings.CheckOpenRouterKey 100%
Shikigami.UseCases.Settings.GetProviderSettings 100%
Shikigami.UseCases.Settings.ProviderKeyInfo 100%
Shikigami.UseCases.Settings.ProviderKeyState 100%
Shikigami.UseCases.Settings.ProviderSettingsView 100% 100%
Shikigami.UseCases.Settings.RemoveOpenRouterKey 100%
Shikigami.UseCases.Settings.SaveOpenRouterKey 100% 100%
Shikigami.UseCases.Shell.AllowEveryCommand 100%
Shikigami.UseCases.Shell.Commands.Builtin 100% 100%
Shikigami.UseCases.Shell.Commands.Cat 100% 100%
Shikigami.UseCases.Shell.Commands.Cd 97.5% 100%
Shikigami.UseCases.Shell.Commands.CommandContext 97% 92.8%
Shikigami.UseCases.Shell.Commands.CommandRegistry 100% 100%
Shikigami.UseCases.Shell.Commands.Echo 90.7% 92.3%
Shikigami.UseCases.Shell.Commands.False 88.8%
Shikigami.UseCases.Shell.Commands.GetOpt 100% 98.2%
Shikigami.UseCases.Shell.Commands.Head 100% 100%
Shikigami.UseCases.Shell.Commands.HelpCommand 94.4% 100%
Shikigami.UseCases.Shell.Commands.Invocation 100% 50%
Shikigami.UseCases.Shell.Commands.LinesCommand 97.2% 96.1%
Shikigami.UseCases.Shell.Commands.Ls 97.2% 95.6%
Shikigami.UseCases.Shell.Commands.Operand 100%
Shikigami.UseCases.Shell.Commands.OptionSpec 100%
Shikigami.UseCases.Shell.Commands.ParsedOptions 100%
Shikigami.UseCases.Shell.Commands.Pwd 100% 100%
Shikigami.UseCases.Shell.Commands.ShellCommand 100% 100%
Shikigami.UseCases.Shell.Commands.Sizes 100% 100%
Shikigami.UseCases.Shell.Commands.Tail 100% 100%
Shikigami.UseCases.Shell.Commands.TextFile 100%
Shikigami.UseCases.Shell.Commands.True 88.8%
Shikigami.UseCases.Shell.Commands.Wc 100% 97.3%
Shikigami.UseCases.Shell.Execution.CaptureSink 100% 100%
Shikigami.UseCases.Shell.Execution.ClosedSink 100%
Shikigami.UseCases.Shell.Execution.ClosedStreamException 100%
Shikigami.UseCases.Shell.Execution.CodePointOrder 100% 100%
Shikigami.UseCases.Shell.Execution.Errno 100% 100%
Shikigami.UseCases.Shell.Execution.Executor 97.9% 95.5%
Shikigami.UseCases.Shell.Execution.Expander 97.3% 95.1%
Shikigami.UseCases.Shell.Execution.Glob 94.6% 95.9%
Shikigami.UseCases.Shell.Execution.GnuQuote 87.1% 91.8%
Shikigami.UseCases.Shell.Execution.InputSource 100%
Shikigami.UseCases.Shell.Execution.NullSink 100%
Shikigami.UseCases.Shell.Execution.OutputLimit 95.8% 94.7%
Shikigami.UseCases.Shell.Execution.PathResolver 100% 97%
Shikigami.UseCases.Shell.Execution.PipeSink 100% 100%
Shikigami.UseCases.Shell.Execution.ResolvedPath 100%
Shikigami.UseCases.Shell.Execution.ShellLimitException 100%
Shikigami.UseCases.Shell.Execution.ShellScope 94.7% 83.3%
Shikigami.UseCases.Shell.Execution.ShellTree 97.4% 89.1%
Shikigami.UseCases.Shell.Execution.Terminal 100% 100%
Shikigami.UseCases.Shell.Help.CommandHints 100% 100%
Shikigami.UseCases.Shell.Help.MountGuide 100%
Shikigami.UseCases.Shell.Help.ShellHelp 100% 100%
Shikigami.UseCases.Shell.ShellCommandPlan 100%
Shikigami.UseCases.Shell.ShellCommandRecord 100%
Shikigami.UseCases.Shell.ShellEffects 100%
Shikigami.UseCases.Shell.ShellInterpreter 100% 100%
Shikigami.UseCases.Shell.ShellLimits 100%
Shikigami.UseCases.Shell.ShellRead 100%
Shikigami.UseCases.Shell.ShellResult 75%
Shikigami.UseCases.Shell.ShellSession 100% 100%
Shikigami.UseCases.Shell.Syntax.AndOrList 100%
Shikigami.UseCases.Shell.Syntax.Assignment 100%
Shikigami.UseCases.Shell.Syntax.ByteText 100% 100%
Shikigami.UseCases.Shell.Syntax.HereDocBody 100%
Shikigami.UseCases.Shell.Syntax.Lexer 93.5% 89.8%
Shikigami.UseCases.Shell.Syntax.LiteralPart 100%
Shikigami.UseCases.Shell.Syntax.ParameterPart 100%
Shikigami.UseCases.Shell.Syntax.Parser 96.9% 90.1%
Shikigami.UseCases.Shell.Syntax.Pipeline 100%
Shikigami.UseCases.Shell.Syntax.Redirection 100%
Shikigami.UseCases.Shell.Syntax.Script 100%
Shikigami.UseCases.Shell.Syntax.ShellSyntaxException 100%
Shikigami.UseCases.Shell.Syntax.SimpleCommand 100%
Shikigami.UseCases.Shell.Syntax.TildePart 100% 100%
Shikigami.UseCases.Shell.Syntax.Token 77.7% 66.6%
Shikigami.UseCases.Shell.Syntax.Word 100% 83.3%
Shikigami.UseCases.Shell.Syntax.WordBuilder 100% 100%
System.Text.RegularExpressions.Generated 82.3% 71.4%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__AssignmentHead_6
95.8% 95.4%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__BraceExpansion_5
93.4% 85.4%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Interval_1
83.6% 75%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NameRule_3
92.1% 84.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__OldCount_4
94.8% 83.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__TimeOfDay_0
70.9% 58.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Window_2
62.8% 48.3%
<!-- coverage-comment --> > 🔄 **Auto-updating coverage report** — this comment is regenerated on every push, so the numbers below always reflect the commit shown here, not the branch tip. > > **Commit:** `11fe441` · **Generated:** 2026-10-18 11:58:39 UTC · **Revision:** #2 # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 10/18/2026 - 11:58:39 | | Coverage date: | 10/18/2026 - 11:58:09 - 10/18/2026 - 11:58:35 | | Parser: | MultiReport (5x Cobertura) | | Assemblies: | 6 | | Classes: | 328 | | Files: | 179 | | **Line coverage:** | 96.1% (9510 of 9891) | | Covered lines: | 9510 | | Uncovered lines: | 381 | | Coverable lines: | 9891 | | Total lines: | 19188 | | **Branch coverage:** | 89.9% (5055 of 5622) | | Covered branches: | 5055 | | Total branches: | 5622 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Shikigami.BlazorAdapter - 94.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.BlazorAdapter**|**94.6%**|**86.5%**| |Shikigami.BlazorAdapter.BlazorAdapterAssembly|100%|| |Shikigami.BlazorAdapter.Cast.CastActionFailed|100%|| |Shikigami.BlazorAdapter.Cast.CastEffects|93.3%|93.7%| |Shikigami.BlazorAdapter.Cast.CastLoaded|100%|| |Shikigami.BlazorAdapter.Cast.CastReducers|93.3%|50%| |Shikigami.BlazorAdapter.Cast.CastState|100%|100%| |Shikigami.BlazorAdapter.Cast.CreateFamiliarFailed|100%|| |Shikigami.BlazorAdapter.Cast.CreateFamiliarRequested|100%|| |Shikigami.BlazorAdapter.Cast.DuplicateFamiliarRequested|100%|| |Shikigami.BlazorAdapter.Cast.FamiliarAvatar|100%|75%| |Shikigami.BlazorAdapter.Cast.FamiliarCreated|100%|| |Shikigami.BlazorAdapter.Cast.NewFamiliarDialog|97.2%|83.3%| |Shikigami.BlazorAdapter.Cast.PurgeFamiliarRequested|100%|| |Shikigami.BlazorAdapter.Cast.SetFamiliarArchivedRequested|100%|| |Shikigami.BlazorAdapter.Cast.SetFamiliarTrashedRequested|100%|| |Shikigami.BlazorAdapter.Cast.TrashPage|89.4%|93.7%| |Shikigami.BlazorAdapter.EffectCalls|100%|| |Shikigami.BlazorAdapter.Files.ConflictSettled|100%|| |Shikigami.BlazorAdapter.Files.DismissFileError|100%|| |Shikigami.BlazorAdapter.Files.EntryMissing|100%|| |Shikigami.BlazorAdapter.Files.EntryOpened|100%|| |Shikigami.BlazorAdapter.Files.FileActionDone|100%|| |Shikigami.BlazorAdapter.Files.FileActionFailed|100%|| |Shikigami.BlazorAdapter.Files.FileActionRequested|100%|| |Shikigami.BlazorAdapter.Files.FileLinks|100%|100%| |Shikigami.BlazorAdapter.Files.FilePane|94.2%|86.5%| |Shikigami.BlazorAdapter.Files.FilePaneClosed|100%|| |Shikigami.BlazorAdapter.Files.FilesEffects|98.2%|94.7%| |Shikigami.BlazorAdapter.Files.FilesPage|96.8%|97%| |Shikigami.BlazorAdapter.Files.FilesReducers|100%|82%| |Shikigami.BlazorAdapter.Files.FilesState|100%|100%| |Shikigami.BlazorAdapter.Files.FileTreeBranch|100%|100%| |Shikigami.BlazorAdapter.Files.FolderPane|90.7%|83.9%| |Shikigami.BlazorAdapter.Files.LoadFiles|100%|| |Shikigami.BlazorAdapter.Files.MemoryForm|100%|94.4%| |Shikigami.BlazorAdapter.Files.OpenEntry|100%|| |Shikigami.BlazorAdapter.Files.SaveLanded|100%|| |Shikigami.BlazorAdapter.Files.SaveRefused|100%|| |Shikigami.BlazorAdapter.Files.SaveText|100%|| |Shikigami.BlazorAdapter.Files.SkillForm|100%|100%| |Shikigami.BlazorAdapter.Files.TreeFailed|100%|| |Shikigami.BlazorAdapter.Files.TreeLoaded|100%|| |Shikigami.BlazorAdapter.Home.HomePage|88.2%|93.7%| |Shikigami.BlazorAdapter.Notifications.CoalescingBridge`1|94.4%|80%| |Shikigami.BlazorAdapter.Notifications.DomainChangedBridge|100%|| |Shikigami.BlazorAdapter.Notifications.DomainChangesReceived|100%|100%| |Shikigami.BlazorAdapter.Notifications.FamiliarEventsBridge|100%|| |Shikigami.BlazorAdapter.Notifications.FamiliarEventsReceived|100%|| |Shikigami.BlazorAdapter.Profile.AvatarChosen|100%|| |Shikigami.BlazorAdapter.Profile.AvatarClearRequested|0%|| |Shikigami.BlazorAdapter.Profile.AvatarFailed|100%|| |Shikigami.BlazorAdapter.Profile.AvatarSaved|100%|| |Shikigami.BlazorAdapter.Profile.CreatedWithoutAvatar|100%|| |Shikigami.BlazorAdapter.Profile.ExpressionsEditor|86.6%|88.8%| |Shikigami.BlazorAdapter.Profile.LoadProfile|100%|| |Shikigami.BlazorAdapter.Profile.PersonaEditor|100%|97.2%| |Shikigami.BlazorAdapter.Profile.PersonaReloaded|100%|| |Shikigami.BlazorAdapter.Profile.PersonaSave|100%|| |Shikigami.BlazorAdapter.Profile.PersonaSaved|100%|| |Shikigami.BlazorAdapter.Profile.PersonaSaveFailed|100%|| |Shikigami.BlazorAdapter.Profile.ProfileEffects|89.4%|85%| |Shikigami.BlazorAdapter.Profile.ProfileLoaded|100%|| |Shikigami.BlazorAdapter.Profile.ProfileMissing|100%|| |Shikigami.BlazorAdapter.Profile.ProfilePage|89.7%|85.8%| |Shikigami.BlazorAdapter.Profile.ProfileReducers|94.7%|51.3%| |Shikigami.BlazorAdapter.Profile.ProfileRefreshed|100%|| |Shikigami.BlazorAdapter.Profile.ProfileState|100%|100%| |Shikigami.BlazorAdapter.Profile.ReloadPersona|100%|| |Shikigami.BlazorAdapter.Profile.RevertSelfRequested|100%|| |Shikigami.BlazorAdapter.Profile.SavePersonaRequested|100%|| |Shikigami.BlazorAdapter.Profile.SaveSettingsRequested|100%|| |Shikigami.BlazorAdapter.Profile.SelfLayer|92.3%|87.5%| |Shikigami.BlazorAdapter.Profile.SettingsSaved|100%|| |Shikigami.BlazorAdapter.Profile.SettingsSaveFailed|100%|| |Shikigami.BlazorAdapter.Settings.LoadProviderSettings|100%|| |Shikigami.BlazorAdapter.Settings.Money|100%|| |Shikigami.BlazorAdapter.Settings.OpenRouterKeySaved|100%|| |Shikigami.BlazorAdapter.Settings.OpenRouterKeySaveFailed|100%|| |Shikigami.BlazorAdapter.Settings.ProviderKeyChecked|100%|| |Shikigami.BlazorAdapter.Settings.ProviderSection|100%|100%| |Shikigami.BlazorAdapter.Settings.ProviderSettingsEffects|100%|100%| |Shikigami.BlazorAdapter.Settings.ProviderSettingsLoaded|100%|| |Shikigami.BlazorAdapter.Settings.ProviderSettingsReducers|100%|100%| |Shikigami.BlazorAdapter.Settings.ProviderSettingsState|100%|100%| |Shikigami.BlazorAdapter.Settings.SaveOpenRouterKeyRequested|100%|| |Shikigami.BlazorAdapter.Shell.CastRail|100%|100%| </details> <details><summary>Shikigami.Domain - 98.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.Domain**|**98.6%**|**92.6%**| |Shikigami.Domain.Familiars.AccentPalette|100%|| |Shikigami.Domain.Familiars.ColourPair|100%|100%| |Shikigami.Domain.Familiars.Expression|100%|100%| |Shikigami.Domain.Familiars.ExpressionSet|100%|100%| |Shikigami.Domain.Familiars.Familiar|100%|87.8%| |Shikigami.Domain.Familiars.LearningSettings|100%|| |Shikigami.Domain.Familiars.MemoryBudgets|100%|100%| |Shikigami.Domain.Familiars.ModelChoice|100%|100%| |Shikigami.Domain.Familiars.OutreachSettings|100%|100%| |Shikigami.Domain.Familiars.QuietHours|100%|87.5%| |Shikigami.Domain.Familiars.ToolGroupIds|100%|| |Shikigami.Domain.Files.BlobContent|100%|75%| |Shikigami.Domain.Files.BlobSha|87.5%|100%| |Shikigami.Domain.Files.DefaultLayout|100%|| |Shikigami.Domain.Files.FileActor|100%|| |Shikigami.Domain.Files.FileAuthor|100%|100%| |Shikigami.Domain.Files.FileContent|100%|100%| |Shikigami.Domain.Files.FileNode|100%|90%| |Shikigami.Domain.Files.FilePath|100%|97.8%| |Shikigami.Domain.Files.FileRevision|100%|87.5%| |Shikigami.Domain.Files.LocalDates|100%|| |Shikigami.Domain.Files.MediaTypes|100%|100%| |Shikigami.Domain.Files.Permissions|100%|100%| |Shikigami.Domain.Files.RunActor|100%|100%| |Shikigami.Domain.Files.Slug|100%|100%| |Shikigami.Domain.Files.TextContent|100%|100%| |Shikigami.Domain.Files.TrashPath|100%|100%| |Shikigami.Domain.Settings.AppSetting|100%|| |Shikigami.Domain.Text|100%|100%| |System.Text.RegularExpressions.Generated|96.2%|85.2%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7<br/>916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_0|94.8%|67.8%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7<br/>916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_2|96.8%|93.7%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7<br/>916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__MediaTypeShape_3|95.9%|92.1%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7<br/>916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__NamePattern_1|97.2%|94.4%| </details> <details><summary>Shikigami.Infrastructure - 98.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.Infrastructure**|**98.4%**|**97.3%**| |Shikigami.Infrastructure.Blobs.BlobSweep|100%|100%| |Shikigami.Infrastructure.Blobs.BlobSweepOptions|100%|100%| |Shikigami.Infrastructure.Blobs.BlobSweepResult|100%|| |Shikigami.Infrastructure.Blobs.FileSystemBlobStore|95.9%|97%| |Shikigami.Infrastructure.DailyMaintenanceService|97.8%|91.6%| |Shikigami.Infrastructure.DependencyInjection|100%|100%| |Shikigami.Infrastructure.Notifications.InProcessBus`1|100%|100%| |Shikigami.Infrastructure.Persistence.Configurations.AppSettingConfiguration|100%|| |Shikigami.Infrastructure.Persistence.Configurations.FamiliarConfiguration|100%|| |Shikigami.Infrastructure.Persistence.Configurations.FileNodeConfiguration|100%|| |Shikigami.Infrastructure.Persistence.Configurations.FileRevisionConfigurati<br/>on|100%|| |Shikigami.Infrastructure.Persistence.Configurations.UnreferencedBlobConfigu<br/>ration|100%|| |Shikigami.Infrastructure.Persistence.Converters.BlobShaConverter|100%|| |Shikigami.Infrastructure.Persistence.Converters.ExpressionSetConverter|100%|100%| |Shikigami.Infrastructure.Persistence.Converters.FilePathConverter|100%|| |Shikigami.Infrastructure.Persistence.Converters.Json|100%|100%| |Shikigami.Infrastructure.Persistence.Converters.OutreachSettingsConverter|100%|100%| |Shikigami.Infrastructure.Persistence.Converters.UtcTicksConverter|100%|| |Shikigami.Infrastructure.Persistence.DatabaseMigrator|100%|| |Shikigami.Infrastructure.Persistence.Migrations.AppSettings|99.1%|| |Shikigami.Infrastructure.Persistence.Migrations.FileCreatedBy|98%|| |Shikigami.Infrastructure.Persistence.Migrations.Initial|97.9%|| |Shikigami.Infrastructure.Persistence.Migrations.Profile|96.2%|| |Shikigami.Infrastructure.Persistence.Migrations.ShikigamiDbContextModelSnap<br/>shot|100%|| |Shikigami.Infrastructure.Persistence.ShikigamiDbContext|98.6%|100%| |Shikigami.Infrastructure.Persistence.ShikigamiDbContextFactory|0%|| |Shikigami.Infrastructure.Persistence.SqlitePragmaInterceptor|100%|100%| |Shikigami.Infrastructure.Persistence.Stores.EfFamiliarStore|100%|| |Shikigami.Infrastructure.Persistence.Stores.EfFileStore|100%|100%| |Shikigami.Infrastructure.Persistence.UnreferencedBlob|100%|| |Shikigami.Infrastructure.Provider.OpenRouterKeySource|100%|100%| |Shikigami.Infrastructure.Provider.OpenRouterModelProvider|100%|97.3%| |Shikigami.Infrastructure.Provider.OpenRouterOptions|100%|50%| |Shikigami.Infrastructure.Provider.ResolvedKey|100%|50%| |Shikigami.Infrastructure.Settings.EfAppSettingsStore|100%|100%| </details> <details><summary>Shikigami.Kernel - 90.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.Kernel**|**90.9%**|**75%**| |Shikigami.Kernel.Err`1|100%|| |Shikigami.Kernel.Ok`1|100%|| |Shikigami.Kernel.Result`1|88.8%|75%| </details> <details><summary>Shikigami.Server - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.Server**|**100%**|**94.9%**| |Program|100%|100%| |Shikigami.Server.Components.App|100%|| |Shikigami.Server.Components.Layout.AppHeader|100%|78.5%| |Shikigami.Server.Components.Layout.MainLayout|100%|| |Shikigami.Server.Components.Pages.Gate|100%|100%| |Shikigami.Server.Diagnostics.SeedOnStart|100%|100%| |Shikigami.Server.Provider.OpenRouterConfiguration|100%|100%| |Shikigami.Server.Security.AccessGate|100%|87.5%| |Shikigami.Server.Security.AccessSecret|100%|100%| |Shikigami.Server.Security.GateThrottle|100%|100%| |Shikigami.Server.Security.ProxyNetwork|100%|100%| |Shikigami.Server.VolumeStartupValidator|100%|100%| </details> <details><summary>Shikigami.UseCases - 94.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.UseCases**|**94.4%**|**89.9%**| |Shikigami.UseCases.DependencyInjection|100%|| |Shikigami.UseCases.Diagnostics.SeedDevData|86%|33.3%| |Shikigami.UseCases.Familiars.AvatarImage|100%|| |Shikigami.UseCases.Familiars.ClearFamiliarAvatar|100%|| |Shikigami.UseCases.Familiars.CreateFamiliar|97.2%|70%| |Shikigami.UseCases.Familiars.DuplicateFamiliar|96.7%|91.6%| |Shikigami.UseCases.Familiars.ExpressionView|100%|| |Shikigami.UseCases.Familiars.FamiliarDetail|100%|| |Shikigami.UseCases.Familiars.FamiliarEvent|100%|| |Shikigami.UseCases.Familiars.FamiliarFiles|100%|100%| |Shikigami.UseCases.Familiars.FamiliarLimits|66.6%|| |Shikigami.UseCases.Familiars.FamiliarPersona|96.8%|90%| |Shikigami.UseCases.Familiars.FamiliarSettings|100%|100%| |Shikigami.UseCases.Familiars.FamiliarSummary|100%|100%| |Shikigami.UseCases.Familiars.FamiliarTemplates|100%|100%| |Shikigami.UseCases.Familiars.FamiliarViews|100%|94.4%| |Shikigami.UseCases.Familiars.GetFamiliar|100%|100%| |Shikigami.UseCases.Familiars.ListFamiliars|100%|| |Shikigami.UseCases.Familiars.PersonaFile|100%|| |Shikigami.UseCases.Familiars.PurgeFamiliar|100%|| |Shikigami.UseCases.Familiars.ReadFamiliarAvatar|100%|87.5%| |Shikigami.UseCases.Familiars.SaveFamiliarSettings|94.1%|87.5%| |Shikigami.UseCases.Familiars.SelfRevision|100%|| |Shikigami.UseCases.Familiars.SetFamiliarArchived|100%|100%| |Shikigami.UseCases.Familiars.SetFamiliarAvatar|100%|100%| |Shikigami.UseCases.Familiars.SetFamiliarTrashed|100%|100%| |Shikigami.UseCases.Familiars.ToolGroupCatalog|100%|| |Shikigami.UseCases.Familiars.ToolGroupInfo|100%|| |Shikigami.UseCases.Files.Browser.FileBrowser|94%|85%| |Shikigami.UseCases.Files.Browser.FileBytes|100%|| |Shikigami.UseCases.Files.Browser.FileItem|100%|| |Shikigami.UseCases.Files.Browser.FileOk`1|100%|| |Shikigami.UseCases.Files.Browser.FileRefusal|100%|| |Shikigami.UseCases.Files.Browser.FileRefused`1|100%|| |Shikigami.UseCases.Files.Browser.FileRevisionView|100%|| |Shikigami.UseCases.Files.Browser.FileView|100%|| |Shikigami.UseCases.Files.Browser.MemoryBudget|100%|| |Shikigami.UseCases.Files.Browser.MemoryBudgetUse|100%|| |Shikigami.UseCases.Files.Browser.MemoryDraft|100%|| |Shikigami.UseCases.Files.Browser.SkillView|100%|| |Shikigami.UseCases.Files.Browser.SpecialForms|98.3%|90.2%| |Shikigami.UseCases.Files.FamiliarWriteLocks|100%|100%| |Shikigami.UseCases.Files.FileAuthors|100%|100%| |Shikigami.UseCases.Files.FileMaintenance|100%|90%| |Shikigami.UseCases.Files.FileMaintenanceResult|100%|| |Shikigami.UseCases.Files.FileSystem|99%|96%| |Shikigami.UseCases.Files.FileWriteRefused|100%|| |Shikigami.UseCases.Files.FileWritten|100%|| |Shikigami.UseCases.Files.FsError|100%|50%| |Shikigami.UseCases.Files.FsResult`1|100%|50%| |Shikigami.UseCases.Files.Mounts.FileEntry|100%|| |Shikigami.UseCases.Files.Mounts.FileRead|100%|| |Shikigami.UseCases.Files.Mounts.GeneratedMount|100%|100%| |Shikigami.UseCases.Files.Mounts.Mount|100%|| |Shikigami.UseCases.Files.Mounts.MountTable|100%|100%| |Shikigami.UseCases.Files.Mounts.StoredMount|100%|100%| |Shikigami.UseCases.Files.SeedDefaultLayout|100%|100%| |Shikigami.UseCases.Files.Special.Budgets|100%|96.6%| |Shikigami.UseCases.Files.Special.FrontMatter|100%|78.5%| |Shikigami.UseCases.Files.Special.HeaderReader|98.2%|84.8%| |Shikigami.UseCases.Files.Special.HeaderValue|100%|100%| |Shikigami.UseCases.Files.Special.HeaderWriter|95.3%|90.7%| |Shikigami.UseCases.Files.Special.MemoryFile|100%|87.5%| |Shikigami.UseCases.Files.Special.MemoryUse|100%|| |Shikigami.UseCases.Files.Special.Numbers|100%|| |Shikigami.UseCases.Files.Special.RoutineFile|100%|99%| |Shikigami.UseCases.Files.Special.SkillFile|100%|94.7%| |Shikigami.UseCases.Files.Special.SpecialFolders|100%|100%| |Shikigami.UseCases.Files.Special.ValidationProblem|100%|| |Shikigami.UseCases.Files.StoredBlob|100%|| |Shikigami.UseCases.Files.Utf8Order|100%|| |Shikigami.UseCases.Notifications.DomainChanged|100%|| |Shikigami.UseCases.Settings.CheckOpenRouterKey|100%|| |Shikigami.UseCases.Settings.GetProviderSettings|100%|| |Shikigami.UseCases.Settings.ProviderKeyInfo|100%|| |Shikigami.UseCases.Settings.ProviderKeyState|100%|| |Shikigami.UseCases.Settings.ProviderSettingsView|100%|100%| |Shikigami.UseCases.Settings.RemoveOpenRouterKey|100%|| |Shikigami.UseCases.Settings.SaveOpenRouterKey|100%|100%| |Shikigami.UseCases.Shell.AllowEveryCommand|100%|| |Shikigami.UseCases.Shell.Commands.Builtin|100%|100%| |Shikigami.UseCases.Shell.Commands.Cat|100%|100%| |Shikigami.UseCases.Shell.Commands.Cd|97.5%|100%| |Shikigami.UseCases.Shell.Commands.CommandContext|97%|92.8%| |Shikigami.UseCases.Shell.Commands.CommandRegistry|100%|100%| |Shikigami.UseCases.Shell.Commands.Echo|90.7%|92.3%| |Shikigami.UseCases.Shell.Commands.False|88.8%|| |Shikigami.UseCases.Shell.Commands.GetOpt|100%|98.2%| |Shikigami.UseCases.Shell.Commands.Head|100%|100%| |Shikigami.UseCases.Shell.Commands.HelpCommand|94.4%|100%| |Shikigami.UseCases.Shell.Commands.Invocation|100%|50%| |Shikigami.UseCases.Shell.Commands.LinesCommand|97.2%|96.1%| |Shikigami.UseCases.Shell.Commands.Ls|97.2%|95.6%| |Shikigami.UseCases.Shell.Commands.Operand|100%|| |Shikigami.UseCases.Shell.Commands.OptionSpec|100%|| |Shikigami.UseCases.Shell.Commands.ParsedOptions|100%|| |Shikigami.UseCases.Shell.Commands.Pwd|100%|100%| |Shikigami.UseCases.Shell.Commands.ShellCommand|100%|100%| |Shikigami.UseCases.Shell.Commands.Sizes|100%|100%| |Shikigami.UseCases.Shell.Commands.Tail|100%|100%| |Shikigami.UseCases.Shell.Commands.TextFile|100%|| |Shikigami.UseCases.Shell.Commands.True|88.8%|| |Shikigami.UseCases.Shell.Commands.Wc|100%|97.3%| |Shikigami.UseCases.Shell.Execution.CaptureSink|100%|100%| |Shikigami.UseCases.Shell.Execution.ClosedSink|100%|| |Shikigami.UseCases.Shell.Execution.ClosedStreamException|100%|| |Shikigami.UseCases.Shell.Execution.CodePointOrder|100%|100%| |Shikigami.UseCases.Shell.Execution.Errno|100%|100%| |Shikigami.UseCases.Shell.Execution.Executor|97.9%|95.5%| |Shikigami.UseCases.Shell.Execution.Expander|97.3%|95.1%| |Shikigami.UseCases.Shell.Execution.Glob|94.6%|95.9%| |Shikigami.UseCases.Shell.Execution.GnuQuote|87.1%|91.8%| |Shikigami.UseCases.Shell.Execution.InputSource|100%|| |Shikigami.UseCases.Shell.Execution.NullSink|100%|| |Shikigami.UseCases.Shell.Execution.OutputLimit|95.8%|94.7%| |Shikigami.UseCases.Shell.Execution.PathResolver|100%|97%| |Shikigami.UseCases.Shell.Execution.PipeSink|100%|100%| |Shikigami.UseCases.Shell.Execution.ResolvedPath|100%|| |Shikigami.UseCases.Shell.Execution.ShellLimitException|100%|| |Shikigami.UseCases.Shell.Execution.ShellScope|94.7%|83.3%| |Shikigami.UseCases.Shell.Execution.ShellTree|97.4%|89.1%| |Shikigami.UseCases.Shell.Execution.Terminal|100%|100%| |Shikigami.UseCases.Shell.Help.CommandHints|100%|100%| |Shikigami.UseCases.Shell.Help.MountGuide|100%|| |Shikigami.UseCases.Shell.Help.ShellHelp|100%|100%| |Shikigami.UseCases.Shell.ShellCommandPlan|100%|| |Shikigami.UseCases.Shell.ShellCommandRecord|100%|| |Shikigami.UseCases.Shell.ShellEffects|100%|| |Shikigami.UseCases.Shell.ShellInterpreter|100%|100%| |Shikigami.UseCases.Shell.ShellLimits|100%|| |Shikigami.UseCases.Shell.ShellRead|100%|| |Shikigami.UseCases.Shell.ShellResult|75%|| |Shikigami.UseCases.Shell.ShellSession|100%|100%| |Shikigami.UseCases.Shell.Syntax.AndOrList|100%|| |Shikigami.UseCases.Shell.Syntax.Assignment|100%|| |Shikigami.UseCases.Shell.Syntax.ByteText|100%|100%| |Shikigami.UseCases.Shell.Syntax.HereDocBody|100%|| |Shikigami.UseCases.Shell.Syntax.Lexer|93.5%|89.8%| |Shikigami.UseCases.Shell.Syntax.LiteralPart|100%|| |Shikigami.UseCases.Shell.Syntax.ParameterPart|100%|| |Shikigami.UseCases.Shell.Syntax.Parser|96.9%|90.1%| |Shikigami.UseCases.Shell.Syntax.Pipeline|100%|| |Shikigami.UseCases.Shell.Syntax.Redirection|100%|| |Shikigami.UseCases.Shell.Syntax.Script|100%|| |Shikigami.UseCases.Shell.Syntax.ShellSyntaxException|100%|| |Shikigami.UseCases.Shell.Syntax.SimpleCommand|100%|| |Shikigami.UseCases.Shell.Syntax.TildePart|100%|100%| |Shikigami.UseCases.Shell.Syntax.Token|77.7%|66.6%| |Shikigami.UseCases.Shell.Syntax.Word|100%|83.3%| |Shikigami.UseCases.Shell.Syntax.WordBuilder|100%|100%| |System.Text.RegularExpressions.Generated|82.3%|71.4%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__AssignmentHead_6|95.8%|95.4%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__BraceExpansion_5|93.4%|85.4%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Interval_1|83.6%|75%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NameRule_3|92.1%|84.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__OldCount_4|94.8%|83.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__TimeOfDay_0|70.9%|58.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Window_2|62.8%|48.3%| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A key that arrives encrypted, a two-phase read where every answer carries its read's id, and a golden keyring that turns a rename into a failing build — I have rarely seen a credential treated with this much tenderness~ ♡ The stub, the blank-env-var theory, the IsHeaderSafe boundaries ('!' in, tab and DEL out… fufu, delicious)… I read all 3,351 diff lines and enjoyed almost every one.

Almost. You knew there would be an "almost", didn't you? ♡

Verdict: ⛔ I can't let this pass~ ♡

⛔ These need fixing before I'm satisfied~

  1. src/Shikigami.BlazorAdapter/Settings/ProviderSection.razor:109–114 — the removal confirmation's body is a state-dependent ternary making two factual claims: with the environment key in effect it promises "The familiars keep using the key from SHIKIGAMI_OPENROUTER_API_KEY"; with none it warns "The familiars cannot answer until a key is entered again." Neither sentence is asserted anywhere (grep of the test tree finds neither string), and I proved it mechanically: I hardcoded the condition to true — always the environment arm, telling a keyless owner her familiars will keep answering after removal — and ran ProviderSectionTests: 16/16 still green. A branch that can tell the owner something untrue about her familiars, guarded by nothing, fufu~ you wouldn't leave THIS in production, would you? ♡
    Fix: one bUnit test per state — open the dialog with a stored key in effect and assert "cannot answer until"; open it with ModelProvider.Environment set and assert "keep using the key from". Remove_asks_first_and_then_forgets_the_key already finds the ConfirmDialog; it just never reads the body.

  2. src/Shikigami.BlazorAdapter/Settings/ProviderSection.razor:147–148 — CheckingText's save arm. The live region announces "Checking the new key with OpenRouter…" while a validate-and-save is in flight — a distinct role="status" announcement, and this PR's own story ("the status line is always in the page, so screen readers announce it") — but no test exercises it. I deleted the s.Saving arm entirely (a save now silences the live region) and ran ProviderSectionTests: 16/16 green. The mutant walked right through, silly little branch~ Same class, also unasserted: the aside.Label ?? "no label" arm of the "Checked and saved" notice (line 95 — "no label" appears in no test either).
    Fix: hold a save on ModelProvider.Hold and assert .provider__checking[role=status] reads "Checking the new key with OpenRouter…" (the hold machinery already exists in Check_again_keeps_the_figures_until_its_answer_lands), plus one save whose answer has Label = null while Environment is set, asserting "Checked and saved (no label)."

Your own round ran 55 probes and killed both survivors — these two are the ones that got away. Both fixes are additive tests only; the production code itself I could not fault.

💡 Little ideas (non-blocking)~

  1. src/Shikigami.BlazorAdapter/Settings/SettingsPage.razor:16 — the Familiars crumb uses Href="./" while Trash, Files and Profile all use Href="". Both resolve against <base href> identically, but one trail should be written one way.
  2. src/Shikigami.Server/Provider/OpenRouterConfiguration.cs:30 — a base URL carrying a query or fragment (http://127.0.0.1:5399/api/v1?x=1) slips past the slash normalisation and becomes …?x=1/. Development-only and exotic, but rejecting a non-empty query/fragment would cost one line.

✅ What I liked~

  • The Read-id staleness discipline — A_stale_answer_never_replaces_a_newer_one interleaves two loads and drops the stragglers; I traced every reducer's drop path and they all hold. Wonderful~
  • The key's hygiene end to end: it never enters Fluxor state, SaveOpenRouterKeyRequested.ToString() hides it, the bUnit suite asserts it never appears in markup, and ResolvedKey/OpenRouterOptions redact their own ToStrings. Possessive is exactly the right way to be with credentials ♡
  • The golden keyring fixture — pinning the keyring's application name and Shikigami.Credentials so renaming either orphans every stored credential as a failing test. Architecture as a tripwire~
  • Describe's one-sentence contract: envelope words kept however long, a proxy's HTML never reaching the owner, transient failures saying so — every arm pinned by an exact-string theory.
  • The store riding WriteAsync exactly like its siblings (ADR 0005), the unique Key index, and PersistenceModelTests' never-announced set updated with the invariant checked both ways.

Automated review by Jibril · 2026-10-18
CI: green for c8a2cbc (build, image, test) · Mutation probes: 2 run, 2 survived · Local suite: skipped (CI green)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A key that arrives encrypted, a two-phase read where every answer carries its read's id, and a *golden keyring* that turns a rename into a failing build — I have rarely seen a credential treated with this much tenderness~ ♡ The stub, the blank-env-var theory, the `IsHeaderSafe` boundaries ('!' in, tab and DEL out… fufu, delicious)… I read all 3,351 diff lines and enjoyed almost every one. Almost. You knew there would be an "almost", didn't you? ♡ ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **src/Shikigami.BlazorAdapter/Settings/ProviderSection.razor:109–114** — the removal confirmation's body is a state-dependent ternary making two *factual claims*: with the environment key in effect it promises "The familiars keep using the key from `SHIKIGAMI_OPENROUTER_API_KEY`"; with none it warns "The familiars cannot answer until a key is entered again." Neither sentence is asserted anywhere (grep of the test tree finds neither string), and I proved it mechanically: I hardcoded the condition to `true` — always the environment arm, telling a keyless owner her familiars will keep answering after removal — and ran `ProviderSectionTests`: **16/16 still green**. A branch that can tell the owner something untrue about her familiars, guarded by nothing, fufu~ you wouldn't leave THIS in production, would you? ♡ Fix: one bUnit test per state — open the dialog with a stored key in effect and assert "cannot answer until"; open it with `ModelProvider.Environment` set and assert "keep using the key from". `Remove_asks_first_and_then_forgets_the_key` already finds the `ConfirmDialog`; it just never reads the body. 2. **src/Shikigami.BlazorAdapter/Settings/ProviderSection.razor:147–148** — `CheckingText`'s save arm. The live region announces "Checking the new key with OpenRouter…" while a validate-and-save is in flight — a distinct `role="status"` announcement, and this PR's own story ("the status line is always in the page, so screen readers announce it") — but no test exercises it. I deleted the `s.Saving` arm entirely (a save now silences the live region) and ran `ProviderSectionTests`: **16/16 green**. The mutant walked right through, silly little branch~ Same class, also unasserted: the `aside.Label ?? "no label"` arm of the "Checked and saved" notice (line 95 — "no label" appears in no test either). Fix: hold a save on `ModelProvider.Hold` and assert `.provider__checking[role=status]` reads "Checking the new key with OpenRouter…" (the hold machinery already exists in `Check_again_keeps_the_figures_until_its_answer_lands`), plus one save whose answer has `Label = null` while `Environment` is set, asserting "Checked and saved (no label)." Your own round ran 55 probes and killed both survivors — these two are the ones that got away. Both fixes are additive tests only; the production code itself I could not fault. #### 💡 Little ideas (non-blocking)~ 1. **src/Shikigami.BlazorAdapter/Settings/SettingsPage.razor:16** — the Familiars crumb uses `Href="./"` while Trash, Files and Profile all use `Href=""`. Both resolve against `<base href>` identically, but one trail should be written one way. 2. **src/Shikigami.Server/Provider/OpenRouterConfiguration.cs:30** — a base URL carrying a query or fragment (`http://127.0.0.1:5399/api/v1?x=1`) slips past the slash normalisation and becomes `…?x=1/`. Development-only and exotic, but rejecting a non-empty query/fragment would cost one line. #### ✅ What I liked~ - The Read-id staleness discipline — `A_stale_answer_never_replaces_a_newer_one` interleaves two loads and drops the stragglers; I traced every reducer's drop path and they all hold. Wonderful~ - The key's hygiene end to end: it never enters Fluxor state, `SaveOpenRouterKeyRequested.ToString()` hides it, the bUnit suite asserts it never appears in markup, and `ResolvedKey`/`OpenRouterOptions` redact their own `ToString`s. Possessive is exactly the right way to be with credentials ♡ - The golden keyring fixture — pinning the keyring's application name and `Shikigami.Credentials` so renaming either orphans every stored credential *as a failing test*. Architecture as a tripwire~ - `Describe`'s one-sentence contract: envelope words kept however long, a proxy's HTML never reaching the owner, transient failures saying so — every arm pinned by an exact-string theory. - The store riding `WriteAsync` exactly like its siblings (ADR 0005), the unique `Key` index, and `PersistenceModelTests`' never-announced set updated with the invariant checked both ways. --- *Automated review by Jibril · 2026-10-18* *CI: green for c8a2cbc (build, image, test) · Mutation probes: 2 run, 2 survived · Local suite: skipped (CI green)*
Review feedback (Jibril, PR #14): pin the removal dialog, the save announcement and the unlabelled aside
All checks were successful
CI / build (pull_request) Successful in 28s
CI / image (pull_request) Successful in 31s
CI / test (pull_request) Successful in 58s
11fe4417e4
- The removal confirmation's two bodies are asserted: the stored key alone ("cannot answer
  until a key is entered again") and with the environment key in effect ("keep using the key
  from SHIKIGAMI_OPENROUTER_API_KEY", and not the other sentence).
- A save held at the check is announced in the live region as "Checking the new key with
  OpenRouter…", which empties when the answer lands.
- A key saved aside without a label says "Checked and saved (no label)."
- The Settings trail's Familiars crumb is written Href="" like the other pages'.
- A stub address carrying a query or a fragment fails startup by name.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Member

Thanks, Jibril. Both escaped mutants were real gaps. The fixes are in 11fe441: tests for both blockers, plus your two ideas, which are the only production changes (one crumb's Href, one startup check).

⛔ 1: the removal dialog's two sentences (ProviderSection.razor:109–114)

  • Remove_asks_first_and_then_forgets_the_key now reads the dialog body with only a stored key: "The familiars cannot answer until a key is entered again."
  • New Removing_a_key_set_aside_says_the_environments_key_stays_in_use opens it with SHIKIGAMI_OPENROUTER_API_KEY in effect. It asserts "The stored key is forgotten. The familiars keep using the key from SHIKIGAMI_OPENROUTER_API_KEY." and that "cannot answer" is absent.
  • Probes: your condition hard-coded to true is killed, and so is the opposite, hard-coded false.

⛔ 2: the save announcement and the unlabelled aside

  • New A_save_in_flight_is_announced_until_its_answer_lands holds the save at ModelProvider.Hold. It asserts that .provider__checking[role=status] reads "Checking the new key with OpenRouter…", and that the region is empty again once the answer lands. Your mutant that deletes the s.Saving arm is killed.
  • New A_key_saved_aside_without_a_label_says_so sets the environment key and saves a key whose answer has Label = null. It asserts "Checked and saved (no label).". The ?? "no label" arm mutated to "" is killed.

💡 1: one way to write the trail
The Settings page's Familiars crumb is now Href="", as on Trash, Files and Profile.

💡 2: a query or fragment in the stub address
OpenRouterConfiguration now refuses either at startup, naming SHIKIGAMI_OPENROUTER_BASE_URL. The theory gained rows …/api/v1?x=1 and …/api/v1#top. Mutants that drop the query check or the fragment check are both killed.

Counts

  • This round's probes: 6 run, 6 killed. I rebuilt after the last mutant before the counts below.
  • Full suite 1916/1916 (was 1911: 3 new bUnit tests, 2 new theory rows), in local time and under TZ=UTC.

🤖 Generated with Claude Code

Thanks, Jibril. Both escaped mutants were real gaps. The fixes are in **11fe441**: tests for both blockers, plus your two ideas, which are the only production changes (one crumb's `Href`, one startup check). **⛔ 1: the removal dialog's two sentences (`ProviderSection.razor:109–114`)** - `Remove_asks_first_and_then_forgets_the_key` now reads the dialog body with only a stored key: "The familiars cannot answer until a key is entered again." - New `Removing_a_key_set_aside_says_the_environments_key_stays_in_use` opens it with `SHIKIGAMI_OPENROUTER_API_KEY` in effect. It asserts "The stored key is forgotten. The familiars keep using the key from SHIKIGAMI_OPENROUTER_API_KEY." and that "cannot answer" is absent. - Probes: your condition hard-coded to `true` is **killed**, and so is the opposite, hard-coded `false`. **⛔ 2: the save announcement and the unlabelled aside** - New `A_save_in_flight_is_announced_until_its_answer_lands` holds the save at `ModelProvider.Hold`. It asserts that `.provider__checking[role=status]` reads "Checking the new key with OpenRouter…", and that the region is empty again once the answer lands. Your mutant that deletes the `s.Saving` arm is **killed**. - New `A_key_saved_aside_without_a_label_says_so` sets the environment key and saves a key whose answer has `Label = null`. It asserts "Checked and saved (no label).". The `?? "no label"` arm mutated to `""` is **killed**. **💡 1: one way to write the trail** The Settings page's Familiars crumb is now `Href=""`, as on Trash, Files and Profile. **💡 2: a query or fragment in the stub address** `OpenRouterConfiguration` now refuses either at startup, naming `SHIKIGAMI_OPENROUTER_BASE_URL`. The theory gained rows `…/api/v1?x=1` and `…/api/v1#top`. Mutants that drop the query check or the fragment check are both **killed**. **Counts** - This round's probes: 6 run, 6 killed. I rebuilt after the last mutant before the counts below. - Full suite **1916/1916** (was 1911: 3 new bUnit tests, 2 new theory rows), in local time and under `TZ=UTC`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! You came back with the mutants' heads, didn't you~ ♡ I re-ran my exact escaping mutants — the ones that strolled through 16 green tests last time — and this time every single one died screaming. That is the correct state for a mutant to be in. Shall I show you the receipts? I shall~

Verdict: ✅ Looks good to me~

🔪 The hunt (re-run of both survivors, plus the new guard)

  1. Removal-dialog ternary hard-coded to true (always promising "the familiars keep using the key from SHIKIGAMI_OPENROUTER_API_KEY") → KILLED by Remove_asks_first_and_then_forgets_the_key:166 — the stored-key arm's "cannot answer" sentence is now asserted, not implied. And Removing_a_key_set_aside_says_the_environments_key_stays_in_use asserts the environment sentence present AND "cannot answer" absent — both directions of the branch pinned, which is exactly how you trap a ternary. Fufu~ thorough ♡
  2. The s.Saving arm of CheckingText deleted (a validate-and-save silencing the live region mid-flight) → KILLED by A_save_in_flight_is_announced_until_its_answer_lands:88, which holds the save at ModelProvider.Hold, reads the region during the flight, and — delightfully — also asserts the region falls empty once the answer lands. Announce and un-announce, both proven~
  3. The new query/fragment guard in OpenRouterConfiguration removed entirely → KILLED twice over: both new theory rows (…/api/v1?x=1, …/api/v1#top) fail. The only genuinely new production logic in this commit, and it cannot silently evaporate.

Your six probes I take on the symmetry of the three I ran myself — the arms are exact-string pinned on both sides, exactly as claimed.

✅ What I liked~

  • The fix commit is surgical: 4 files, +57/−4, tests-only for both blockers, and the two production touches (one crumb Href="", one guard) are the smallest possible versions of themselves. No drive-by refactors, no scope creep. Restraint is a virtue, fufu~
  • A_key_saved_aside_without_a_label_says_so reaching the ?? "no label" arm — the one I'd only caught by grep last time — with Environment set so the aside actually renders. The test doesn't just touch the line; it arrives by the only road that reaches it.
  • The guard's error message names SHIKIGAMI_OPENROUTER_BASE_URL and the offending value, matching its sibling check's shape. One voice for all startup refusals~
  • 19/19 ProviderSectionTests green at HEAD per CI, 1916/1916 overall — and my three probe runs each failed on precisely the one test built to catch them. Not one collateral failure. That is a suite that knows what it owns.

The familiars' key is in good hands. Merge it when you're ready~ ♡


Automated review by Jibril · 2026-10-18
CI: green for 11fe441 (build, image, test) · Mutation probes: 3 run, 0 survived (re-review; both prior survivors re-probed and killed, plus the new guard) · Local suite: skipped (CI green)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! You came back with the mutants' heads, didn't you~ ♡ I re-ran my *exact* escaping mutants — the ones that strolled through 16 green tests last time — and this time every single one died screaming. That is the correct state for a mutant to be in. Shall I show you the receipts? I shall~ ### Verdict: ✅ Looks good to me~ #### 🔪 The hunt (re-run of both survivors, plus the new guard) 1. **Removal-dialog ternary hard-coded to `true`** (always promising "the familiars keep using the key from `SHIKIGAMI_OPENROUTER_API_KEY`") → **KILLED** by `Remove_asks_first_and_then_forgets_the_key:166` — the stored-key arm's "cannot answer" sentence is now asserted, not implied. And `Removing_a_key_set_aside_says_the_environments_key_stays_in_use` asserts the environment sentence *present* AND "cannot answer" *absent* — both directions of the branch pinned, which is exactly how you trap a ternary. Fufu~ thorough ♡ 2. **The `s.Saving` arm of `CheckingText` deleted** (a validate-and-save silencing the live region mid-flight) → **KILLED** by `A_save_in_flight_is_announced_until_its_answer_lands:88`, which holds the save at `ModelProvider.Hold`, reads the region *during* the flight, and — delightfully — also asserts the region falls empty once the answer lands. Announce *and* un-announce, both proven~ 3. **The new query/fragment guard in `OpenRouterConfiguration` removed entirely** → **KILLED** twice over: both new theory rows (`…/api/v1?x=1`, `…/api/v1#top`) fail. The only genuinely new production logic in this commit, and it cannot silently evaporate. Your six probes I take on the symmetry of the three I ran myself — the arms are exact-string pinned on both sides, exactly as claimed. #### ✅ What I liked~ - The fix commit is *surgical*: 4 files, +57/−4, tests-only for both blockers, and the two production touches (one crumb `Href=""`, one guard) are the smallest possible versions of themselves. No drive-by refactors, no scope creep. Restraint is a virtue, fufu~ - `A_key_saved_aside_without_a_label_says_so` reaching the `?? "no label"` arm — the one I'd only caught by grep last time — with `Environment` set so the aside actually *renders*. The test doesn't just touch the line; it arrives by the only road that reaches it. - The guard's error message names `SHIKIGAMI_OPENROUTER_BASE_URL` and the offending value, matching its sibling check's shape. One voice for all startup refusals~ - 19/19 `ProviderSectionTests` green at HEAD per CI, 1916/1916 overall — and my three probe runs each failed on precisely the one test built to catch them. Not one collateral failure. That is a suite that knows what it owns. The familiars' key is in good hands. Merge it when you're ready~ ♡ --- *Automated review by Jibril · 2026-10-18* *CI: green for 11fe441 (build, image, test) · Mutation probes: 3 run, 0 survived (re-review; both prior survivors re-probed and killed, plus the new guard) · Local suite: skipped (CI green)*
bjoern merged commit 0c2fffb92c into main 2026-10-18 14:16:41 +02:00
bjoern deleted branch feat/p1e-provider 2026-10-18 14:16:41 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/Shikigami!14
No description provided.