Files
shard-wiki/research/260614-notion-deep-dive/findings.md
tegwick 64796f7b3a research: Notion deep dive (closed block-DB SaaS, external REST API only, database-as-pages); UC-57/58/59
The closed/hosted/schema-rich extreme: everything is a block (UUID id,
type, properties, ordered child content, single parent), pages are blocks
and database rows are pages with a schema, Postgres-backed hosted SaaS.
Databases add typed properties + relations + rollups + formulas across
many views = the apex of wiki-page-as-structured-record. Extension model
has no in-app plugin runtime; the only extensibility is the external REST
API (+ webhooks 2026) inside a tight envelope (~3 rps, eventual
consistency, recursive child fetch, scoped/revocable per-page grants).
Adds the third attachment mode (external-API-only) alongside file-store
(Obsidian/TWiki) and in-engine host (Roam/XWiki); Notion enforces no
silent remote mutation via scoped grants. Added UC-57 (attach closed
external-API-only shard w/ operational envelope + scoped grant), UC-58
(typed database w/ schema+relations+views, no flattening), UC-59
(lossy-aware translation w/ fidelity report); enriched
UC-31/34/36/39/50/51/52/54/56. Boundary: one external-API candidate shard,
best as projection/mirror/overlay/backup, not a substrate and not the
federation layer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 13:44:09 +02:00

