Choosing the SQLite Format for the CDP Knowledge Brain

Executive Summary

The best first implementation for this repository is an ordinary upstream SQLite 3 database stored as .cdp-index/knowledge.sqlite, with one database for the whole repository and a project_id on every project-owned record. SQLite 3 uses a stable, cross-platform, single-file format, which makes it well suited to a local knowledge index and application file. SQLite: Single-file Cross-platform Database SQLite As An Application File Format

The schema should use normalized relational tables for stable concepts, STRICT tables for type enforcement, JSON stored as TEXT for provider-specific fields, and FTS5 for full-text search. SQLite supports per-table strict typing, built-in JSON functions in current releases, and FTS5 virtual tables for search. SQLite STRICT Tables SQLite JSON Functions and Operators SQLite FTS5 Extension

The live .sqlite file should not be the canonical Git artifact. It is a binary, machine-managed file, so ordinary Git diffs and merges are not useful. Git’s own documentation treats machine-managed database-like files as binary content unless a text conversion is supplied. Git Attributes The durable repository record should therefore be sanitized NDJSON snapshots under each project. The central SQLite file can be rebuilt from those snapshots and the existing Markdown documents.

For Adaptation Explorer, Typeform should become an ingestion source rather than the system queried for every read. A trusted scheduled job uses TYPEFORM_ACCESS_TOKEN, upserts responses into the knowledge store, and writes a sanitized snapshot. All later CLI, documentation, and MCP reads use persisted data and do not require the Typeform credential. Typeform’s Responses API supplies historical submissions as JSON and supports incremental retrieval with since, until, before, and after, while webhooks can deliver each new submission to an HTTPS endpoint. Typeform Responses API Typeform Retrieve Responses Typeform Webhooks API

Cloudflare D1 is the strongest second-stage option if the deployed MCP must query or update the latest records directly. D1 provides SQLite-compatible SQL through Workers and Pages bindings, supports JSON and FTS5, and adds managed recovery. Cloudflare D1 Overview Cloudflare D1 SQL Statements It is not necessary for the first local and Git-backed implementation.

What “SQLite Format” Means Here

Several independent decisions are hidden inside the phrase “SQLite format”.

DecisionRecommendationReason
Database engineUpstream SQLite 3Stable, portable and dependency-light local format
Filename.cdp-index/knowledge.sqliteClearly identifies the file; .db and .sqlite3 would not change the underlying format
Repository scopeOne database for the whole CDP IndexEnables cross-project search, relations and analytics without attaching multiple databases
Project isolationproject_id foreign key on project-owned rowsKeeps project queries simple while preserving a repository-wide graph
Schema styleNormalized relational core plus JSON edge payloadsStable fields remain queryable; provider-specific data is retained without creating a column for every form question
Table modeSTRICTRejects incompatible values instead of silently accepting them
SearchFTS5 external-content indexesSupports phrase and full-text search across feedback and documentation
Local journalWAL during normal local useSupports concurrent readers and a writer on the same host
Durable Git representationSanitized NDJSON per projectHuman inspectable, diffable and rebuildable
AttachmentsFiles on disk plus metadata and checksums in SQLiteAvoids inflating the database with screenshots and documents
Remote evolutionCloudflare D1Natural fit if Pages and MCP require live hosted access

The extension does not define a different SQLite variant. knowledge.sqlite, knowledge.db, and knowledge.sqlite3 can all contain the same SQLite 3 file format. .sqlite is recommended here because it communicates the format most clearly.

Repository Context

The current repository already contains the main building blocks of a knowledge system:

Existing surfaceCurrent roleRelevant evidence
Schema-backed MarkdownDurable narrative project memoryProject frontmatter schema
Adaptation Explorer Typeform collectorFetches, normalizes and optionally exports responsesFeedback collector
Generated feedback MarkdownHuman-readable archive of rich feedback and attachmentsTypeform feedback report
PostHog collectorQueries live product analytics and builds summariesAnalytics collector
Trigger.dev automationSchedules Typeform and PostHog collectionAutomation definitions
MCP toolsExpose feedback, analytics and repository knowledgeFeedback MCP tools
Kennel generationBuilds a searchable MCP documentation bundleMCP configuration

