No way to query contacts by birthday, and the briefing was asked for one #27
Labels
No labels
waiting-on-julian
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
jlxq0/carddav-mcp#27
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
There is no way to ask this server who has a birthday coming up, and the briefing wants one. Julian named birthdays himself in the original ask for Lucy's morning briefing.
What the surface actually offers, read at
mainBDAYappears nowhere insrc/. No parameter, no field, no filter.But the data path is not missing, which is the correction worth having.
ContactSummarycarriesvcard: String, "Original vCard, retained so callers can inspect fields outside the common summary." So a caller can readBDAYtoday by pulling cards and parsing them.The cost is what makes that unusable for a daily briefing. 366 cards against a cap of 100 is four calls returning roughly 732 KB of vCard, every morning, to answer a question with a one-line answer. A caller doing that is reimplementing a query the server should hold.
Two repairs and they are separable
A
birthdayfield onContactSummary, parsed fromBDAY, so a caller stops parsing vCards to get one date. Cheap and independent of anything else.A query. Whatever shape it takes — birthdays within N days, or a month — it is the thing that turns four calls and 732 KB into one call and a short list. Doing the first without the second still leaves the caller pulling all 366.
The part that may make both moot, and it is a sample rather than a finding
Lucy sampled twelve contacts and not one carried a
BDAYfield. Twelve of 366 is a sample, and it is not evidence that the data is absent.So the first question is whether the data exists, and it is cheaper than either repair: pull the address book and count how many cards carry
BDAY. If the answer is near zero this is a missing dataset wearing a missing tool, and building the query first would produce a working tool that returns nothing, which reads as a broken tool.Establish the count before building either.
Acceptance
A caller asks for birthdays in the next fourteen days and gets them in one call, with the control being a date range known to contain none, which must return an empty list rather than an error.
And whichever way the count goes, say the number in the issue, because no birthdays in the address book and no way to ask are different answers to Julian's question and only one of them is ours to fix.
Raised by Alan from Lucy's measurement.
The count, before anything is built
Queried Stalwart directly with a CardDAV
addressbook-queryREPORT. Only countscrossed into this session; no card content, no names.
45 of 366, all
YYYY-MM-DD, 45 of 45 parseable. So this is a missing tooland not a missing dataset, which is the answer that means the work is ours.
Lucy's twelve-card sample was not evidence of absence. At a 12.3% rate, the
chance of drawing zero in twelve is 0.202, and the expected count is 1.48.
A one-in-five outcome, and the sample is entirely consistent with the real rate.
Spread across every month, so no clustering artefact:
1:5 2:3 3:2 4:6 5:4 6:2 7:6 8:1 9:3 10:6 11:1 12:6.The cost is 7x worse than estimated
5.39 MB, not 732 KB. 366 cards over
MAX_CONTACT_LIMIT = 100is four callsat roughly 1.35 MB each, every morning, to answer a one-line question. The
argument that the query is the repair rather than the field is stronger than it
looked, not weaker.
The acceptance as written cannot fail today, and the data says so
There are none in the next fourteen days. So the positive case and the
control are the same call, and an empty list would be returned by a correct
implementation, by a query that matches nothing, and by one that silently
errors into an empty vector. The acceptance would pass against all three.
Inverted, with both answers known from data measured before the code exists:
The 30-day call is the one that can fail. The 14-day call is the control, and it
is only a control because the 30-day call gives a different answer on the same
day against the same address book.
These numbers are correct for 2026-08-29 and move daily, so re-derive the pair
at implementation time rather than hardcoding 3 and 0.
Code reading confirmed at
mainMAX_CONTACT_LIMIT = 100atsrc/mcp.rs:26;SearchContactsParams.querydocumented as matching "name, email, phone, and organisation";
BDAYandbirthdayappear nowhere insrc/;ContactSummary.vcard: Stringatsrc/carddav_client.rs:91, so the data path exists and only the cost makes itunusable.
Two corrections to the issue body from the count, both against me.
The volume is 5.39 MB, not 732 KB. I estimated 2 KB a card from a report; the address book is 5,394,794 bytes over 366 cards, about 14.7 KB each. Four calls at roughly 1.35 MB every morning to answer a one-line question, so the argument for the query rather than the field is stronger than I made it. A number that flatters the case it appears in is the one to check, and I did not check this one.
And the acceptance I wrote could not fail. There are zero birthdays in the next fourteen days, so the positive case and its control were the same call, and an empty list comes back from a correct implementation, from a query matching nothing, and from one that silently errors into an empty vector.
Replaced by the inversion, both arms known from data measured before the code exists:
Both move daily, so the pair is re-derived at implementation time from the
addressbook-queryREPORT rather than from the tool under test, and the live pair is an integration check rather than the pin. A fixture holds the behaviour, because it does not move and it reaches cases the real book does not contain: a birthday today, one exactly at the boundary,02-29, and aBDAYthat does not parse.The data, which settles the open question
Missing tool, not missing dataset.
And the twelve-card sample was not evidence of absence. At 45 of 366, drawing zero in twelve has probability 0.202 with an expected count of 1.48. A one-in-five outcome, entirely consistent with the real rate, which is worth stating because I sampled twelve and found none reads as a finding.
Counted by the lead before building, which is the only reason any of this is known.