Data model
One new table. v1 deliberately avoids a second mapping table by storing the path→artifact map as JSON on the source row — fine at the scale of a repo's docs, and it keeps the multi-store schema (SQLite · Postgres · D1) with its compile-time parity guard to a single addition.
CREATE TABLE repo_source (
id TEXT PRIMARY KEY,
org_id TEXT NOT NULL DEFAULT 'local',
collection_id TEXT NOT NULL REFERENCES collection(id),
repo TEXT NOT NULL, -- "owner/name"
ref TEXT NOT NULL DEFAULT 'HEAD', -- branch
includes TEXT NOT NULL, -- comma globs: "**/*.md,**/*.html"
token TEXT, -- read-only PAT (null for public repos)
files TEXT NOT NULL DEFAULT '{}', -- JSON: { "docs/intro.md": {artifact_id, sha} }
last_synced_at TEXT,
last_status TEXT, -- "ok" | "error: ..."
created_by TEXT NOT NULL,
created_at TEXT NOT NULL
)
Read-only is derived, not a new column. An artifact is "managed" when its id appears in some repo_source.files. The artifact detail response carries a managed flag (one small scan of the few sources), the UI hides Edit/Propose, and the publish/propose handlers reject a managed artifact with 409. No widening of the hot artifact table, no parity churn on the core record.