The Typeform collector already defines a useful normalized response shape with a response ID, form ID, answers, timestamps, hidden fields, attachment URLs and a Slack-ready message. The problem is persistence and ownership: both the project collector and MCP tool contain fetching and normalization behavior, and normal reads still depend on Typeform credentials.

The generated Markdown report is valuable as a readable presentation, but it is not a good primary data store. A single response can contain multiple answers and attachments, which means filtering, updating one response, deduplicating by response ID, or relating feedback to analytics requires reparsing a large document.

Options Considered

Option 1: One committed SQLite file

This is the simplest conceptually: commit knowledge.sqlite and let every project update it.

It is not recommended. SQLite is a sound application file format, but the file is binary from Git’s perspective. Git can store binary files, but normal line diffs and text merges do not apply without custom conversion. Git Attributes Two branches updating different records can still produce a conflict on the same database file.

This option is acceptable only when there is one writer, branch-based collaboration is unimportant, and the database is treated as a release artifact rather than collaboratively authored source.

Option 2: One ignored SQLite file rebuilt from project snapshots

This is the recommended first stage.

The repository keeps sanitized, normalized records as NDJSON under the project that owns them. The cdp-index CLI imports those records into one ignored SQLite file. Narrative Markdown is indexed into the same database but remains authored Markdown.

Advantages:

  • Project data is readable and reviewable in Git.
  • The SQLite file can be deleted and rebuilt.
  • Queries work without provider credentials after the snapshot is present.
  • One database supports cross-project search and relationships.
  • Provider-specific raw fields can be retained locally without automatically publishing them.

The cost is a build or hydration step after cloning or pulling new snapshots. The CLI should make that automatic.

Option 3: Cloudflare D1 as the primary store

D1 is Cloudflare’s managed serverless database with SQLite SQL semantics, Worker and HTTP API access, and point-in-time recovery. Cloudflare D1 Overview Pages Functions can access D1 through an environment binding. Cloudflare Pages D1 Bindings

This is attractive because CDP Index already deploys its documentation and MCP through Cloudflare. It would allow a Typeform webhook or scheduled worker to write feedback and let the MCP read it immediately.

It is not the best first step because it changes the project from a self-contained knowledge repository into a hosted database application. Local querying, backups, access control, data export and offline work become dependent on another managed surface. D1 should be introduced when freshness or remote multi-writer access becomes an actual requirement.

Option 4: libSQL or Turso

libSQL is a production-oriented fork of SQLite that retains the SQLite file format and API while adding features such as replication and vector support. Turso libSQL Documentation Turso’s embedded replicas can maintain a local file synchronized with a remote primary, although the documentation now describes embedded replicas as a legacy feature and recommends newer Turso Sync for new synchronization use cases. Turso Embedded Replicas

This solves a more complex problem than the repository currently has. It becomes useful if several machines must work offline, write locally, and synchronize changes. It is not necessary for a scheduled Typeform importer plus predominantly read-heavy project knowledge.

flowchart LR
  TF["Typeform"] -->|"credentialed ingestion"| ING["cdp-index ingest"]
  PH["PostHog"] -->|"credentialed ingestion"| ING
  MD["Project Markdown"] --> BUILD["cdp-index build"]
  ING --> RAW["Local raw source records"]
  ING --> SNAP["Sanitized project NDJSON"]
  SNAP --> BUILD
  RAW --> DB[".cdp-index/knowledge.sqlite"]
  BUILD --> DB
  DB --> CLI["Project-aware CLI"]
  DB --> DOCS["Generated reports"]
  DB --> EXPORT["Sanitized MCP export"]
  EXPORT --> MCP["Cloudflare MCP"]

Filesystem Layout

