Download Markdown specification ↗ · Test Evidence ↗ · Workflow Diagram ↗
Status and scope
Public-facing specification v0.1 · October 15, 2026. Rewritten from maintained project notes; no private workspace links, source identifiers, execution payloads, or real household lists are included.
Grocery functionality is implemented in part, with node-level observations and reported checks. End-to-end regression, security checks, and production reliability are not established. Dinner and task editing are separate planned work.
Problem and intended outcome
Let a user maintain a Notion grocery list through Telegram while avoiding duplicate records and unintended changes. A missing item in an update must prompt for confirmation rather than silently create a record.
Acceptance goals: correct target, predictable writes, no silent additions, no repeat duplicates, and a reply reflecting confirmed Notion state. No time-saving or accuracy metric has been measured.
Architecture and responsibilities
- Telegram receives input and returns lists, questions, and results. The workflow includes an authorized-user gate; its security effectiveness still needs acceptance evidence.
- n8n orchestrates queries, branching, Notion writes, and replies. JavaScript parses model output, normalizes names, resolves matches, and formats messages.
- Ollama runs qwen2.5:7b locally for intent and field extraction. The model does not directly write grocery records or supply authoritative Notion page IDs.
- Notion is authoritative for groceries: Item (title), Quantity (text), Category (select), Store (select), Purchased (checkbox), and Notes (text). Purchased=false is active; true is inactive/purchased.
- An n8n Data Table holds proposed-action state scoped to chat. It is temporary state, not a second grocery or inventory database.
- Docker hosts n8n locally; a Cloudflare quick tunnel exposes the Telegram webhook. The machine and tunnel must remain running.
Functional requirements
| ID | Behavior | Acceptance goal |
|---|---|---|
| GL-01 | List | Return active groceries grouped by category; exclude purchased records. |
| GL-02 | Add | Extract name and optional quantity, query existing items, then create an active record only when absent. |
| GL-03 | Duplicate | An active normalized match must not create a second page. |
| GL-04 | Reactivate | Reuse a purchased matching page by setting it active. |
| GL-05 | Remove | Set Purchased=true; distinguish absent and already-inactive cases. |
| GL-06 | Update | Resolve the existing page and update supported fields. If absent, ask before adding. |
| GL-07 | Confirm | Recover stored name and quantity, rejoin shared duplicate checks, then clean up after the correct outcome. |
| GL-08 | Cancel | Clear the pending request without changing groceries. |
| GL-09 | Compatibility | Unrelated messages and empty pending lookups must preserve normal command/chat routing. |
| GL-10 | Guardrails | Gate senders, handle ambiguous matches, and confirm writes only after Notion success. |
Data contracts
Logical examples use fictional groceries and placeholders. Validate missing fields, empty names, unknown intents, and types before entering write branches. Treat chat identity as trusted trigger metadata, not a model-authorized identity.
{
"intent": "grocery_add",
"chatId": "<trusted-chat-id>",
"items": [
{
"name": "oat milk",
"quantity": "2 cartons",
"updates": {}
}
]
}Match and pending-action contracts
Match status is new, already_listed, or previously_purchased. Resolve existingPageId from Notion results, never from model invention. The current matcher uses whitespace/case normalization and basic plural simplification; it is heuristic and may collide.
Pending fields: id, chatId, intent, itemName, quantity, createdTime. Lookup is by chat; the selector chooses the latest system createdAt. Confirmation wording uses bounded regex rules. One-active-action policy, expiry, and multiple-row behavior still need hardening.
{
"name": "oat milk",
"quantity": "2 cartons",
"status": "new",
"existingPageId": null,
"pending_example": {
"id": "<pending-row-id>",
"chatId": "<trusted-chat-id>",
"intent": "grocery_add",
"itemName": "oat milk",
"quantity": "2 cartons",
"createdTime": "<ISO-timestamp>"
}
}Decision and write rules
- Ingress: authorized Telegram input → pending lookup → latest action → Confirm, Cancel, or Normal Message. Empty lookup must not discard the message.
- Normal: preserve slash commands; use local intent extraction for conversational requests, then parse and route structured data.
- Add: fetch candidates → normalize/match → new record, active no-op, or purchased reactivation. New records are classified and the updated list is returned. Quantity persistence needs regression.
- Remove: set Purchased=true on the resolved record. Do not delete the page.
- Update: match existing page and update supported properties. Missing page → persist proposed add → ask user. Combined-property updates remain outside verified scope.
- Confirm: reconstruct the stored add shape and reuse duplicate checking. Cleanup must follow successful create/reactivation or a valid no-op; all outcome paths still need verification.
- Cancel: delete the selected pending row without a grocery write. Expanded negative wording remains unverified.
- Failure: do not report success before the Notion operation succeeds; preserve recoverable state and verify retry behavior.
Risks and nonfunctional acceptance
- Retry safety: repeated delivery must not duplicate writes or clear the wrong pending action.
- State isolation: test chat isolation, stale rows, several pending rows, interrupted conversations, restarts, and expiry.
- Matching: ambiguous names, plural collisions, and multi-item requests must not update the wrong page.
- Observability: record sanitized expected/actual outcomes and execution references without exposing secrets or private content.
- Local operation: latency, availability, and cost savings are not measured. Host sleep and tunnel reachability are constraints.
Test evidence and release boundary
See the separate Test Evidence register for observations and pending cases. The cancellation mismatch is a known defect, not a passing test. The workflow is not labeled fully tested or production-ready.
Before a stronger release claim: document GL-01–GL-10 results, confirmation cleanup on every valid path, quantity persistence, cancellation variants, normal-message regression, retries, state lifecycle, and authorization.
Decisions and next scope
- Use AI for interpretation and regular workflow nodes for writes so conversational flexibility does not bypass record resolution.
- Keep grocery truth in Notion and temporary conversational intent in n8n state.
- Soft removal supports reuse and history.
- Reuse the add branch for confirmation, accepting the need for explicit branch-context and cleanup tests.
- Regex confirmation is inexpensive and predictable but brittle for language variants.
- Finish grocery validation before extending the pattern to dinner and task editing.