# Plan: What the ambient calendar path should be

**The decision Vikunja #432 asked for. Written 04-08-2026.**

**Verdict: keep the endpoint, change the contract.** The relay app sends structured
fields, not a notification blob. The free-text parser stays as the degraded path, because
there is a real case where the phone cannot produce structure. Delete the endpoint only
if the answer to the one open question below is no.

## First, the task's premise is out of date

#432 states as confirmed that every ambient event lands on the day the notification was
posted, because there is no date parsing at all. That was true when the task was filed
and it is not true now.

`4e4c917` added `dayWords` and `dayOffset` (`internal/calendar/ambient.go:53`), so
"завтра в 15:00" now dates to tomorrow. The same commit added `ambientPastGrace`, which
refuses an event landing more than two hours before the notification, on the reasoning
that the day was inferred and a stale inference is wrong rather than late. `45a5e37`
(#482, this week) fixed a second dating bug the task did not know about: the wall clock
was resolved against the notification's own zone, so every ambient meeting on a non-UTC
box landed off by the deploy's UTC offset.

What is still missing is an explicit date. "5 августа в 15:00" and "12.08 15:00" carry no
day word, so they date to today and the past-grace check drops them. That is a smaller
defect than the one filed, and it fails safe rather than storing a wrong meeting.

The task's third question also has an answer, and the answer is yes. `internal/morning/plan.go:174`
prefixes an uncertain item with "похоже, " and `cmd/mavend/actions_query.go:334` carries
`Confidence < 1.0` into the calendar recital. Both readers hedge.

## The open question, and it is the owner's

**Can the phone read Android's calendar provider, or only the notification text?**

Everything follows from this and nothing in this repo can answer it.

A `NotificationListenerService` sees a title and a body. It cannot know a meeting's real
start, end or organiser, because those are not in the notification. So if the relay is
limited to the notification stream, free-text parsing on this side is not a choice, it is
the only thing available, and #432's suggestion that the phone send structured JSON
cannot be honoured.

If the app may instead read `CalendarContract`, it has the actual event rows, and the
whole parser stops being necessary. That is the better shape by a wide margin: a real
start and end, a real title, an explicit date, no clock-reading heuristic and no
past-grace guard, because nothing is inferred.

Reading the phone's calendar provider does not break the constraint the design was built
around. The refusal in `internal/calendar/ambient.go:10` is about holding a work
credential **on the homelab**, which is what ties the box's blast radius to the employer.
The phone already holds that session. Nothing new lands on homesrv either way.

**Assumption, and it needs his answer:** a managed work profile may block a third-party
app from reading work calendar rows. If it does, the notification stream is all there is.

## The decision

**Keep the endpoint.** Deleting it costs the parser, the tests and the wg-facing token,
and buys nothing while the question above is open. It is off unless `-ambient-token` is
set, so an unbuilt relay carries no surface today.

**Make structured the primary shape.** `/api/ambient` should accept an event with an
explicit start, end and title, and store it without parsing anything. Confidence stays
below 1.0 and the source stays `ambient:notif`, because the provenance claim is unchanged:
this is the phone telling Maven what it sees, not Maven reading a calendar.

**Keep the free-text shape as the degraded path.** It is what a notification-only relay
can send, and it is already written and tested.

**Delete it instead if** the relay is not going to be built. That is the one answer that
closes this without code, and it is his to give.

## What not to do

Do not add date parsing to the free-text path yet. That is the patch #432 explicitly
refuses to accept as closure, and it is the wrong order: if the relay can send a date, no
date parser is needed, and if it cannot, the parser is guessing at a date from text that
was never meant to carry one.

## Follow-on, unrelated to the decision

`cmd/mavweb/ambient.go:33` records a known gap worth keeping visible: an ambient meeting
writes `calendar_event_*` and never `calendar_busy`, so it is good enough to recite and
not good enough to suppress a nudge. That is backwards. Suppressing a nudge is the
lower-risk use of a low-confidence signal, and reciting one is the higher-risk use. It
needs an expiry on the busy level, so it is its own task either way.
