Repository navigation
Notion adapter v1: read, verified append, formatting-preserving block edit - #48
Merged
Merged
Conversation
added 2 commits
October 1, 2026 08:53
…ing block edit notion.page.read, notion.page.append and notion.block.update behind DAVE_ENABLE_NOTION_TOOLS (needs DAVE_ENABLE_TOOLS). Pages are named in DAVE_NOTION_PAGES; the secret comes only from DAVE_NOTION_TOKEN. - Refs (b1, b2, ...) live in a per-run server-side ledger keyed by run_id; the model never supplies a Notion ID, and the tools refuse outside lifecycle runs. - Before every write: re-read the block, walk its parents to the configured page, compare the content the run saw. - Text edits replace inside one rich text run and send every other run, link, mention and equation back unchanged. - Writes end verified, failed, or unknown; an unknown append is never repeated in the run. Write plus check run shielded from cancellation under a per-page guard. - Fixed api.notion.com transport, pinned Notion-Version 2026-03-11, no redirects, no env proxies, response cap. - Approval previews for both write tools (text nodes only), catalog fixture, manifest, docs.
…deadlines Stop new Notion writes after cancellation and bound response reads
…conciliation Keep Notion cancellation tests on the ledger outcome contract
… failed vs unknown before send - The run ledger records whether a verified append was delivered to the caller. When a run deadline stopped waiting, the shielded append still lands and verifies; an identical retry in that run is now refused instead of duplicating the blocks. An in-flight identical append gets its own refusal message. - The page guard is checked before the ledger, so a busy refusal no longer overwrites an earlier record for the same content. - An internal error before the write request is sent is reported as failed (nothing written), not unknown. - Probes: tests/test_notion_adversarial.py (uncertain writes and cancellation, fault at every request position); fake Notion gains delays and skip counts.
…accurate depth reason - notion.block.update needs block_text (the to-do's text, checked against what the run read) when only checked changes, and the approval card shows it, so a confused ref cannot tick the wrong to-do unseen. - Before any write, the configured page is fetched; a trashed page is refused with a clear reason instead of Notion's opaque refusal. - A block nested past the eight-level parent walk is reported as too deep to confirm, not as moved off the page. - Probes: exact-match refs, moves into child pages and between configured pages, type changes, child-page refs, trashed pages and blocks, per-run ref isolation, deep nesting, a database configured as a page.
- old_text uniqueness counts overlapping matches: "aa" in "aaa" occurs
twice, so the edit is refused instead of silently taking the first.
Found by a Hypothesis property ("bb" + bold "b", old_text "bb").
- Text runs are split at 2000 UTF-16 units on code-point boundaries, so
emoji-heavy text is accepted for appends and edits that grow a run.
- Probes: Hypothesis properties for single-run edits (every other run,
link, mention and equation kept), cross-run refusal, split-invariant
signatures, round trips through the fake; overlap and UTF-16 cases;
the fake enforces the UTF-16 limit.
…t; actionable refusals - DAVE_NOTION_TOKEN must be 20-256 printable ASCII characters without whitespace; otherwise every call answers a configuration error and the value is never sent. - httpx.LocalProtocolError / UnsupportedProtocol mean nothing left the process: a write reports failed (retry allowed), not unknown. - unauthorized, object_not_found and restricted_resource refusals name the operator's fix (check the token / share the page). - A response over the 2 MB cap is reported as such for reads. - Probes: proxies and SSL env ignored, redirects not followed, hostile error codes, cursor and ID injection, token absent from DEBUG logs, routes, events, transcript and the manifest.
- Listing stops once 300 blocks are held (3 requests for a 1,000-block page instead of 10). - Blocks whose children were not read (depth limit, child pages, synced blocks, meeting notes, request budget, a refused subtree listing) are marked children_not_read; a refused subtree no longer fails the read. - Out of request budget, blocks already fetched are still returned. - Duplicate block IDs, repeated cursors, and has_more without a cursor end the listing as truncated; the append baseline treats them as incomplete and refuses rather than anchoring on a wrong last block. - Probes: long pages, depth and type markers, refused subtrees, cursor loops, missing cursors, output budget speed and order, clipped text, request budget, tables and columns.
…ncel mid-write - Probes through the router: tampered decisions (409, nothing written), expired approvals, approval after the run deadline, the legacy loop cannot pre-approve a Notion write, an edit made while approval waits is never overwritten, a model that edits before reading recovers, failed and unknown outcomes reach the model and events as errors, and cancelling mid-write lands once and frees the page. - Docs: DaveHarness approves before the adapter's own checks run, so a call that will be refused can still ask for approval (it writes nothing); cancelling a run during an approved write cannot recall a request Notion already has (the run ends cancellation_failed).
- New GET /tools/agent/runs/{run_id}/pending/notion-context (run owner
only) returns, from the run ledger and never from the model, the whole
block before and after a pending edit, or the refusal a doomed call will
meet. It contacts nothing and changes nothing.
- The approval card renders it as text: 'Whole block now / after', a note
that formatting is kept, and a red 'This call will be refused, so
approving it writes nothing' above the buttons.
- Probes: router pending calls render the Notion preview in the real JS;
context content, refusals, and route scoping.
- Verified in the real app in a browser (simulated Notion and Ollama):
edit, to-do, reject, hostile append, phone width, dark mode.
This was referenced Oct 1, 2026
added 5 commits
October 1, 2026 15:59
- A write that finds another DaveLLM write to the same page waits up to 8 s (polling, outside the shield, nothing held while waiting) before refusing, instead of throwing away the user's approval at once. - A run's ledger keeps at most 8 MiB of block snapshots as well as 2,000 blocks. - Probes: two runs racing on one page take turns; the wait is bounded and a cancelled waiter takes nothing; stale cross-run edits are refused; ten pages in parallel stay separate; the guard is released on errors and cancellation; dropping a ledger mid-write is harmless; ledger memory bound.
- docs/DAVELLM_TOOLS.md lists every Notion message verbatim, in four tables (refused before sending, reads, Nothing was written, Outcome unknown); a probe extracts every message from the source with ast and keeps the catalog complete. - CI measures davellm_notion.py branch coverage separately (97% now, gate 90%) without touching the DaveHarness coverage gate. - CLAUDE.md's required checks compile davellm_node_profiles.py like CI; a probe pins the two lists together. - Remove the unused _ANNOTATION_KEYS. - Probes for every previously untested outcome path: long pages, accepted-but-different appends and edits, vanished anchors, unreadable or changed blocks after a lost edit, refused page checks, odd parents, the 100-run limit, errors after sending, malformed rich text, argument checks without the schema, ledger eviction.
CI failed on Python 3.14 for 580be1a and 6d4cdb3: the Run 5 probe used a 1.0 s wall-clock limit and the runner took 1.13 s and 1.35 s. The limit was flaky, but it was measuring real waste: trimming re-encoded the whole result after every removal (182 MB of JSON for one 300-block read, on the event loop). Each entry is now measured once and cut from a running total; the probe bounds bytes encoded instead of time.
…ion's client - 429 and 529 (service_overload) mean Notion did not carry the request out, as in Notion's own JS client: retried once (Retry-After up to 5 s, or 1 s without one), otherwise reported as Nothing was written instead of an unknown outcome that blocked a safe retry. - Reads are retried once after a 500 or 503; writes never are. - The legacy transcription block (renamed meeting_notes) is not opened. - Probes: the proposal's whole live acceptance sequence through the router (read, approved append, verified, formatting-preserving edit, rejected edit, out-of-scope page, re-read), and listing shapes from the SDK's types (partial blocks, tab, transcription). - The lifecycle fixture keeps one event loop per test: a TestClient outside a with block starts a loop per request and cancels run tasks that outlive their POST.
…em today Developer portal URL, workspace-owner requirement, Configuration tab for the token, and the page's ••• menu → Add connections; start the router from a terminal so the exports are seen.
DaveHomeAssist
marked this pull request as ready for review
October 2, 2026 04:24
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
DaveLLM's own Notion adapter, behind
DAVE_ENABLE_NOTION_TOOLS(honored only withDAVE_ENABLE_TOOLS):notion.page.read(read): title plus blocks with run-scoped refs (b1,b2, …); marks blocks whose children were not read.notion.page.append(write, approval every call): plain-text blocks at the end of a configured page, verified by readback.notion.block.update(write, approval every call): replace text inside one rich text run (every other run, link, mention and equation sent back unchanged) and/or check a to-do (named byblock_text).Guarantees:
DAVE_NOTION_PAGES; blocks only by run-local refs held server-side; before every write the page and block are re-read, parents walked to the configured page, content compared. Not a lock (Notion has none).verified,failed(retrying is safe), orunknown(never repeated in the run). 429/529 count as not carried out and 5xx writes are never retried, matching Notion's own JS client. A cancelled run sends no new write; a dispatched one is still checked.GET /tools/agent/runs/{id}/pending/notion-context), and a warning when the call will be refused. Text nodes only.api.notion.com,Notion-Version: 2026-03-11, no redirects or proxies, absolute deadlines, response cap; the secret (environment only) is validated and appears in no result, log, route, event, transcript or manifest.Iterated through 10 test → fix runs (scope, cancellation, rich-text fuzzing with Hypothesis, transport and secrets, read limits, lifecycle, approval UI in a real browser, concurrency, docs and contracts, the proposal's acceptance sequence and Notion's SDK types). Includes Codex's independent cancellation fixes (#49, #50). Complete error catalog in
docs/DAVELLM_TOOLS.md.Separate follow-ups (task chips, not in this PR):
file.editoverlapping-match ambiguity; a DaveHarness pre-approval check hook;Cache-Controlfor static assets.Test plan
mypy daveharness, manifest--check, node checks, 9 approval-preview tests,git diff --check