303 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Findings — Notion: a closed block-database SaaS, attached only through its API
Date: 2026-06-14
Source kind: **modern shipped product** — a hosted block-database SaaS; a *candidate
shard* of a distinct family (closed, external-REST-API-only, schema-rich, heaviest
translation cost); has **no in-app plugin runtime**
Lens: shard-wiki — the external-API attachment mode, database-as-pages, lossy
translation/fidelity, scoped consent, and graceful degradation against a sovereign
closed backend
> Where Notion sits in the set. Roam and Notion are both **block databases with
> per-block UUIDs**, but they are opposite on access: Roam is a *client-side*
> DataScript DB reachable only through an *in-app* JS API (write-through needs an
> adapter hosted inside Roam); **Notion is a *server-side* Postgres store reachable
> through a real *external* REST API** — attachable from outside with no in-engine
> hosting, but rate-limited, eventually consistent, and **scoped by explicit
> per-integration page grants**. And against Obsidian's "file over app," Notion is the
> mirror image: **app over file** — a closed hosted store with no portable files at
> all. So Notion is the dive that stress-tests shard-wiki's hardest constraints:
> *graceful degradation*, *no silent remote mutation*, *union without erasure*, and
> *Markdown-first that must degrade* against a proprietary, schema-rich, non-Markdown
> backend.
Pairs with — and contrasts against — the Roam dive (block-DB, in-app vs external API),
the Obsidian dive (closed-hosted vs file-over-app), and the XWiki dive (both are
structured wiki-as-app-platforms; Notion is the apex of database-as-pages but closed
and REST-only).
---
## 1. Core data model — everything is a block
Notion's data model is uniform: **"Everything you see in Notion is a block. Text,
images, lists, a row in a database, even pages themselves — these are all blocks."**
Each block record holds:
| Field | Meaning |
|-------|---------|
| **id** | a randomly generated **UUID v4**, visible in the page URL — the block's stable address |
| **type** | determines rendering + which properties apply (text, to-do, heading, **page**, **child_database**, …) |
| **properties** | type-specific attributes (e.g. `title` text; for database rows, the typed property values) |
| **content** | an **ordered array of child block IDs** — nesting |
| **parent** | a single upward pointer — used for **permission inheritance** |
Blocks form a **render tree** (content + parent pointers); indentation is *structural*,
not cosmetic — it changes block relationships. **Pages are blocks**; **databases are
blocks whose children are pages**; a database **row is a page** with typed properties.
Stored in **Postgres** on Notion's servers (hosted SaaS — not local, not files).
The shard-wiki-relevant facts: (a) **per-block UUIDs** give native sub-page addressing
(UC-51); (b) the **page = block, database-row = page-with-schema** identity is a real
page-model impedance (§3); (c) the store is **closed and server-side** — reachable only
through the API (§4).
---
## 2. Databases — schema, typed properties, relations, views
A Notion **database** is a collection of pages sharing a **schema** of typed
**properties**: `title`, `rich_text`, `select` / `multi_select`, `number`, `date`,
`checkbox`, `person`, `files`, **`relation`** (a typed link to rows in another
database — shown on *both* sides, i.e. bidirectional), **`rollup`** (an aggregate over a
relation), and **`formula`** (computed). The same row-set is shown through multiple
**views****table, board (kanban), calendar, gallery, list, timeline** — each a
filtered/sorted/grouped projection of the same data.
For shard-wiki this is the **apex of "wiki page as structured record"** (stronger than
XWiki XObjects, UC-39): a page can be *bodiless typed data*, pages are joined by
**typed inter-database relations** (a graph of typed links), and "the page" is routinely
**a row in a schema** rather than prose. Notion databases also ship the ZigZag insight
commercially: **one row-set, many views/dimensions** (UC-47/48) and **filtered/linked
databases = query-defined pages** (UC-54).
---
## 3. Page-model impedance
Mapping Notion to a Markdown-first page model is the **heaviest translation case** in
the research set:
- **Block/rich-text ≠ Markdown.** Notion's rich text is an annotated-span model; many
block types (synced blocks, columns, callouts, embeds, database views) have no clean
Markdown equivalent. Notion's own **export to Markdown/CSV is lossy** (databases →
CSV, relations/rollups/formulas flatten or drop).
- **Database-row-as-page + schema** must map onto pages + sidecar metadata without
discarding the schema or the relations (extends UC-34/UC-39).
- Therefore translation must be **lossy-aware with a fidelity report** — surface what
did *not* round-trip rather than silently flattening (UC-59). This is *different from*
UC-42 (Foswiki TML↔HTML *lossless* round-trip); Notion is fundamentally lossy.
---
## 4. Extension model — there is no plugin runtime; only the API
This is the defining architectural fact for shard-wiki: **Notion has no third-party
in-app plugin/extension system.** There is no marketplace of code that runs inside
Notion (the only "in-app modding" is the *unofficial* Notion Enhancer desktop patcher,
out of scope). **Extension = external integration via the public REST API** (plus
embeds and, recently, webhooks).
The **public REST API** (`api.notion.com/v1`):
- **Resources:** `pages`, `blocks` (retrieve / append / update / delete children),
`databases` (retrieve, **query** with filters+sorts, create), `users`, `search`,
`comments`.
- **Authorization:** an **internal integration token** (workspace-owned) or **OAuth
2.0** (public integrations). Critically, **an integration only sees pages a user has
explicitly connected to it** — the user "approves the app and connects specific
pages" via *Add connections*. Access is a **scoped, revocable, per-page grant** (§6).
- **Operational constraints the adapter must encode:**
- **Rate limit** ~**3 requests/second** average (429 `rate_limited`); **no paid
increase**.
- **Eventual consistency / no live read model** — the API is not real-time; reads can
lag.
- **Recursive retrieval** — a block returns only **first-level children**; full-page
reconstruction requires walking `has_children` recursively.
- **Payload caps** — 1000 blocks / 500 KB per request; child arrays ≤ 100.
- **Webhooks** (added 2026) deliver page/database change events — a push transport for
UC-31, replacing pure polling.
- **Version history:** Notion keeps **internal page history** (retention bounded by
plan); it is **not portable** and not exposed as git — a UC-36 *supplementation* case
(like Confluence/MediaWiki), not an import case.
- **Publish:** pages can be **published to the web** as read-only public pages — an
outbound publish surface (UC-56).
Consequence: Notion is attachable **only** as an **external-API shard** — but, unlike
Roam, **no in-engine adapter hosting is needed** (the REST API is external). The cost is
the operational envelope (rate-limit, eventual consistency, scoped grant) that the
capability profile must model (UC-57).
---
## 5. Notion as a shard — capability profile
| Capability | Notion | Notes for the adapter contract |
|------------|--------|--------------------------------|
| Read | **yes (external REST)** | rate-limited (~3 rps), eventually consistent, recursive child fetch |
| Write | **yes (external REST)** | append/update/delete blocks, create pages — no in-engine host needed (vs Roam) |
| Write granularity | **block-level (fine)** | like Roam; per-block ops |
| Identity / addressing | **block UUID v4** | native sub-page addressing (UC-51), store-minted (like Roam, not in-file) |
| Structured data | **yes (apex)** | databases: schema + typed properties + relations + rollups + formulas (UC-34/39/58) |
| Native query | **yes** | database query API (filters/sorts) → delegate views (UC-52) |
| Views / dimensions | **yes** | table/board/calendar/gallery = many views of one row-set (UC-47/48/54) |
| Subscribe | **webhooks (2026)** | push events; else poll (UC-31) |
| Version history | **internal, not portable** | supplement via coordination journal (UC-36) |
| Diff / merge | **no native** | — |
| Lock | **no** | — |
| Publish | **publish-to-web** | outbound read-only (UC-56) |
| Access model | **scoped per-page grant (OAuth/token)** | explicit consent; revocable (UC-57, authz) |
| Syntax / content | **proprietary block + rich text** | **lossy** to Markdown; needs fidelity-aware translation (UC-59) |
Verdict: Notion is a legitimate but **demanding** shard — **external-API-attached,
schema-rich, fine-grained, closed**. It behaves best as a **projected / mirrored /
overlay / backup** participant; full write-through is possible but bounded by rate limits
and eventual consistency. The strongest reasons to attach it: structured databases
(UC-58) and block-UUID addressing (UC-51); the strongest cautions: lossy translation
(UC-59) and no portable history (UC-36).
---
## 6. Scoped consent and "no silent remote mutation"
Notion *enforces* one of shard-wiki's INTENT constraints at the platform level: an
integration can touch **only** the pages a user has explicitly **connected** to it, and
the grant is **revocable**. This is a clean, real-world model of **no silent remote
mutation** and of a shard granting the orchestrator **scoped, consented access** — and
it ties directly to the settled **authz-in-core / authn-delegated** decision
([[shard-wiki-auth-in-core-decision]]): authentication to Notion is delegated (OAuth /
integration token), while shard-wiki's *own* authorization decides what to do with the
granted scope. The adapter contract should treat **"scoped, revocable grant"** as a
first-class attachment property (UC-57), not an afterthought.
---
## 7. Mapping to shard-wiki INTENT (compare, do not equate)
### 7.1 Reinforcements
- **Graceful degradation** has its sharpest test here: a closed SaaS with no files, rate
limits, eventual consistency, and lossy export must still be usable as
read/projection/overlay/backup. If the adapter contract handles Notion, it handles
most things.
- **No silent remote mutation** is *modeled by the platform* (scoped grants) — Notion
validates the principle (§6).
- **Database-as-pages** validates that "wiki page" must stretch to **typed records with
relations**, not just prose (UC-34/39/58).
- **Block UUIDs** reconfirm (with Roam) that native sub-page addressing is real and
adoptable (UC-51).
### 7.2 Deliberate divergences (design bugs if conflated)
1. **Closed hosted store; no sovereignty over bytes.** shard-wiki cannot make Notion
git-native or local. It can mirror/project/overlay/back-up and **supply** a
git-addressable history (UC-36) — never claim to own Notion's store.
2. **Lossy, proprietary content.** Do **not** pretend Notion round-trips to Markdown.
Translate lossily *with a fidelity report* and preserve non-mappable elements as
provenance/sidecar (UC-59) — union without erasure includes erasure *of fidelity*
being made visible.
3. **External-API-only, rate-limited, eventually consistent.** Projection must be
**cache/poll/webhook**, not a live read model; sync is bounded — encode this in the
capability profile (UC-57). Do not design flows that assume cheap, instant, unlimited
reads.
4. **One workspace (or its granted page set) = one shard**, never the federation layer.
### 7.3 What Notion teaches that shard-wiki should keep
- Model **operational envelope** (rate limit, consistency class, payload caps,
pagination) as explicit capability-profile fields — Notion makes them unignorable.
- Model **scoped, revocable consent** as a first-class attachment property (UC-57, §6).
- Treat **translation fidelity as data**: a per-shard, per-page report of what projects
cleanly vs. degrades (UC-59) — applies beyond Notion.
- Recognize **external-REST attach** as a distinct, *preferred-where-available* mode:
full write-through without in-engine hosting (contrast Roam) — but pay the operational
envelope.
---
## 8. Use-case seeds → catalog (promoted 2026-06-14)
Last existing UC is **UC-56**. New UCs **UC-57UC-59** added; existing UCs enriched.
| Seed | Catalog action |
|------|----------------|
| **Attach a closed hosted shard via its external REST API only** — no file store, no in-app runtime — honoring rate limits, eventual consistency, payload caps, and a scoped/revocable access grant | **UC-57 (new)** |
| **Attach a typed database (schema + relations + rollups + multiple views) as a shard** without flattening the schema or the inter-record relations | **UC-58 (new)** |
| **Translate a proprietary block/rich-text model to/from Markdown with an explicit fidelity report**, preserving non-mappable elements rather than silently dropping them | **UC-59 (new)** |
| Block UUIDs = store-minted native span addresses (external-API variant) | **enriches UC-51** |
| External-API block-DB attach (no in-engine host) — contrast Roam's in-app-only | **enriches UC-50** |
| Database query API + filtered/linked DBs | **enriches UC-52, UC-54** |
| Database-as-pages apex; typed records + relations | **enriches UC-34, UC-39** |
| Webhooks (2026) as a push transport | **enriches UC-31** |
| Internal-only page history, not portable | **enriches UC-36** |
| Publish-to-web outbound | **enriches UC-56** |
| Scoped, revocable per-integration grant; no silent mutation | links **UC-57** + [[shard-wiki-auth-in-core-decision]] |
---
## 9. Architecture notes for SHARD-WP-0002 (no UC)
- Add an **operational-envelope** section to the adapter capability profile: rate limit,
consistency class (live / eventually-consistent / snapshot), payload/pagination caps,
recursive-fetch requirement, push-vs-poll transport. Notion is the forcing example.
- Add **access-grant semantics**: scope (which pages), revocability, auth mode
(delegated token/OAuth) — ties the authz-in-core decision and "no silent mutation".
- Add a **translation-fidelity capability**: adapters declare and report what content
round-trips vs. degrades (UC-59); generalizes UC-42 (lossless) to the lossy case.
- **Attachment-mode taxonomy** now spans: file-store direct (Obsidian/TWiki, UC-40),
in-engine hosted adapter (Roam/XWiki, UC-38/50), and **external-API-only**
(Notion, UC-57). T14 binding should enumerate all three.
- **Database/schema/relations** as a unit (UC-58) presses the page-model spec: collection
+ schema + typed relations, not just a page.
---
## 10. Open questions (for spec / workplans)
1. Is **external-API-only with a tight rate limit** (Notion) viable for write-through at
wiki scale, or do we cap Notion at read/projection/overlay/backup by default?
2. How are **inter-database relations** (UC-58) represented in the union — as typed links
in the link graph, as a separate relation index (cf. ZigZag many-to-many), or both?
3. What is the **fidelity report** format (UC-59), and where does it surface — provenance
panel, projection metadata, reconciliation review?
4. For **scoped grants** (§6), how does shard-wiki represent partial visibility (only
*some* of a workspace's pages granted) without misrepresenting the shard as complete?
5. Do we consume Notion **webhooks** (push) or poll, given eventual consistency and the
rate limit (UC-31)?
---
## 11. Sources
| Source | Used for |
|--------|----------|
| Notion — "The data model behind Notion's flexibility" (https://www.notion.com/blog/data-model-behind-notion) | Everything-is-a-block; block record (id/type/properties/content/parent); render tree; pages/databases as blocks; Postgres |
| Notion Docs — Request limits (https://developers.notion.com/reference/request-limits) | ~3 rps rate limit, 429, payload caps, recursive first-level children |
| Notion Docs — Authorization (https://developers.notion.com/docs/authorization) | Internal token vs OAuth; integration connected to specific pages; scoped grant |
| Hookdeck / ClickUp — Notion webhooks guides (https://hookdeck.com/webhooks/platforms/guide-to-notion-webhooks-features-and-best-practices) | Webhook support (2026), page/database change events |
| Truto / Rollout — Notion API architecture & essentials (https://truto.one/blog/how-to-integrate-with-the-notion-api-architecture-guide-for-b2b-saas/) | REST endpoints (pages/blocks/databases/search), integration patterns, no in-app plugin model |
| General API knowledge — database property types, views, relations/rollups/formulas, export-to-Markdown lossiness | §2, §3 |
Cross-references: `research/260614-roam-deep-dive/findings.md` (block-DB/UUID, in-app vs
external API), `research/260614-obsidian-deep-dive/findings.md` (closed-hosted vs
file-over-app), `research/260613-xwiki-deep-dive/findings.md` (structured wiki-app-
platform), `spec/UseCaseCatalog.md` (UC-31, UC-34, UC-36, UC-39, UC-50/51/52, UC-54,
UC-56), `workplans/SHARD-WP-0002-federation-architecture.md` (T14), and the authz
decision [[shard-wiki-auth-in-core-decision]].
---
## 12. Traceability
- New UCs: **UC-57, UC-58, UC-59**`spec/UseCaseCatalog.md`.
- Enriched UCs: **UC-31, UC-34, UC-36, UC-39, UC-50, UC-51, UC-52, UC-54, UC-56**.
- Architecture (no UC): operational-envelope + access-grant + translation-fidelity
capability fields; three-way attachment-mode taxonomy; database/schema/relations in the
page model → `SHARD-WP-0002` (T14).
- Decision link: scoped/revocable grant + no-silent-mutation → [[shard-wiki-auth-in-core-decision]].
- Boundary recorded: Notion is **one external-API candidate shard** — closed, hosted,
schema-rich, lossy-to-Markdown — best as projection/mirror/overlay/backup; not a
substrate, not the federation layer (INTENT graceful-degradation + no-silent-mutation).
</content>