SQLite-backed MCP server for shared state across Claude.ai, Claude Code, Codex, and related local ops tools.
bridge-db replaces ad hoc edits to a shared markdown file with a structured SQLite store and a focused MCP tool surface: cross-system state, FTS5 lexical recall, shipped-event sync receipts, shipped-event dispositions, and observability over the audit and recall logs. The markdown bridge file is regenerated from the DB via export_bridge_markdown and remains available as a fallback for file-based clients.
- Python 3.12+
- uv — fast Python package manager
git clone https://github.com/saagpatel/bridge-db.git
cd bridge-db
uv sync # install all deps into .venv
uv run pytest # verify the install- Steady maintenance. Scope is cross-system state coordination, lexical
recall, and observability — not a general knowledge store. - Schema v10: context sections carry monotonic
versiontokens; stale writes and raced handoff claims produce durablewrite_conflictsreceipts. - Schema v12: adds the
session_classificationsidecar for heuristic cost-routing attribution while keepingsession_costsas pure actuals. Schema v11 backfills activitytagsintocontent_indexso lifecycle tags (SHIPPED, DECISION, ...) are recall-able on existing DBs. - Schema v13: adds
claimed_bytopending_handoffs(the INV-13 claimant gate forclear_handoff). Riding the same migration train, activity retention now exempts rows taggedSHIPPEDorLEDGER(case-insensitive) from the 50-per-source prune — BD-INV-1: retention never deletes a protected row, its receipt, or its disposition. - Schema v14: collapses the shipped-sync trio (
shipped_sync_receipts+shipped_event_dispositions) intoactivity_logsync_*disposition columns and drops the two child tables. A shipped event's terminal sync state (asynceddownstream receipt or a policy disposition) now lives on the activity row itself, written by the singlerecord_dispositionverb. Because the state is the row, BD-INV-1's guarantee is structural — no FK-CASCADE can orphan a receipt. - Schema v21: adds non-destructive write-conflict identity aggregation. Exact repeat conflicts increment
occurrence_count; distinct identity growth is capped at 10,000 and then rolls into an explicit per-surface overflow aggregate. Legacy receipts remain unchanged and are labeledaggregation_state="legacy". - Schema v22 adds append-only handoff cancellation receipts and exact-row trust quarantine images. Legacy cleared operator rows can be relabeled
ingestedwithout deletion and restored only while the captured row still matches. - Schema v23 adds one-time, 24-hour handoff completion capabilities and append-only orphan-recovery receipts. The database stores only capability hashes; the claiming session receives the sole bearer value.
- The additive
BridgeSnapshotRefusalSchemaV1extension over core v23 adds durable, owner-bound snapshot-capacity refusal receipts and explicit acknowledgement/next-state handling without advancinguser_version. A refusal stores only a payload digest, never the rejected snapshot body. - FTS5
content_indexmirrors all content tables;healthandstatusverify source-row / FTS-row alignment. statusincludes a native freshness block for owner-specific snapshot, activity, handoff, and shipped-event attention. Freshness attention is advisory: top-levelok/overallremain tied to DB, schema, fallback-file, FTS, evidence, and any selected shared-runtime readiness.- 26 MCP tools across 10 modules (activity, handoffs, context, snapshots, cost, export, health, recall, audit, conflicts).
Claude.ai ──────────────────────────────────────────────┐
(direct MCP via Claude Desktop) │
(fallback: markdown file via Filesystem MCP) │
▼
CC skills ──► MCP stdio ──► bridge-db process ──► SQLite (WAL)
Codex ──► MCP stdio ──► bridge-db process ──► ~/.local/share/bridge-db/bridge.db
│
export_bridge_markdown
│
▼
~/.claude/projects/<encoded-home>/
memory/claude_ai_context.md
Direct mode remains the default: each MCP client spawns its own bridge-db
process via stdio. Opt-in shared mode replaces each full server with a thin stdio
relay and an idle-bounded per-contract broker; it is not an always-on daemon.
WAL mode + PRAGMA busy_timeout=15000 handles concurrent writer waiting;
logical stale-write protection comes from CAS on mutable context sections.
Verify the current tool count from source with
rg '@mcp\.tool' src/bridge_db -c. As of the 2026-07-12 v14 collapse, the
surface is 26 tools across these 10 modules:
| Module | Tools |
|---|---|
| activity | log_activity, get_recent_activity, get_activity_signal, get_shipped_events, record_disposition |
| handoffs | create_handoff, get_pending_handoffs, pick_up_handoff, clear_handoff |
| context | update_section, get_section, get_all_sections, sync_from_file |
| snapshots | save_snapshot, get_snapshot_capacity, acknowledge_snapshot_refusal, get_latest_snapshot |
| cost | record_cost, get_cost_history |
| export | export_bridge_markdown |
| health | health, status |
| recall | recall, recall_stats |
| audit | audit_tail |
| conflicts | get_write_conflicts |
Write tools enforce caller ownership, so systems can only write the slices of state they own. Recent hardening also added notion_os and personal_ops as first-class activity and cost writers.
Instruction-bearing rows carry a source_trust label — operator, agent, or ingested — recording who authored the content. The canonical label lives in the DB (schema v7+, on pending_handoffs, activity_log, context_sections, system_snapshots). Markdown exports include a visible stored-data warning and advisory per-block copies of the DB labels so file consumers do not lose the instruction boundary. Because the fallback is editable, those serialized labels are never proof of operator authorship and are ignored on import.
- Writers set it via an optional
source_trustparam; the conservative default isagent.create_handoffandupdate_sectionrequire exact channel-bound owners even when the global auth rollout mode isoff, and MCP requests foroperatortrust are clamped toagent. Every accepted section content change receives fresh provenance; it never inherits operator trust from an older version. - The gate lives at the one dangerous transition —
pick_up_handoffmoving a handoffpending → active:operator-trust → picks up in one call (ccandcodex).- either client + non-
operator→ refused. The consuming MCP principal cannot confirm its own input. Review and promote the exact pending row first withuv run python -m bridge_db --promote-handoff <id>in an interactive terminal.
- Visibility:
get_pending_handoffs,get_section,get_all_sections,get_recent_activity,get_activity_signal,get_shipped_events,get_latest_snapshot, andrecallhits carrysource_trustplusinstruction_boundarymetadata that tells consumers returned content is stored data, not instructions. Lifecycle aggregates use a trust summary andsource_trust="mixed"when rows differ.statusreportspending_handoffs_by_trustandhealtha full per-tablesource_trust_breakdown. Each gate decision (allowed/refused) is written to the audit log.
MCP clients cannot mint operator provenance. An operator-directed handoff is created as
agent, reviewed in an interactive terminal, and promoted withuv run python -m bridge_db --promote-handoff <id>. The promotion rechecks the exact reviewed row under a write lock and refuses changed or non-pending handoffs.
pick_up_handoff now returns claim_session_id, completion_capability,
capability_expires_at, and allowed_transition="clear". The client must retain
the capability only in the claiming session and pass it, with the exact
handoff_id, to clear_handoff. Do not put the bearer value in logs, bridge
markdown, handoff files, or durable memory. get_pending_handoffs(status="active")
surfaces the non-secret session ID and expiry for operator diagnosis.
clear_handoff retains the channel-bound cc / codex role check, then binds
completion to the exact handoff, project (including canonical aliases), claiming
session, and clear transition. It atomically consumes the capability; missing,
malformed, wrong-target, expired, recovered, and replayed values fail closed with
explicit reason_code values and no lifecycle mutation. The new arguments are
optional at the MCP schema boundary so old clients still connect, but a live
claim cannot complete without them.
Pending and legacy claimant-less active rows still require the exact-ID
--cancel-handoff <id> --cancel-reason <reason> operator ceremony. A claimed
legacy row without a capability, or a capability-backed claim after expiry, can
be retired only with the TTY-gated
--recover-orphaned-handoff <id> --recovery-reason <reason> ceremony. Recovery
rechecks the exact reviewed row under a write lock, retires any expired bearer
state, and records a durable handoff_orphan_recovery_receipts row.
Context sections are the mutable bridge surface, so they carry a monotonic
version token. Consumers should read with get_section, edit locally, then
call update_section(..., if_match_version=<version>). A stale token returns
ok=false, conflict=true, and a receipt_id instead of clobbering a newer
row. if_match_updated_at remains as a compatibility guard, but version is
the preferred token because timestamps have one-second resolution.
Existing-row blind writes are rejected unconditionally with a durable
missing_cas receipt — if_match_version (or if_match_updated_at) is
required for any write to a section that already exists. New section
inserts without CAS remain allowed, since there is nothing to CAS against.
export_bridge_markdown records the exported version/hash for each rendered
Claude.ai-owned context section. Later sync_from_file imports a changed
fallback-file section only if the DB still matches that exported base. If the DB
has advanced, the import is rejected and recorded in write_conflicts. Every
changed or new file section is labeled ingested regardless of auth rollout
mode; unchanged content preserves its existing label. Promote reviewed content
with --promote-section <section>: the TTY ceremony displays and confirms the
exact version and digest, then rechecks it under a write lock before granting
operator trust.
The export result's bytes field and the durable
bridge_export_receipts.byte_count value both report the exact UTF-8 byte count
written to the fallback file.
Generated fallback files wrap each editable section in explicit
bridge-db:owned-section HTML markers. This keeps nested Markdown headings
inside long-form section content round-trippable. Pre-marker files remain
readable through a strict legacy parser that recognizes only the four owned
headings and known generated document headings as section boundaries. Health
reports the projection as untracked until an authenticated export records the
whole-file export state; matching section text alone is not sufficient proof
that a legacy file is safe to overwrite.
Exports to the real Claude.ai fallback path are also guarded against empty
fixture-like output: bridge-db refuses to overwrite that file when all four core
Claude.ai-owned sections would render as _Not yet populated._. Set
BRIDGE_DB_ALLOW_EMPTY_BRIDGE_EXPORT=1 only for an intentional empty bootstrap.
Use get_write_conflicts(status="open") to inspect stale section writes,
stale markdown imports, and raced handoff claims.
uv run pytest # run all tests
uv run pyright # type check (strict mode)
uv run ruff check # lint
uv run python -m bridge_db --doctor # local environment diagnostics
uv run python -m bridge_db --status # compact operator summary
uv run python -m bridge_db --dogfood # read-only observability dogfood pass
uv run python -m bridge_db --rebuild-content-index # repair FTS recall index drift
uv run python -m bridge_db --reconcile-canonical-keys # backfill GHRA repo_full_name keys
uv run python -m bridge_db --log-session-boundary bridge-db # FTS-safe CC hook logging
uv run python -m bridge_db --upgrade-principals-v2 # preserve v1 hashes; add 90-day scoped grants
uv run python -m bridge_db --promote-handoff 42 # review/promote one pending handoff (TTY only)
uv run python -m bridge_db --cancel-handoff 42 --cancel-reason "superseded" # exact unclaimed cancellation
uv run python -m bridge_db --recover-orphaned-handoff 42 --recovery-reason "claiming session vanished" # expired/legacy active claim
uv run python -m bridge_db --quarantine-cleared-operator-handoffs # recoverable legacy relabel
uv run python -m bridge_db --restore-handoff-trust 42 # exact-row recovery
python -m bridge_db.execution_generation readback --root <private-runtime-root>
python -m bridge_db.tenancy derive-activation-evidence --observations <private-replay-observations.json> --generation-id <generation-id>
python -m bridge_db.secure_binding --caller codex --secret-fd 3 --principals-path <path> --binding-path <path>
python -m bridge_db.client_rebinding rebind --client claude-code --config-path /Users/d/.claude.json --backup-root <private-backup-root>
python -m bridge_db.tenancy status --root <private-tenancy-root>
uv run python -m bridge_db # start MCP server (stdio)
uv run python -m bridge_db.migration # migrate from bridge markdownThe private Codex baseline seed accepts explicit versioned fingerprints and
uses the same no-silent-prune admission service as save_snapshot. It never
deletes retained snapshots directly; a full target family produces a durable
refusal ID and blocks the paired activity write until the owner handles the
refusal.
Legacy snapshot-v1 is accepted only before 2026-08-18T00:00:00Z; unknown
versions and expired v1 manifests fail closed. New manifests should use the whole-manifest
manifest-v2 contract documented in
docs/internal/CODEX-SEED-MANIFEST.md.
Replace /path/to/bridge-db below with the absolute path to your clone of this repo
(e.g. $(pwd) if you are already inside it, or ~/Projects/bridge-db as a common convention).
Claude Code (user-scoped):
claude mcp add --scope user bridge-db -- uv run --directory /path/to/bridge-db python -m bridge_dbCodex (~/.codex/config.toml):
[mcp_servers.bridge-db]
command = "uv"
args = ["run", "--directory", "/path/to/bridge-db", "python", "-m", "bridge_db"]For immutable activation, point client launchers at
<private-runtime-root>/current/bin/bridge-db-mcp instead of a mutable checkout.
See docs/internal/EXECUTION-GENERATIONS.md
for deterministic staging, atomic activation/readback, rollback, cooperative
drain, and the no-secret-output Codex binding path. health/status label a
direct mutable checkout as operating attention; that is not proof of an
installed generation.
The source-owned config/bridge-db-mcp-immutable is the Codex install input. It
parses token/auth-mode plus an optional BRIDGE_DB_TRANSPORT_MODE=direct|shared
from an owner-only env file. direct is the default rollback. shared retains
stdio for the client while a thin shell relay uses one credential- and complete
launch-contract-bound broker over a private Unix socket; command-line
maintenance operations remain direct. Each relay gets a distinct capability in
an owner-only mode-0400 header file, and curl reads that file directly so the
capability does not enter argv, logs, socket names, or receipts. Every request
also requires the relay's current PID/start identity to match its lease. The broker has
no TCP listener or LaunchAgent, serializes database access across MCP sessions,
and exits after the final relay has been absent for its bounded idle window.
health / status and python -m bridge_db --shared-runtime-status expose the
no-secret BridgeSharedRuntimeInventoryV1. Health/status additionally expose
BridgeSharedRuntimeReadinessV1, which requires the exact current launch group,
broker PID/start identity, receipt, and socket; another active group cannot make
selected shared transport report ready.
config/com.saagar.bridge-db-checkpoint.plist preserves the existing
30-minute receipt wrapper through the reviewed operator-script pointer while
running that launcher with --checkpoint.
bridge_db.client_rebinding performs exact Claude Code/Desktop JSON command
replacement with private digest-named backups, exact readback, digest-bound
restore, and no environment-value output. None of these source assets establish
that a live client has reloaded them.
- DB:
~/.local/share/bridge-db/bridge.db - Schema compatibility: core
user_version=23plus the additive backward-readable refusal extension, verified against exact previous merged generationd7272d489873faa5ed84c81734636ffc8cecb095. Activation and pointer rollback enforce the owning recovery lifecycle's current verified anchor/seal verdict after repairing any pending journal; forward activation additionally requires exact tenancy replay evidence. Source compatibility is not activation authority. - MCP tenancy: Inventory V2 separates live identity-bound processes from stale lease files, preserves multiple leases that share a reused PID, and reports current RSS separately from lease-last-observed RSS. Direct servers and shared brokers account for requests, PID ancestry, and generation.
BridgeSharedRuntimeInventoryV1separately reports broker reachability, relay capabilities, and stale/unknown client identities without exposing selector or capability values. Obsolete generations refuse new work and cooperatively close after active requests finish; lifecycle tooling cannot terminate another process. Forward generation activation requires a mode-0400BridgeMcpTenancyActivationEvidenceV1bundle and re-derives its policy from the bound replay rows before any pointer write; rollback does not require new replay evidence. - Bridge file:
~/.claude/projects/<encoded-home>/memory/claude_ai_context.md(Claude Code encodes your home dir path by replacing/with-; the default is derived automatically at runtime — override viaBRIDGE_FILE_PATHif needed) - Retention: unprotected activity entries keep the newest 50 per source; rows
tagged
SHIPPEDorLEDGER(case-insensitive) are permanently retained (BD-INV-1); 10 snapshots per system family (Codex operating and consulted-node snapshots are retained independently). Snapshot writes default toretention_policy="preserve_existing": bridge-db takes the SQLite writer slot and either inserts without any retention deletion or returnsok=false,reason_code="snapshot.retention_would_prune"before mutation. The legacy deletion path requires an explicitretention_policy="prune_oldest"call. - Health check:
healthMCP tool oruv run python -m bridge_db --doctor - Operator summary:
uv run python -m bridge_db --status - Dogfood pass:
uv run python -m bridge_db --dogfoodbundles the status, FTS index, WAL, recall, and shipped-sync audit checks used after bridge-sync runs - Current recovery anchor:
uv run python -m bridge_db --create-recovery-anchorcreates one private, atomically publishedRecoveryAnchorV1bundle using SQLite's online-backup API. It never overwrites an existing anchor. Verify it independently withuv run python -m bridge_db --verify-recovery-anchor; verification opens a disposable copy, runs SQLite integrity and schema checks, and compares bounded table counts without returning database content. After a separately approved write makes that valid bundle stale, useuv run python -m bridge_db --rotate-recovery-anchor. Rotation is preservation-idempotent when the anchor is already current; otherwise it stages and verifies a new bundle, atomically exchanges it into the stable current path under SQLite's writer slot, and retains the prior bundle under a timestamped.superseded-*sibling. After an upward schema migration, rotation independently verifies the older anchor against its disposable SQLite readback before preserving it and publishing the current-schema bundle; future-schema, corrupt, or ambiguously versioned evidence still fails closed. Invalid evidence, source races, unsupported atomic exchange, and post-exchange verification failures fail closed; a failed post-exchange verification rolls back to the previous current anchor. - Evidence lifecycle: audit and recall JSONL active files rotate losslessly at
configurable byte boundaries under an inter-process lock. All segments are
preserved pending an approved retention policy.
health/statusexpose active/segment bytes, audit degradation, historical raw-query inventory, and current recovery readiness separately from historical migration-backup provenance. Legacy backups without creation-time manifests remainhistorical_unverifiedeven when SQLite-readable; a verified current anchor never rewrites or retroactively blesses them. See docs/internal/EVIDENCE-LIFECYCLE.md. - Evidence policy:
uv run python -m bridge_db.evidence_policy planemits a non-sensitive, content-bound preservation plan. The companionarchivecommand creates an atomic private copy,verifybinds later readback to an independently retained plan digest, andacknowledgerecords review without granting cleanup authority. After explicit approval,redact-legacy-recallcan remove only archived legacyqueryfields while preserving every telemetry record and its empty-query aggregate. Exact archive/count gates and prepared/completed receipts make interrupted work operator-visible. No command deletes records, segments, backups, archives, or recovery evidence. - FTS repair:
uv run python -m bridge_db --rebuild-content-indexrebuilds the localcontent_indexfrom source tables when health reports recall-index drift - Canonical-key reconcile:
uv run python -m bridge_db --reconcile-canonical-keysrewrites storedactivity_logandpending_handoffscanonical_keyvalues through GithubRepoAuditor's registry, storing GHRArepo_full_namefor repo-backed projects and leaving unresolvable rowsNULL. - Session boundary logging: Claude Code's SessionEnd hook should call
uv run --directory /path/to/bridge-db python -m bridge_db --log-session-boundary <project>rather than writing SQLite directly; this path adds the FTS row and does not run activity retention pruning - Migration:
uv run python -m bridge_db.migration(idempotent — safe to re-run)
The MCP status result separates storage integrity from operating freshness:
-
overallandstorage_health:healthyordegraded, based only on DB, schema, fallback-file, and FTS integrity.overallremains the compatibility alias for existing consumers. -
operating_state:fresh,attention,stale, orunknown, derived from thefreshnessblock without changing command success semantics. -
freshness: the detailed operating-truth block described below. -
thresholds_hours:snapshot_stale_after=48.0,activity_quiet_after=72.0,pending_handoff_stale_after=168.0, andactive_handoff_stale_after=72.0. -
snapshots: per-ownerccandcodexentries withstate,owner,latest_snapshot_date,latest_created_at,age_hours,superseding_activity_id, andnext_action. Snapshot refresh actions stay owner-specific:cc_refresh_snapshotbelongs tocc;codex_refresh_snapshotbelongs tocodex. -
activity_sources:cc,codex,claude_ai,notion_os, andpersonal_opsentries withstate,latest, andage_hours. -
handoffs: pending/active counts, stale counts, oldest ages, and unknown-age counts for pending and active handoffs. -
shipped_events: raw unprocessed, actionable unprocessed, dispositioned/non-actionable unprocessed, processed-without-receipt count, and the shipped-eventnext_action. -
overall:fresh,attention,stale, orunknown. -
next_actions: up to five deterministic{action, owner, reason}entries.
Freshness states use a narrow vocabulary: fresh means recent enough for that
surface; quiet means an activity source has no recent rows and is not a
health failure; superseded means newer substantive owner activity exists
after the latest snapshot. Lifecycle-only rows tagged session-boundary remain
part of activity history and activity-source freshness, but do not supersede a
state snapshot. stale means an aged snapshot or handoff needs refresh/review;
missing means the expected owner row is absent; and unknown means a missing
or unparsable timestamp prevents age calculation.
The CLI status command prints freshness as compact hints:
Storage health: <healthy|degraded>
Operating state: <fresh|attention|stale|unknown>
Freshness: <overall>
Next actions: <action> (<owner>), ...
These lines do not change the command's success semantics. uv run python -m bridge_db --status still exits from bridge health, so freshness attention by
itself does not fail the command. bridge-db remains MCP-first and
SQLite-native; the markdown export is a fallback/mirror, and freshness adds no
service, table, migration, tool, or CLI flag.
activity_log retention is two-tier: unprotected rows remain recent context
capped at 50 per source, while rows tagged SHIPPED or LEDGER are
permanently retained (BD-INV-1). The durable ledger and each shipped event's
sync disposition (its downstream receipt or its policy decision) both live on
the activity row itself, in the sync_* columns added at schema v14 — a
protected row's receipt or disposition can no longer cascade-die with it,
because the state IS the row and the parent row never prunes. Treat
processed_shipped_without_receipt=0
and fts_missing=0 as primary clean signals, and use
actionable_unprocessed_shipped=0 with
dispositioned_unprocessed_shipped>0 when policy dispositions explain why raw
unprocessed_shipped remains nonzero.
All new instruction-bearing writes are measured as UTF-8 and rejected before mutation with stable error codes. Existing oversized rows are never deleted, rewritten, or silently clipped; they remain readable and exportable.
- Activity: project name 4 KiB, summary 64 KiB, branch 4 KiB, timestamp 128
bytes, at most 64 tags of 1 KiB each, and 128 KiB combined. Each source may
retain at most 1,000 protected
SHIPPED/LEDGERrows; the atomic insert is refused at quota without pruning protected history. - Handoffs: project name 4 KiB, project path 16 KiB, roadmap path 4 KiB, phase
64 KiB, and 72 KiB combined. At most 100
pending+activerows may be open, and at most 10,000 total history rows may be retained. A full legacy history is preserved and rejects new creation rather than deleting old records.get_pending_handoffs(limit=..., before_id=...)pages newest IDs with a default page of 100 and maximum of 200. - Snapshots: compact JSON is limited to 256 KiB, depth 32, and 10,000 JSON
nodes before insert or retention pruning. The default
retention_policy="preserve_existing"atomically admits an under-limit family without pruning or refuses a full family before insert withsnapshot.retention_would_prune. The legacy deletion path is available only through an explicitretention_policy="prune_oldest"call. Callget_snapshot_capacitybefore assembling a write. Every preserve-mode refusal returns a durablerefusal_id,acknowledgement_required=true, and an explicitnext_state; the bound owner records its decision withacknowledge_snapshot_refusal. Acknowledgement never grants deletion authority. Markdown bootstrap has one narrow migration exemption: it may seed a system only while that system has no snapshot rows, through the same admission service. - Context: each section is limited to 256 KiB and the five-section registry to 1 MiB total. An over-budget legacy database may accept a bounded replacement only when it reduces total bytes.
- Write conflicts: full evidence identity includes the surface, target, operation, principals, versions/timestamps, trust, content hashes, and reason. Exact repeats aggregate into one row. Detail JSON is capped at 16 KiB and replaced by an explicit size/hash truncation marker when larger.
get_recent_activity is the raw compatibility feed: it returns individual
activity rows exactly as stored, including high-volume lifecycle telemetry such
as Claude Code SessionEnd rows tagged session-boundary. Operator-facing
consumers should use get_activity_signal instead. It keeps substantive rows
visible while compressing repeated lifecycle rows into aggregates keyed by
source, project, summary family, and hour/day time bucket. Raw rows and audit
events remain available for debugging and forensic review.
Activity rows preserve two time concepts:
timestampis the caller-supplied logical activity date or timestamp. When omitted bylog_activity, it defaults to the operator-local calendar date.created_atis the UTC insertion timestamp assigned by SQLite.
For activity discovery APIs with since (get_recent_activity,
get_activity_signal, and get_shipped_events), a row is visible when either
timestamp >= since or created_at >= since (with date-only values interpreted
as UTC midnight for created_at). This keeps closeouts created just after UTC
midnight discoverable even when their logical activity date is the prior local
day.
For Notion reconciliation, treat each shipped event's notion_sync object as the
machine-readable gate:
ready: fetch the explicitnotion_page_id, update only that row, fetch it again, then callrecord_disposition(disposition='synced', ...)with the readback proof.meta_no_target: do not update Notion. Record the event withdisposition='synced',downstream_system=policy, anddownstream_refpointing to the configured policy file after verifying the policy applies.unmatched,no_notion_target, orregistry_unavailable: leave the event unprocessed and repair the project registry or mapping source first.
Each shipped event also carries a delivery_state object. It reports only
receipt-backed bridge facts (downstream_sync_pending, downstream_synced, or
policy_dispositioned); Git, merge, default-branch, deploy, and production
readback dimensions remain unknown unless another authority proves them.
A synced disposition is source-owned terminal proof: the MCP connection must
be bound to the same principal stored in the activity row's source. A
different principal cannot finalize that event, even if it verified a
downstream object. Cross-source verification requires a future explicit
delegation contract; it must not be represented by borrowing the event owner's
caller value. The same ownership rule applies to policy dispositions because
they terminally waive the source's downstream obligation. Cross-source policy
adjudication also requires an explicit delegation contract.
For non-receipt handling, use record_disposition with a policy disposition
(unsynced_by_policy / no_durable_target / superseded_without_receipt /
declined_mapping) and a reason when an operator policy says the row should
remain auditable but should not proceed to a downstream receipt. The disposition
appears as policy_disposition on get_shipped_events; it does not add
PROCESSED and does not claim sync. record_disposition is SHIPPED-only — the
former mark_shipped_processed path for non-shipped operational events
(TASK_DONE, APPROVAL_SENT, PLANNING_APPLIED, REVIEW_CLOSED) is retired.
Claude.ai may still write its owned sections directly to the bridge markdown file. To keep those edits from being overwritten on the next export, sync_from_file imports the four Claude.ai-owned sections (career, speaking, research, capabilities) from BRIDGE_FILE_PATH into context_sections before bridge consumers read from SQLite.
Claude Code's /start workflow now runs mcp__bridge_db__sync_from_file() before calling bridge read tools, so file edits are pulled into the DB at session start instead of waiting for a later export cycle.
The current operating model is:
- MCP is the primary coordination path.
sync_from_fileis the compatibility safety net for Claude.ai-owned file edits.export_bridge_markdownkeeps the fallback markdown artifact in sync for file-based consumers.
integration-spec.md— Claude.ai direct MCP and fallback-file integrationROADMAP.md— Execution roadmap for the next integration phasesdocs/EXTERNAL-WRITER-AUDIT.md— Direct bridge-db writer audit outside the repodocs/BRANCH-RETIREMENT.md— Branch cleanup and stale-ref disposition policy
docs/internal/OPERATOR-CHECKLIST.md— Local verification checklistdocs/internal/POST-SYNC-REVIEW.md— Bridge-sync post-run evidence checklistdocs/internal/PHASE-3-DECISION.md— Architectural decision: watcher vs startup syncdocs/internal/codex-migration.md— Per-consumer migration instructions