cdp-index/
├── .cdp-index/
│   ├── knowledge.sqlite
│   ├── knowledge.sqlite-wal
│   └── knowledge.sqlite-shm
├── knowledge/
│   ├── migrations/
│   │   ├── 001-core.sql
│   │   ├── 002-feedback.sql
│   │   ├── 003-analytics.sql
│   │   └── 004-search.sql
│   └── schemas/
├── src/knowledge/
│   ├── cli.ts
│   ├── database.ts
│   ├── project-context.ts
│   ├── migrations.ts
│   ├── ingest/
│   │   ├── typeform.ts
│   │   └── posthog.ts
│   └── repositories/
│       ├── feedback.ts
│       ├── analytics.ts
│       └── documents.ts
└── projects/
    └── adapt-action-explorer/
        ├── knowledge.json
        ├── data/
        │   ├── feedback/responses.ndjson
        │   └── analytics/observations.ndjson
        └── assets/typeform-feedback/

.cdp-index/ should be ignored by Git. The -wal and -shm files are normal WAL-mode companion files and must never be treated as independent backups. SQLite documents that WAL state can involve the main database, WAL and shared-memory files; WAL is designed for processes on the same host and does not work across a network filesystem. SQLite Database File Format SQLite Write-Ahead Logging

For a portable database snapshot, the tool should create a clean copy with the SQLite backup API or VACUUM INTO, rather than copying a live database file casually. SQLite documents both mechanisms for producing a consistent database copy. SQLite Backup API SQLite VACUUM

Schema Design

Common relational core

CREATE TABLE projects (
  id TEXT PRIMARY KEY,
  slug TEXT NOT NULL UNIQUE,
  title TEXT NOT NULL,
  status TEXT NOT NULL,
  created_at TEXT NOT NULL,
  updated_at TEXT NOT NULL
) STRICT;
 
CREATE TABLE sources (
  id TEXT PRIMARY KEY,
  project_id TEXT NOT NULL REFERENCES projects(id),
  provider TEXT NOT NULL,
  external_id TEXT,
  record_type TEXT NOT NULL,
  visibility TEXT NOT NULL CHECK (
    visibility IN ('public', 'internal', 'private', 'restricted')
  ),
  config_json TEXT NOT NULL DEFAULT '{}'
    CHECK (json_valid(config_json)),
  UNIQUE (project_id, provider, external_id)
) STRICT;
 
CREATE TABLE knowledge_items (
  id TEXT PRIMARY KEY,
  project_id TEXT NOT NULL REFERENCES projects(id),
  source_id TEXT REFERENCES sources(id),
  kind TEXT NOT NULL,
  external_id TEXT,
  title TEXT,
  body TEXT,
  occurred_at TEXT,
  status TEXT NOT NULL DEFAULT 'active',
  visibility TEXT NOT NULL,
  redaction_state TEXT NOT NULL DEFAULT 'unreviewed',
  payload_json TEXT NOT NULL DEFAULT '{}'
    CHECK (json_valid(payload_json)),
  content_hash TEXT NOT NULL,
  ingested_at TEXT NOT NULL,
  updated_at TEXT NOT NULL,
  UNIQUE (source_id, external_id)
) STRICT;

The relational columns hold fields needed for constraints, joins, sorting and filtering. payload_json preserves irregular provider data that is useful for audit or future normalization. SQLite stores JSON as text and provides functions such as json_extract, json_valid, json_each and json_tree for querying it. SQLite JSON Functions and Operators

Do not put everything into one JSON document. Ratings, timestamps, project identifiers, response identifiers, visibility, answer ordering and attachment relationships are stable concepts and deserve typed columns or child tables.

Feedback extension

CREATE TABLE feedback_items (
  item_id TEXT PRIMARY KEY REFERENCES knowledge_items(id) ON DELETE CASCADE,
  rating INTEGER CHECK (rating BETWEEN 1 AND 5),
  feedback_type TEXT,
  area TEXT,
  landed_at TEXT,
  submitted_at TEXT,
  sentiment TEXT,
  severity TEXT,
  triage_status TEXT NOT NULL DEFAULT 'new'
) STRICT;
 
