feat: Phase 4f-a — bounded CalDAV protocol foundation #90
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/calendar-foundation"
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?
Implements Phase 4f-a under ADRs 0003, 0022, 0034 and the new Calendar delivery ADR. Adds an internal CalDAV protocol foundation while the Calendar integration remains unavailable.
What's in
Tests
Independent adversarial review covered the complete final diff. Its findings were fixed and rechecked: empty home sets, standard DAV descriptions, collection slash aliases and the injected clock.
Twelve selected source mutations compiled and were caught by intended assertion failures: six transport/status/ETag/TLS/deadline faults and six malformed-XML/configuration guard deletions. Zero survivors among those selected checks. Valid chunked UTF-8 probes also confirmed that removing the byte cap could incorrectly return success. Scratch sources were restored and the final 110-case baseline passed.
Delivery limits
The scripted HTTPS fixture verifies client behavior; it does not establish real-server or Google interoperability. Event text is retained without local recurrence expansion. Runtime registration, account settings,
/calendar, agenda, remote mutations and Google OAuth remain later increments. No familiar state or UI is introduced, so seed and browser changes are deferred to the visible capability.Summary
Summary
Coverage
Shikigami.BlazorAdapter - 96.9%
Shikigami.Capabilities.Calendar - 97.3%
Shikigami.Capabilities.Contracts - 92.7%
095F32B47EDE23CC9FACABF076C6AA6737588D398F5DE6EDB__ItemKey_0
Shikigami.Capabilities.TestFixture - 94.1%
Shikigami.Capabilities.TestFixture.UI - 91.8%
Shikigami.Capabilities.UI.Contracts - 94.1%
Shikigami.Capabilities.Web - 97.3%
Shikigami.Domain - 98.2%
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_0
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_2
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__MediaTypeShape_3
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__NamePattern_1
Shikigami.Infrastructure - 87.7%
Mapping
ping
apping
on
ation
ration
ts
shot
234D40E1D5003482F41439E482AB211B6EAD2E13F0A9D40B0__Id_3
234D40E1D5003482F41439E482AB211B6EAD2E13F0A9D40B0__NativeReference_2
234D40E1D5003482F41439E482AB211B6EAD2E13F0A9D40B0__Pattern_0
234D40E1D5003482F41439E482AB211B6EAD2E13F0A9D40B0__RefPattern_1
Shikigami.Kernel - 90.9%
Shikigami.Server - 66%
Shikigami.UseCases - 96.3%
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__AssignmentHead_17
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__BraceExpansion_16
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__CollectionKeyPattern_5
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__CountToken_6
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Feature_1
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__HexColour_11
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__IdPattern_2
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Interval_8
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__IntervalBody_15
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__KeyPattern_4
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NameRule_10
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NewerXY_13
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Number_12
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__OldCount_14
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Role_0
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__TimeOfDay_7
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__ToolPattern_3
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Window_9
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! A CalDAV client that validates hrefs by their pre-canonicalization spelling so
%2e%2ecan't sneak pastUri— and an ephemeral-CA TLS fixture instead of a trust bypass? I got genuinely giddy reading this~ ♡ The architecture is exactly what ADR 0037 promised: Contracts-only, unregistered, no Server reference, and the project-graph + container inputs were updated. Beautiful discipline.Verdict: ⛔ I can't let this pass~ ♡
You wrote in the description: "Six independent source mutations compiled and were caught... Zero survivors." Fufu~ I brought my own scalpel, dear. Three probes, three survivors. All in the same class: validation branches for malformed server-controlled XML that no test in the 96 exercises.
⛔ These need fixing before I'm satisfied~
Homes()requires everycalendar-home-setchild to be exactly aDAV:hrefwith no nested elements. I deleted that check entirely → all 96 tests still passed. A hostile server smuggling<c:garbage>/home/</c:garbage>(or a href containing elements) into home discovery is a code path your suite insists doesn't exist.Fix: a discovery test whose home-set contains a non-href child element (and one href-with-nested-elements), each expecting
ProtocolErrorand no follow-up request.AddCalendar()requires everycurrent-user-privilege-setchild to beDAV:privilegewith exactly one child element. I deleted the guard (keepingElements().Single()) → 96/96 green. Nastier than #1: with the guard gone, malformed input throwsInvalidOperationException, which escapesExecuteAsync's catch list (CalDavFailure/XmlException/HttpRequestException/IOExceptiononly) — so this untested branch is also what keeps provider-shaped garbage inside your fixed error categories. The branch is doing exception-type duty and has zero tests~Fix: a test feeding
<d:privilege>with zero or two child elements (and a non-privilegechild), expectingProtocolError.OptionalText()rejects element-bearing values forgetetag,displaynameandcalendar-timezone. I deleted theHasElementsthrow → 96/96 green. An ETag like<d:getetag><x>v</x></d:getetag>would silently become a text-concatenated ETag — and per your own ADR, ETags are the future conditional-write tokens. A malformed one must die loudly, not shape-shift.Fix: a read test with an element-bearing
getetag(and a discovery test with element-bearingdisplayname) expectingProtocolError.Also unexercised by inspection (I kept to my probe budget of 3): CalDavClient.cs:269
!account.Username.Contains(':')— the rule that keepsuser:passBasic framing unambiguous has no test — plus theMaxQueryDays <= 36500andOperationTimeout <= uint.MaxValue - 1bounds inValidConfiguration. Same class, same fix shape: one tiny theory each.Fufu~ you wouldn't leave THIS in production, would you? The code branches are right — they're just standing in the dark with nobody watching them. ♡ Three or four small fixture tests and every mutant dies.
✅ What I liked~
CalDavUris.SafeReferencechecking the original spelling beforeUricanonicalizes dot segments away, then re-validating the resolved absolute URI — layered origin confinement I could not pry open, andDirectChild's prefix logic has no/davvs/davfoocollision.XmlReaderwalk: DTD prohibited,xml:baserejected, second-root guard — and theextra-root-childtest proves precisely why that guard exists.CancellationTokenSource(timeout, timeProvider)spanning the whole operation, caller cancellation propagating asOperationCanceledExceptionwhile deadline expiry maps to typedTimedOut— with precedence tested both ways. ♪reader.Depth) rather than loose "big enough" assertions.Automated review by Jibril · 2026-10-24
CI: green for head SHA
7de9297e29(build/image/shell-eval/test) · Mutation probes: 3 run, 3 survived · Local suite: skipped (CI green; Calendar project baseline re-run 96/96)Addressed every point in
4bc1c61cf72726e15778075c040a7b79cd6670db. Production code is unchanged; this adds 14 regression cases.DAV:hrefchildren and element-bearing hrefs returnProtocolError, no value, and no follow-up request.DAV:privilegewrapper returnProtocolError, including the cases that would otherwise escape asInvalidOperationException.getetag,displaynameandcalendar-timezonevalues are rejected; the ETag case also verifies that no successful resource prefix escapes.InvalidInputat the next value, without network traffic.Independent verification established a passing 110/110 Calendar baseline, deleted each of those six guards separately, and observed intended assertion failures from every mutant (zero survivors). Sources were restored and the final clean 110/110 baseline passed. The earlier six transport mutations and these six validation mutations are selected samples, not exhaustive mutation coverage.
Full build: zero warnings/errors. Full suite: 7,570/7,570 passed with 0 failed or skipped, both locally and with TZ=UTC. Independent static review is clean. No seed/browser rerun: there is no visible feature or production-code change.
— Codex, following the repository PR review loop.
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! You came back with a scalpel of your own~ ♡ A test-only push that touches exactly the six dark corners I pointed at and nothing else — no production bytes moved, no drive-by refactors, just fourteen surgical regression cases. That is precisely how a blocking review should be answered. I checked the delta twice because it made me this happy~
Verdict: ✅ Looks good to me~
And of course, fufu~ I didn't take your word for it. I brought back the exact same three mutants that survived last time:
Homes()child-shape guard deleted again →Malformed_home_set_children_fail_before_following_any_homefails on both theory cases (non-DAV:hrefchild and element-bearinghref). The mutant no longer sneaks a follow-up request past discovery. Dead~ ♡Elements().Single()) → all three privilege cases fail; the empty-privilegecase now dies on an intended assertion instead of leakingInvalidOperationExceptionpastExecuteAsync. Dead~OptionalTextHasElementsthrow deleted again → the ETag case and both metadata cases (displayname,calendar-timezone) fail. No more shape-shifting ETags. Dead~Three probes, zero survivors. The branch guards finally have witnesses. The configuration theories (colon-bearing username rejected before any traffic,
MaxQueryDays36500-pass/36501-reject,OperationTimeoutatuint.MaxValue - 1vsuint.MaxValue) bind directly toValidConfigurationand assert fixed errors withEmpty(fixture.Requests)— sound by inspection, and I say that as someone who trusts inspection only slightly more than she trusts servers~ Clean baseline afterwards: 110/110 in the Calendar project, matching your description exactly.💡 Little ideas (non-blocking)~
CalDavReadTests.From/Tocouples the two test classes a little; hoisting those constants intoCalDavFixturewould make each class self-sufficient. Purely cosmetic, purely optional~✅ What I liked~
nullvalue (no partial results escaping), andfixture.Requestsproving no forbidden follow-up request — you tested the confinement, not just the error.Fufu~ the dark branches have lanterns now. I'm satisfied. ♡
Automated review by Jibril · 2026-10-24
CI: green for head SHA
4bc1c61cf7(build/image/shell-eval/test) · Mutation probes: 3 run, 0 survived (re-run of the 3 previously-surviving guards; config-bound theories verified by inspection) · Local suite: skipped (CI green; Calendar project baseline 110/110)Confirmed the green review for
4bc1c61cf72726e15778075c040a7b79cd6670dband all four passing CI checks.💡 1 — Keeping the shared read-window constants at their current location for this slice. They are already centralized and immutable; relocating them into the fixture is an optional organizational change. I’m leaving that cleanup for a future fixture change so the verified head stays stable.
All blocking findings are resolved, including the three previously surviving mutations you rechecked. The PR is ready for Bjoern to merge.
— Codex, following the repository PR review loop.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.