Normalize every intake path into one event envelope (#283) #78
Closed
claude
wants to merge 1 commits from
overnight/event-envelope into overnight/coldstart-unlock
pull from: overnight/event-envelope
merge into: kami:overnight/coldstart-unlock
kami:master
kami:task/725-capability-ledger-and-empirical-baseline
kami:task/692-heads-path-may-equal-model-path-and-noth
kami:task/694-staticcheck-and-deadcode-are-still-not-i
kami:task/682-go-1-25-5-and-x-text-0-14-0-carry-20-rea
kami:task/674-caveats
kami:task/673-mavgpud-serves-the-model-to-the-whole-la
kami:task/487-capture-device-doc
kami:task/487-capture-device
kami:task/487-wake-word-deploy
kami:task/487-wake-word-threshold
kami:task/487-wake-word-stage-two
kami:task/671-mavwaked-registers-as-a-voice-consumer-i
kami:task/670-cut-claude-md-to-200-lines
kami:task/515-deploy-mavwaked-workpc
kami:task/669-prune-claude-md
kami:task/668-e4b-phrasing
kami:task/668-title-capital
kami:task/668-kiwix-answers-a-question-it-cannot-answe
kami:task/666-only-a-stage-0-grammar-may-take-the-pers
kami:task/487-mavwaked-has-no-wake-word-only-an-energy
kami:task/486-deploy-the-workstation-transcriber
kami:task/486-move-stt-and-tts-to-the-workstation-wher
kami:task/665-crisperwhisper-2-russian
kami:task/664-routing-heads-in-go
kami:task/662-usage-harness-source-badge
kami:task/661-post-merge-usage-rerun
kami:task/661-routing-heads-step-3-train-the-multi-hea
kami:task/660-router-prompt-destination
kami:task/659-destination-fixture
kami:task/655-query-source-is-a-routing-decision-made
kami:task/654-a-pending-clarify-has-no-way-out-neither
kami:task/654-week-of-usage-eval-docs
kami:task/649-needs-kami-telegram-is-the-only-reach-an
kami:task/643-memorystore-search-decodes-and-unmarshal
kami:task/641-two-maps-grow-for-the-process-lifetime-w
kami:task/644-mavcaldav-is-built-documented-as-running
kami:task/642-the-store-caps-sqlite-at-one-connection
kami:task/647-factenrichmentworker-walks-the-pending-q
kami:task/646-v-637-follow-up-telegram-intake-has-no-d
kami:task/638-no-deadline-survives-the-turn-path-from
kami:task/637-inbound-telegram-turns-and-corrections-f
kami:task/636-correcting-a-turn-from-telegram-and-from
kami:task/634-an-act-alias-resolves-the-verb-but-not-t
kami:task/630-one-gesture-correction-on-chat-v-628
kami:task/629-persist-the-routing-trace-and-record-it
kami:task/631-mode-inventory-written-from-the-handlers
kami:task/586-defaultfactparser-uses-hand-written-russ
kami:task/633-reconcile-the-seed-labels-with-the-handl
kami:task/627-reminder-verbs-has-no-alarm-verb-so-an-a
kami:task/626-the-classifier-seeds-teach-an-older-inte
kami:task/546-route-with-a-fine-tuned-e5-small-instead
kami:task/586-measure-the-fact-parser
kami:fix/gofmt-ecosystem-acts
kami:task/584-media-store-a-failed-write-leaks-its-bud
kami:task/518-no-write-path-for-a-backdated-event-so-t
kami:task/287-qa-voice-session-quality-polish
kami:task/492-qa-plan-reconcile
kami:task/530-sweep-tail-four-files-the-russian-sweep
kami:task/405-score-how-often-a-real-utterance-reaches
kami:task/529-money-and-list-pick-a-mechanism
kami:task/528-sweep-tail-the-three-files-on-467
kami:task/527-embedder-open-set-phrasings-stop-being-r
kami:task/526-morphology-a-dictionary-answers-the-gram
kami:task/525-lexicons-the-finite-russian-sets-move-to
kami:task/524-entity-reference-ask-nexus-about-every-l
kami:task/523-risk-tiers-take-hexis-s-tier-for-a-hexis
kami:task/521-review-pr-111-query-strings-declension-h
kami:task/491-llama-server-core-dumps-on-every-sigterm
kami:task/479-bug-an-unconfigured-capability-does-not
kami:task/467-bug-spoken-task-capture-is-dead-the-rout
kami:task/463-deploy-mavwaked-and-mavenclient-run-nowh
kami:task/480-hearing-no-shipped-client-can-start-a-re
kami:task/432-ambient-calendar-intake-is-fragile-and-p
kami:task/431-board-surface-maven-holds-the-work-board
kami:task/433-reactivehandler-has-30-fields-and-is-pas
kami:task/371-swap-the-embedder-for-an-asymmetric-retr
kami:task/408-review-31-07-split-the-30-method-coreapi
kami:task/410-review-31-07-hand-rolled-string-enums-st
kami:task/423-review-pr50-split-internal-ipc-server-go
kami:task/422-review-pr50-split-cmd-mavend-tick-go-860
kami:task/409-review-31-07-finish-moving-mavweb-markup
kami:task/482-ambient-ingest-reads-a-notification-s-ti
kami:task/444-kuma-a-fact-per-monitor-so-she-can-name
kami:task/452-capability-model-homelab-docker-restart
kami:task/449-destructive-confirm-policy-risk-tiers-no
kami:task/453-grocery-list-items-table-fourth-append-o
kami:task/399-run-the-persona-checks-inside-the-daemon
kami:task/448-bounded-follow-up-state-pending-candidat
kami:task/455-conversation-repair-name-the-misroute-co
kami:task/454-go-mod-tidy
kami:task/458-pronunciation-dictionary-for-piper
kami:task/456-command-history-read-only-query-over-exi
kami:task/457-clarification-templates-for-the-router-s
kami:task/474-query-source-ordering-feeds-and-calendar
kami:task/469-reminders-spelled-out-times-fail-the-bod
kami:task/475-bug-the-praxis-attention-capability-is-u
kami:task/481-bug-a-transient-complaint-is-stored-as-a
kami:task/476-bug-the-router-transliterates-latin-enti
kami:task/385-decide-whether-a-parked-clarify-question
kami:task/377-backfill-routines
kami:task/421-weather-geocoder
kami:task/390-no-read-path-for-delivery-attempts
kami:task/386-recall-fixture-filler-note-ids
kami:task/473-bug-morning-item-has-no-required-flag
kami:task/465-bug-make-simulate-routes-with-an-empty
kami:task/467-bug-spoken-task-capture-is-dead
kami:task/466-bug-a-pending-clarify-is-global-so-one-u
kami:task/468-bug-pattern-detect-has-no-minimum-interv
kami:task/462-bug-checkfeminine-flags-second-person-ma
kami:task/443-safekey-drops-cyrillic-so-russian-calend
kami:task/471-bug-agendaquerygrammars-covers-today-but
kami:task/383-slottext-in-clarify-answer-would-clobber
kami:task/323-qa-phraser-coverage-is-65-3-but-the-llam
kami:task/498-bug-and-x-reach-the-model-with-no-determ
kami:task/506-strings-family-6-summaries-and-reports-i
kami:task/504-strings-family-4-act-and-smart-home-repl
kami:task/503-strings-family-3-query-answers-and-gaps
kami:task/502-strings-family-2-capture-acknowledgement
kami:task/501-strings-family-1-phrasing-fallbacks-into
kami:task/397-phrasechat-and-phrasequery-hide-model-fa
kami:task/396-the-reply-path-can-t-be-tested-llmreplie
kami:task/496-recall-a-cross-language-question-loses-i
kami:task/495-bug-x-escapes-the-personal-boundary-and
kami:task/499-llama-server-holds-7-9gb-rss-for-a-1-1gb
kami:task/470-bug-a-question-writes-invented-knowledge
kami:task/493-bug-the-memory-index-stores-the-raw-utte
kami:task/490-name-the-gap-world-questions-through-the
kami:task/485-run-the-big-model-on-the-workstation-wit
kami:task/489-workstation-deploy-mavgpud-on-workpc-and
kami:task/488-workstation-a-supervisor-that-keeps-llam
kami:task/483-docs-offload-design
kami:task/483-design-offload-ml-to-the-workstation-kee
kami:task/459-docs-refresh-the-qa-plan-against-the-liv
kami:task/446-doc-reorg-tier-the-tree-retire-the-three
kami:fix/367-voice-parks-routine-accept
kami:task/365-dialogue-slots-and-router-slots-are-hand
kami:task/364-snooze-does-nothing-at-runtime-the-gate
kami:task/447-retire-progress-md-the-backlog-and-the-f
kami:task/445-session-workflow
kami:overnight/eco-versioned-traces
kami:overnight/eco-entity-refs
kami:overnight/eco-degraded-suite
kami:overnight/netscan
kami:overnight/smarthome
kami:overnight/replay-simulator
kami:overnight/coldstart-unlock
kami:overnight/voice-barge-in
kami:overnight/stt-golden-audio
kami:overnight/senses-speaker
kami:overnight/senses-hearing
kami:overnight/senses-media-vision
kami:overnight/mcp-tools
kami:overnight/mcp-client
kami:overnight/self-update
kami:overnight/model-swap
kami:overnight/web-crawler
kami:overnight/rss-feeds
kami:overnight/email-poller
kami:overnight/email-extract
kami:overnight/email-imap
kami:overnight/money-zenmoney
kami:overnight/task-priority
kami:overnight/task-capture
kami:overnight/behavior-profile
kami:overnight/day-plan
kami:overnight/ambient-calendar
kami:overnight/local-calendar
kami:overnight/memory-eval
kami:overnight/proactive-proposals
kami:overnight/split-voice-quiet
kami:overnight/nginx-maven-block
kami:overnight/stepup-chat-surface
kami:integration/small-batch
kami:docs/fix-drift
kami:fix/ru-wording
kami:integration/jul31
kami:overnight/resident-1.7b
kami:overnight/nudge-templates
kami:overnight/kiwix-rewrite
kami:overnight/eval-writeup
kami:overnight/fix-truncation
kami:overnight/kiwix-client
kami:overnight/ru-prompts
kami:overnight/external-data
kami:overnight/phrasing-grammar
kami:overnight/talk-eval
kami:overnight/prompt-context
kami:overnight/prompt-address
kami:overnight/eval-label-kill
kami:overnight/delivery-boundary
kami:overnight/address-check
kami:overnight/system-replies-pr
kami:overnight/clock-intent-pr
kami:overnight/embedder-backfill-pr
kami:overnight/embedder-marker-pr
kami:overnight/note-recall-pr
kami:overnight/thinking-off-pr
kami:overnight/dialogue-persist-pr
kami:overnight/persona-2p-pr
kami:overnight/clarify-expiry-pr
kami:overnight/clarify-rework
kami:overnight/phrasing
kami:overnight/bakeoff
kami:overnight/recall-margin
kami:overnight/router-on
kami:overnight/slot-extract
kami:overnight/embedder-e5
kami:overnight/router-refusal
kami:overnight/eval-rerun
kami:overnight/eval-harnesses
kami:overnight/eval-rerun-base
kami:overnight/fmt-gate
kami:overnight/routines-fire
kami:overnight/router-prompt
kami:overnight/away-leak
kami:overnight/recall-eval
kami:overnight/snooze-works
kami:overnight/clarify-wiring
kami:overnight/delivery-tests
kami:overnight/routine-accept
kami:overnight/llm-router-flag
kami:overnight/clarify-data-layer
kami:overnight/loop-rule-tests
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "overnight/event-envelope"
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?
What changed
internal/event— the unified intake envelope from 20-07-2026-BACKLOG.md item 1:plus
Bus, a bounded in-memory ring with subscribers. Both pure:PublishandNormalizetakenowas a parameter, so no clock read sits on a path a replay would drive (#284 depends on that).Why it did not require rewriting eight callers
Every intake path in the repo already converges on three
ipc.CoreAPImethods:WriteFactPOST /api/ambient, mavcaldav, mavpoll (zenmoney + wg),/api/signalpresence probes, RSS/crawl watermarksWriteNoteCaptureTaskcmd/mavend/intake.godecorates that one interface. mavweb, mavcaldav, mavpoll, mavmaild and the in-core feed/crawl/capture/vision workers are unchanged — they call the interface they always called, and it now also narrates. The only explicit publish is incmd/mavend/mail.go, which captures through the store directly and so is invisible to the decorator.What it deliberately does not do
Nothing dispatches on an event. An event is a report that something arrived, never an instruction to speak — a feed item becoming a notification is the nag this repo refuses. The journal is memory-only and is never read by the tick loop, the router or delivery.
Read surface
ipc.MethodRecentEvents— AuthRead, daemon-cached exactly likeTickTrace/DayPlan(the store adapter errors; a bare store cannot serve a ring)./eventsin mavweb, read-only, sidebar "Intake" under Automation.Config
intake_journal(int,internal/config): absent ⇒ 512 entries; negative ⇒ off, and then there is no decorator on the intake path at all. Not gated behind an "off unless configured" block: that rule is for capabilities that reach out, and this is a bounded in-memory log of writes core already performed.Files
internal/event/{event,bus,event_test}.go,cmd/mavend/{intake,intake_test}.go,cmd/mavend/{main,tick,mail}.go,cmd/mavweb/{main.go,events.html,events_test.go},internal/ipc/{wire,api,client,server,unimplemented}.go,internal/auth/policy.go,internal/config/config.go.Verified
make buildandmake test(go test -race) both clean. New tests cover the envelope and ring (internal/event at 95.7% statements), the decorator's invariants — a failed write publishes nothing, a deduped capture publishes nothing,OccurredAtcarries the fact'sTsrather than notice time, a nil bus means no wrapper — and the/eventspage, including escaping of feed-supplied titles.Vikunja #283
Decorating one interface instead of patching eight callers is the right seam.
ipc.CoreAPIreally is where every intake path converges. The nil-bus-is-a-no-op rule means a daemon with the journal off carries no decorator at all.WriteFactpublishingreq.Tsrather thannowis the correct call and the comment explains why.CaptureTaskpublishing only onresp.Createdis the detail that keeps a mailbox re-read from refilling the ring.1. The ring is insertion-ordered, but the page sorts nothing and calls itself newest first.
Bus.Recentwalks the ring backwards fromnext, so it returns publish order.events.htmlsays "Everything that arrived, newest first" and rendersOccurredAtin thewhencolumn. Those are two different orderings. The envelope's own doc comment is what makes them diverge: "OccurredAt is when the thing happened, NOT when Maven noticed it."Walk a cold start.
rss.Poller.writepassesit.Publishedas the note ts, and a feed's first read returns twenty items spread over a week. All twenty go throughintakeAPI.WriteNotein feed order./eventsthen shows, top to bottom, thepoll:rssmark fact stamped now. Below it sit notes stamped six days ago, two days ago, and yesterday, in that order. The column walks forwards and backwards.cmd/mavweb/ambient.go:106is the other half. It writesTs: ev.Start. A 09:00 relay of an 18:00 meeting then sits above rows that arrived after it, carrying a future timestamp.TestBusRecentIsNewestFirstdoes not catch this because it publishes with a monotonically increasingOccurredAt. Nothing in the suite publishes out of order. Either sortRecentbyOccurredAt, or add aNoticedAtand order and label the page by that. The second is more honest, because the whole point of keepingOccurredAttruthful is that it is not arrival time.2.
factPriorityinverts attention, and confidence is not recoverable anywhere else.Confidence > 0 && Confidence < 1.0means low. Two writers in the tree pass anything below 1.0. One is the ambient relay, atcalendar.AmbientConfidenceof 0.6. The other is mavcaldav, when it chooses to. So a relayed phone notification about a meeting lands asPriorityLow. Meanwhilefeeds.go:165writesrss:latest:<feed>withConfidence: 1.0and lands asPriorityNormal. The bookkeeping watermark outranks the meeting.Confidence is also gone after that mapping.
Payloadisnilat every one of the four publish sites, so nothing downstream can recover the 0.6. A reader cannot tell an inference from a credentialled read. That is the distinctioninternal/calendarwent out of its way to preserve in the fact row. CarryConfidenceinPayload, or add the field. The three-value priority is a display hint and should not be the only surviving trace.Related and smaller:
Confidence: 0(unset) andConfidence: 1.0both yieldPriorityNormal. The> 0guard makes "nobody said" and "certain" identical.3. Bookkeeping self-writes are journalled as things that arrived.
The decorator sees every
WriteFact, including the ones Maven makes about her own state:feeds.go:165, sourcepoll:rss, keyrss:latest:<feed>, once per poll that found anything.crawls.go:165, sourcepoll:crawl, keycrawl:hash:<name>, once per detected change. A changed page so produces two rows, the note and the hash.ecosystem_acts.go:204, sourcepraxis:trace, keypraxis:<operation>. That is the audit trail of an act she performed, not an arrival.quiet_toggle.go:33, sourcetap:voice, keyquiet_hours. He toggled a setting.The page header claims "Everything that arrived". Four of the writers on it did not arrive from anywhere. With a 512 ring and a handful of feeds, a cold start can evict real intake behind marks. Filter on
Kind == "config"andKind == "system"in the decorator, or make the page say what it lists.Note the second-order effect on the PR 53 finding. Quiet toggles from the text path are stored with
Source: "tap:voice". That mislabel used to live in a facts row nobody reads by source. It now has a page that prints the source in its own column.4.
VoidsIDis dropped, so a retraction reads as an observation.intakeAPI.WriteFactcopiesSource,Key,ValueandTsand ignoresVoidsID. APOST /api/revertthat voids the latest value for a key publishes an envelope indistinguishable from a fresh reading of that key. On a page whose whole job is "what came in", a correction rendered as an arrival is the wrong shape. SetPriorityLowand prefix the title, or put the voided id inPayload.Smaller notes:
req.Kind(env,self,config,system) is discarded. The envelope'sKindis a different taxonomy, which is fine. But the fact's own kind is the field that would have made finding 3 a one-line filter.Payloadis the natural home and is unused.SourceKindmaps anemail:prefix toKindTaskunconditionally. Any futureWriteFactwith anemail:<mailbox>source is journalled as a task. The mail path today captures tasks, so this is latent, not live.eventsTmplwas inserted betweenmorningTmpl's doc comment andmorningTmplincmd/mavweb/main.go. The morning comment now documents the events template.newEventBuslogs "intake journal: off" before checking anything else whencfg == nil. Harmless in the daemon, noisy in a test that passes nil.Landed on master. The stack was one linear chain, so #84 carried every commit from #50 up, and master now contains this branch in full. Merging this PR on its own is an empty diff, so it is closed rather than merged. The review findings for it were fixed in the 2026-08-01 pass and are on master as commits on the stack tip, not on this branch.
Pull request closed