CREATE TABLE feedback_answers (
  id TEXT PRIMARY KEY,
  feedback_item_id TEXT NOT NULL
    REFERENCES feedback_items(item_id) ON DELETE CASCADE,
  field_id TEXT,
  field_ref TEXT,
  field_type TEXT,
  question TEXT NOT NULL,
  answer_type TEXT,
  answer_text TEXT,
  answer_json TEXT CHECK (
    answer_json IS NULL OR json_valid(answer_json)
  ),
  ordinal INTEGER NOT NULL,
  UNIQUE (feedback_item_id, field_id, ordinal)
) STRICT;
 
CREATE TABLE attachments (
  id TEXT PRIMARY KEY,
  item_id TEXT NOT NULL REFERENCES knowledge_items(id) ON DELETE CASCADE,
  source_url TEXT,
  local_path TEXT,
  media_type TEXT,
  byte_size INTEGER,
  sha256 TEXT,
  visibility TEXT NOT NULL,
  redaction_state TEXT NOT NULL DEFAULT 'unreviewed'
) STRICT;

Typeform response tokens or response IDs should be retained as external_id and protected by UNIQUE (source_id, external_id). This makes retries and overlapping incremental fetch windows safe because the same source response is upserted rather than duplicated.

Typeform documents that responses include landing and submission timestamps and can contain answer arrays with different answer types, including text, choices, numbers, booleans and file URLs. Typeform JSON Response Explanation Typeform Webhook Example Payload Separate answer rows preserve that structure without adding a new column every time the form changes.

Analytics extension

CREATE TABLE metric_observations (
  id TEXT PRIMARY KEY,
  project_id TEXT NOT NULL REFERENCES projects(id),
  source_id TEXT NOT NULL REFERENCES sources(id),
  metric_name TEXT NOT NULL,
  period_start TEXT NOT NULL,
  period_end TEXT NOT NULL,
  numeric_value REAL,
  text_value TEXT,
  unit TEXT,
  dimensions_json TEXT NOT NULL DEFAULT '{}'
    CHECK (json_valid(dimensions_json)),
  query_hash TEXT,
  observed_at TEXT NOT NULL,
  UNIQUE (
    source_id,
    metric_name,
    period_start,
    period_end,
    dimensions_json
  )
) STRICT;

Analytics and feedback should share project, source, ingestion and visibility infrastructure, but they should not share one overloaded payload table. Analytics values have time windows, dimensions, units and reproducibility requirements that feedback does not.

Search index

CREATE VIRTUAL TABLE knowledge_items_fts USING fts5(
  title,
  body,
  content='knowledge_items',
  content_rowid='rowid',
  tokenize='unicode61'
);

FTS5 is SQLite’s full-text-search virtual table module. SQLite FTS5 Extension An external-content index avoids maintaining a second canonical copy of the item body. Triggers or an explicit rebuild command should keep the FTS table synchronized.

SQLite Connection Settings

Every CLI connection should explicitly apply:

PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = FULL;
PRAGMA busy_timeout = 5000;

Foreign-key enforcement must be enabled explicitly per connection because SQLite does not guarantee that it is enabled by default. SQLite Foreign Key Support

WAL is suitable when project scripts and read commands may overlap on one local machine. SQLite notes that WAL can improve read and write concurrency, but all processes must use the same host. SQLite Write-Ahead Logging

synchronous = FULL is the conservative choice for an ingestion database whose latest external records should survive a power failure. SQLite documents that NORMAL in WAL mode protects database consistency but can lose the latest committed transactions after a power failure. SQLite PRAGMA Documentation If all data is rebuildable and ingestion speed later matters, NORMAL can be considered deliberately.

Store timestamps as UTC ISO 8601 text. SQLite has no dedicated datetime storage class and supports date/time values as ISO 8601 text, Julian day numbers or Unix timestamps. SQLite Datatypes ISO text is the most readable and aligns with the Typeform timestamps already used by the collector.

Project-Aware CLI

The CLI should be named cdp-index and discover context from the current working directory.

From projects/adapt-action-explorer/:

