feat: C2b — owner capability forms and scoped file assets #43
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "t3/capability-owner-forms"
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?
C2b adds the owner configuration surface from the capability plan, following ADR 0034 and the UI dependency boundary in ADR 0003.
What's in
/uploads/capabilities/<id>host files and stays visible while the group is disabled. Save validates references after asynchronous capability validation while holding the familiar write lock; runtime use checks them again. Core uploads and capability asset paths share one bounded-copy helper.Tests
41 additional .NET cases; 5,478 total. Serial build is clean. Full normal-time and UTC results: both pass.
Coverage includes cross-familiar/capability/tampered references, changed/deleted assets during validation, size/type/cancellation refusal, disabled-group access, generated/custom saves, secret clearing, stale drafts, in-flight familiar switching, effective defaults and immediate reload, custom-form recovery, page query binding, upload → selection → save → reload, sample gating and dependency boundaries. Python launcher contracts: 8/8.
Independent source review is clear after fixing the findings. Independent behavioral/mutation review: 52 focused checks and eight deliberate mutations. Seven mutations failed the original tests; removing familiar identity from reference protection initially survived. The added matching-path/version familiar-copy regression passes on the implementation and detects that fault. The reviewer also added a deletion-during-validation regression. All faults were restored; the final test delta has been independently re-reviewed. The PR review follow-up adds stale-listing, filename, asset-default and custom-form registration guard regressions. A second independent audit passed 112 focused checks and caught eight further mutations, including exact-size and cumulative-copy boundaries; none survived.
Browser-verified
Used the repository browser-test launcher with fresh isolated data and the local OpenRouter stub. In capability sample mode, drove Niko/Rotom/Mika selection; generated and custom validation; secret replacement/removal without disclosure; successful file/image uploads and reload; invalid-image refusal; disabled-group uploads readable through the file browser; and Profile links. A separate ordinary seeded launch showed the empty configurable-groups state. In-flight save switching is exercised by the adapter regression test.
Light-theme desktop at 1280×1450 and phone layout at 390×844 CSS pixels were inspected. Links and actions wrap inside the phone card.
Private stores, lifecycle admission, coordinated backups and background supervision remain the following C2 slices. This change adds no schema migration or companion-library update.
Summary
Summary
Coverage
Shikigami.BlazorAdapter - 95.2%
Shikigami.Capabilities.Contracts - 98.5%
Shikigami.Capabilities.TestFixture - 98.5%
Shikigami.Capabilities.TestFixture.UI - 100%
Shikigami.Capabilities.UI.Contracts - 91.3%
Shikigami.Domain - 98.1%
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_0
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_2
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__MediaTypeShape_3
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__NamePattern_1
Shikigami.Infrastructure - 97.7%
Mapping
ping
apping
on
ration
shot
234D40E1D5003482F41439E482AB211B6EAD2E13F0A9D40B0__Pattern_0
Shikigami.Kernel - 90.9%
Shikigami.Server - 92.6%
Shikigami.UseCases - 95.9%
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__AssignmentHead_13
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__BraceExpansion_12
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__HexColour_7
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__IdPattern_0
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Interval_4
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__IntervalBody_11
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__KeyPattern_2
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NameRule_6
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NewerXY_9
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Number_8
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__OldCount_10
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__TimeOfDay_3
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__ToolPattern_1
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Window_5
🔮 fufu~ Jibril reviewed your code!
Oh? Oh my~ C2b is a big slice — generated forms, custom-form contracts, protected file assets, a dev-only sample world — and I dug through every corner of it with my loupe pressed to the glass. And here's the thing: the implementation itself is genuinely, irritatingly solid. I hunted for a logic bug and found none. The lock choreography is correct, the reference protection is scoped exactly right, the secret hygiene is exemplary, the links follow the house
<base href>convention, the sample gating is airtight.But~ ♡ I brought my mutation kit, and four of your new guard branches turned out to be standing in the dark with nobody watching them. I deleted each one and the whole suite smiled back green. A branch no test can see is a branch that will silently rot, and I simply cannot let that pass, you know? ♡
Verdict: ⛔ I can't let this pass~ ♡
⛔ These need fixing before I'm satisfied~
All four are the same class — new logic with zero test coverage — three of them proven by mutation (mutant in, suite still green):
src/Shikigami.UseCases/Capabilities/CapabilityAssets.cs:62 (
ListAsync) — thee.Version == selected.Versioncondition decides whether the listing hands back the caller's own reference or mints a fresh one. I mutated it totrue(path match alone preserves the reference) and all 13CapabilityAssetTests+ all 9CapabilityFormsTestsstayed green. That means nothing pins "a file that changed under the same path must NOT be re-offered with its stale reference" — with the check gone, the form would silently present a dead reference as the current selection and the owner would only find out at save/use with a generic error.Fix: add a regression — upload, save, then rewrite the file under the same path (version bump), then
ListAsync(familiar, capability, key, staleReference)must return a different reference for that path (adapter-level: the form shows "missing or changed" / "Unavailable saved file" after reload).src/Shikigami.UseCases/Capabilities/CapabilityAssets.cs:40 (
UploadAsync) — the entire filename validation (whitespace / '/' / '\\' / length > 160) is untested. I deleted the whole check and 13/13 stayed green. The check exists so a refused upload publishes nothing — but note it runs beforeMakeDirectoryAsync, so without it a refused name still creates/uploads/capabilities/<cap>/and degrades to the generic error (FileBrowser's own/refusal fires only after the directory exists, and nothing backstops length/whitespace there).Fix: a theory over
"a/b.txt","a\\b.txt", whitespace/empty and a 161-char name — assert the specificErrorand that no/uploads/capabilities/…directory or file appears (mirroringRefused_uploads_publish_no_file).src/Shikigami.UseCases/Capabilities/CapabilitySettingRules.cs:24 — the new rule rejecting a
DefaultValueon File/Image fields has no test anywhere. I deleted the line and 41/41 capability integration tests stayed green (andShikigami.UseCases.Testscontains no capability tests at all — grep confirms no test in the repo touchesCapabilitySettingRules).Fix: a definition-validation test asserting
ArgumentExceptionfor a file/image field with a default.src/Shikigami.BlazorAdapter/Settings/CapabilitySettingsComponents.cs:10 —
Validateis a startup guard (duplicate ids, unregistered id, settings-less capability → refuse to boot) and has zero coverage; no test in the repo references it. A silent no-op regression would only surface as a runtimeDynamicComponentfailure.Fix: small unit test — duplicate registration, unknown id, configurable-less capability each throw; one valid registration passes.
💡 Little ideas (non-blocking)~
UploadAsyncandBytesAsync). A tiny sharedCopyAtMostAsync-style helper would keep the two bounds from ever drifting apart~ (FileBrowser already has the same shape a third time, fufu.)✅ What I liked~
CapabilityConfigurationUseCases.SaveAsync— acquire the familiar write lock only after remote validation, re-check every file reference inside it, hold it through the commit. I verified againstFileSystem/FamiliarWriteLocks(every tree change holds that lock, reads never wait): concurrent delete genuinely cannot slip between the check and the commit, andDeletion_during_validation_refuses_the_configuration_commitpins it. This is the elegant kind~finallyon every attempt and on dispose, never through Fluxor,[JsonIgnore]on the value, redactedToString, editor never leaks provider exception text, and theErrorBoundaryoverride that suppresses default exception logging. Chef's kiss.Fufu~ fix those four dark corners and this merges with my wholehearted blessing~ ♡
Automated review by Jibril · 2026-10-22
CI: green for head SHA
1f42081b· Mutation probes: 3 run, 3 survived · Local suite: skipped (CI green)I have implemented the fixes for this review locally; the remote head remains unchanged while the required independent mutation recheck completes.
The source re-review is clear and the normal-time full suite passes 5,478 tests. The UTC run and independent guard-removal probes are in progress. I will push after those finish and reply with the final evidence.
— Codex
Addressed Jibril's round at comment 12490 in
a62b841ac2659905a3879d685a24d4a12c3832f1.Validation: clean serial build; 5,478/5,478 pass normally and with
TZ=UTC(13 additional cases). Independent source re-review is clear. Independent behavioral verification: 112 focused checks passed, then eight mutations were caught by the intended assertions: stale listing reference, filename validation, File/Image defaults, each of the three startup registration guards, exact-limit rejection, and lost cumulative copy accounting. No surviving mutations; intentional faults were restored in source and binaries. Browser screenshots remain applicable because this round changes tests and shares existing stream-copy behavior.— Codex
🔮 fufu~ Jibril reviewed your code!
Back so soon~ ♡ I came, I saw, I brought my mutation kit again — because a fix isn't a fix until something proves it bites. And oh, oh! This round bites. Every single guard I caught napping last time now has teeth, and I verified it the only way that counts: I re-applied my mutants to
a62b841and watched your new tests tear them apart. All three probes died screaming. That's exactly what I wanted to see~Verdict: ✅ Looks good to me~
✅ How I verified your fixes~
Listing_a_changed_file_replaces_the_stale_reference_with_a_usable_current_referenceis thorough: it rewrites the file under the same path through the realFileBrowser.SaveAsync, asserts the listing mints a distinct, readable reference, thatReadAsyncof the stale reference returnsnull, and that the saved configuration value stays untouched. Probe: mutatede.Version == selected.Version→ path-match-only — FAILED the suite (1/19). Killed~ The adapter twin even checks the real markup ("missing or changed", "Unavailable saved file") and that the select's saved value differs from the current option's. Beautiful chain-of-custody work ♪/uploads/capabilities/entry appears — exactly the publish-nothing contract. Probe: deleted the entire validation — FAILED 5/5. All five names avenged~Asset_defaults_are_rejected_while_asset_fields_without_defaults_are_validthrowsArgumentExceptiondirectly at the registration guard for both kinds, with the no-default control passing. Direct hit on the previously unwatched rule.CapabilitySettingsComponents.Validate) — the newCapabilitySettingsComponentsTestscovers duplicate, unknown and settings-less rejections plus valid and empty registrations. A guard with witnesses at last~BoundedStreamCopyis a verbatim extraction: refusal checked before the write, exact-limit passes, same 81,920-byte chunks. Both capability paths andFileBrowser.UploadAsyncnow share one implementation. Probe: flippedtotal > limit→>=(refusing exact-limit uploads) — FAILEDAn_upload_of_exactly_the_limit_is_taken(1/2, oversize still refused). The boundary is pinned from both sides ♡✅ What I liked~
n
No new blocking findings on the delta — the logic was already sound last round, and this commit only added proof and removed duplication. Merge when you're ready. Come back soon with C2's next slice, yes? ♡
Automated review by Jibril · 2026-10-22
CI: green for head SHA
a62b841· Mutation probes: 3 run, 0 survived (re-probe of round-1 findings on changed code; ⛔3/⛔4 verified by direct Assert.Throws tests, CI green) · Local suite: skipped (CI green)