cdp-index status
cdp-index build
cdp-index feedback list --since 30d
cdp-index feedback search "CSV export"
cdp-index feedback show 69zt8mx8e3in8wtc297y69zt8428tv5s
cdp-index analytics list --since 7d
cdp-index sql "select area, count(*) from feedback_items group by area"

Those commands should not load Doppler or require Typeform access. They read the persisted store.

Only source synchronization needs credentials:

cdp-index ingest typeform
cdp-index ingest posthog

The CLI should walk upward to find the repository root, then select the nearest projects/*/knowledge.json. --project adapt-action-explorer can override automatic selection for repository-wide commands.

Typeform Ingestion Strategy

The most robust first implementation is incremental polling plus periodic reconciliation:

  1. Store the last successful Typeform cursor and timestamp in ingestion_runs or sync_cursors.
  2. Request responses after the stored cursor, with a small overlap window.
  3. Upsert by (source_id, external_id) inside one transaction.
  4. Download attachments, compute checksums and store files outside SQLite.
  5. Write a sanitized NDJSON snapshot for Git.
  6. Advance the cursor only after the transaction and snapshot succeed.
  7. Periodically reconcile a wider date window to recover missed records.

Typeform’s Responses API supports cursor-based after and before traversal as well as since and until date filtering. The API documentation warns that very recent responses may not appear immediately, which supports using an overlap window or webhook for low-latency ingestion. Typeform Retrieve Responses

A later webhook can send submissions immediately to a D1-backed endpoint. Typeform requires new webhook URLs to use HTTPS, supports signed payloads, and retries certain failed deliveries. Typeform Webhooks API Polling should still remain as a reconciliation path because it is simpler to replay and verify.

Privacy and Publication Boundaries

The repository’s content boundaries exclude personal emails, relationship notes, private threads and sensitive access details. Typeform answers, hidden fields and attachments can contain information that should not be published.

The knowledge schema should therefore make visibility and redaction first-class fields. At minimum:

public      safe for published documentation and public MCP
internal    available to authenticated collaborators
private     retained locally, never exported automatically
restricted  requires explicit handling or removal

The MCP generator must select only permitted records. It should never export payload_json, hidden fields or attachments merely because they exist in SQLite. A specific projection should define which fields are included.

Raw Typeform payloads should remain in the local database or protected hosted store. Sanitized normalized snapshots can enter Git. If a raw response must be deleted for privacy reasons, the deletion workflow must remove the local source record, normalized item, attachment files, snapshots and generated exports.

Why D1 Is a Later Promotion Path

The local schema should stay within the SQLite subset supported by D1 so it can be promoted without redesign. D1 supports most SQLite conventions as well as JSON and FTS5. Cloudflare D1 SQL Statements Cloudflare recommends STRICT tables for avoiding JavaScript and database type mismatches. Cloudflare D1 Workers Binding API

Move the canonical store to D1 when one or more of these become true:

  • The deployed MCP must query feedback immediately after submission.
  • Trigger.dev or a webhook must write from outside a persistent local machine.
  • Several collaborators need shared live writes.
  • Automated consumers cannot rely on pulling repository snapshots.
  • Managed recovery and hosted availability are worth the operational dependency.

Until then, local SQLite plus project snapshots keeps the system transparent, portable and easy to rebuild.

Decision

Use this combination:

  1. Upstream SQLite 3 as the database engine.
  2. One repository-wide .cdp-index/knowledge.sqlite file, ignored by Git.
  3. STRICT normalized tables for projects, sources, knowledge items, feedback, answers, attachments, analytics and ingestion runs.
  4. JSON stored as validated text only for irregular source payloads and dimensions.
  5. FTS5 for project-wide search.
  6. WAL with explicit foreign keys and conservative synchronization for local operation.
  7. Sanitized NDJSON snapshots inside each project as the durable Git representation.
  8. A cwd-aware cdp-index CLI that queries locally without provider credentials.
  9. Credentialed ingestion only, using Typeform and PostHog secrets from Doppler.
  10. Generated sanitized exports for the MCP and docs.
  11. Cloudflare D1 later, if live hosted reads and writes become necessary.

This is more than choosing a .sqlite suffix. It defines a stable separation between source systems, durable repository records, the fast local query index, and publishable knowledge.

Implementation Sequence

PhaseOutcome
1. FoundationAdd migrations, database wrapper, root discovery and project context
2. Feedback persistenceMove Typeform normalization into one adapter and upsert responses, answers and attachments
3. Keyless readsChange local CLI and MCP preparation to query persisted data
4. Durable snapshotsExport sanitized project NDJSON and rebuild SQLite from it
5. SearchIndex Markdown and feedback with FTS5
6. AnalyticsPersist PostHog metric observations using the shared source and ingestion model
7. Hosted decisionEvaluate D1 only after freshness or multi-writer requirements are demonstrated

Before implementation, the project schema should be updated to permit the existing feedback area and document type, and the repository’s currently broken dependency and verification paths should be repaired so migrations and ingestion behavior can be tested reliably.

Sources

SourcePublisherDateUsed For
SQLite: Single-file Cross-platform DatabaseSQLiteAccessed 2026-07-12File portability, compatibility and application-file suitability
SQLite Database File FormatSQLite2026Main database, journal and WAL file behavior
SQLite As An Application File FormatSQLiteAccessed 2026-07-12Suitability as an application data format
SQLite STRICT TablesSQLiteUpdated 2025-06-12Strict type enforcement
SQLite JSON Functions and OperatorsSQLite2026JSON storage and query functions
SQLite FTS5 ExtensionSQLiteAccessed 2026-07-12Full-text-search support
SQLite Write-Ahead LoggingSQLite2026WAL behavior, concurrency and same-host constraint
SQLite PRAGMA DocumentationSQLite2026Synchronization and connection settings
SQLite Foreign Key SupportSQLite2026Explicit foreign-key enforcement
SQLite DatatypesSQLiteAccessed 2026-07-12Date and time storage alternatives
SQLite Backup APISQLite2025Consistent snapshots and backup API
SQLite VACUUMSQLite2025VACUUM INTO snapshots
Git AttributesGit projectAccessed 2026-07-12Database-like binary files in Git
Git gitattributes DocumentationGit projectAccessed 2026-07-12Binary diff and text conversion behavior
Typeform Responses APITypeformAccessed 2026-07-12On-demand response retrieval
Typeform Retrieve ResponsesTypeformAccessed 2026-07-12Pagination, cursors, date filtering and response delay
Typeform JSON Response ExplanationTypeformAccessed 2026-07-12Response timestamps and answer structure
Typeform Webhooks APITypeformAccessed 2026-07-12Webhook delivery, HTTPS, signing and retry behavior
Typeform Webhook Example PayloadTypeformAccessed 2026-07-12Answer and attachment payload types
Cloudflare D1 OverviewCloudflareUpdated 2026-04-30Managed SQLite semantics and recovery
Cloudflare D1 SQL StatementsCloudflareUpdated 2026-04-21SQLite, JSON and FTS5 compatibility
Cloudflare D1 Workers Binding APICloudflareUpdated 2026-04-21Typed Worker access and strict-table recommendation
Cloudflare Pages D1 BindingsCloudflare2026Pages Function access to D1
Turso libSQL DocumentationTursoAccessed 2026-07-12libSQL relationship to SQLite
Turso Embedded ReplicasTursoAccessed 2026-07-12Local replica and remote synchronization model
Project frontmatter schemaCDP Index repository2026-07-12Current project document schema
Feedback collectorCDP Index repository2026-07-12Existing response normalization and export behavior
Typeform feedback reportCDP Index repository2026-07-02Existing generated feedback archive
Analytics collectorCDP Index repository2026-07-12Current PostHog query and report behavior
Automation definitionsCDP Index repository2026-07-12Existing scheduled ingestion surfaces
Feedback MCP toolsCDP Index repository2026-07-12Current live feedback query surface
MCP configurationCDP Index repository2026-07-12Current generated MCP and Cloudflare configuration
Content boundariesCDP Index repository2026-07-12Privacy and publication rules