contract topology · 01-topology.md · guide · card
System topology
Contextful runs connectors, stores Parquet files and serves queries under access controls. Operators use this page to choose a binary and deployment. Implementers use the component, dependency and coordination requirements below.
Deployment instructions name the executables, embedded libraries and storage services:
- Linux: native processes or Linux containers.
- macOS: native processes or Docker containers.
- Cloudflare: Workers, Linux Containers, D1 and R2.
- AWS: Fargate, Postgres and S3.
compose
Execution, storage, queries, access controls and the contracts these components share.
workspacetestOne Cargo workspace compiles execution, storage and query code. These components share one binary, package scope and release.read-pathtestStorage and query code manages the context store, catalog, queries, ranking, memory queries and bucket sync. A capability check admits each record before storage.run-pathtestExecution code manages the journal, scheduler, cursor commits, awakeables and journaled connector steps. Execution code owns no store or ranking.three-crossingstestExecution, storage and query code share exactly three contracts: the connector interface world, columnar-part-plus-manifest layout and capability-token format. No general internal API connects these components.crossing-versionitemEach shared contract has a version. Execution, storage and query code run its conformance suite. Adding a contract member requires a security review.undeclared-crossingtestAn execution crate depends on storage or query crates only through crates implementing the three shared contracts.crate-graphchecks each dependency.run-path-droppabletestA build without the execution engine, scheduler or component host reads data written by a full-profile build. Both builds use the same part-and-manifest layout and connector contract.cli-binarytestThe runtime ships one command binary,contextful, and one package scope,@contextful/*; release assurance followssuite. The smallest deployable build is a profile, not a crate.mediationtestEach function returning or releasing stored rows requires an access-control admission value (row-token). A call omitting that value fails to compile. Access controls have no disable option.enforcement-spantestExecution enforces capability allowlists and records journal entries. Storage and queries enforce statement guards, visibility semi-joins, row and column restrictions, masking and the audit chain.semantic-layertestAccess controls do not interpret content. Retrieval, memory synthesis, the operator console and inference placement interpret content and access enforcement through the three shared contracts alone.one-treetestLocal and clustered deployments use one source tree. Single-node and edge deployments use no external queue, cache or coordination process. Multi-node deployments add one shared database.queue-extensiontestWith aqueuerole, the target carriesreceivebatches, object-arrival keys and post-commit wake-ups through theQueueport. Without that role, the target uses the local spool. Only the engine writes the store.queue-sqstestWithCONTEXTFUL_QUEUE_URLset,receivebatches and object-arrival keys travel through that SQS standard queue, each request signed with Signature Version 4 underrole-credentials; post-commit wake-ups stay in the local spool.Why
because theawstarget’s queue role is an SQS standard queue, and its task holds a workload role rather than a key pairqueue-sqs-absenttestA build linking no SQS adapter, givenCONTEXTFUL_QUEUE_URL, refuses each queue use ascapability-absentand never falls back to the local spool.Why
because a batch queued where no consumer of the configured queue reads it is a batch lostqueue-sqs-waittestA receive long-polls the SQS queue for up to 20 s on its first call and waits on none after, stopping at the first empty answer.Why
because a long poll answers an idle queue with one request rather than many, and later calls drain what already waitsqueue-sqs-batchtestOne SQS receive call asks for at most 10 messages.Why
because SQS answers no more per callqueue-sqs-visibilitytestA received SQS message stays hidden for 300 s; a consumer acknowledges it by deleting its receipt handle, and one left undeleted redelivers after that window.Why
because a batch’s landing and push finish inside it, and a consumer that died must hand its messages to the nextqueue-sqs-captestAn SQS message carries at most 1 MiB; areceivebody past it spills underreceive-queued-spillto the store’s[sync]bucket beneath its prefix’squeue/spill/.Why
because SQS refuses a larger message, and the store’s bucket is the one object store every shape with a queue role already writesqueue-object-notificationtestAn S3 event notification on the queue delivers one object-arrival key perObjectCreatedrecord, its key URL-decoded; a message holding none, the test event among them, is deleted and delivers nothing.Why
because S3 notifies object creation in its own envelope, and a message no read consumes must not redeliver foreverconnector-pillartestA connector is an interface world run in a sandboxed component host that mediates every capability. A native connector, the first-party path for stateful sources, satisfies the same schema, record and cursor contract.open-storetestTable data sits in columnar parts with a JSON manifest and a rebuildable catalog on plain object storage, readable by standard SQL tooling. No proprietary container or opaque blob holds table data.inference-egresstestModel access leaves the process through one operator-configured endpoint speaking OpenAI-compatible HTTP. The one provider-shaped step serializes a tool’s parameter schema into the provider’s envelope.vendor-sdktestA model-vendor SDK or second outbound model path raisesVendorSdkLinkedin the dependency audit. The error names the crate and dependency.local-firsttestPipelines need no network beyond their sources, and bucket sync is opt-in per deployment. The engine makes no control-plane callback, no license check, and sends no telemetry to its authors.script-runtimetestA JavaScript runtime linked into any profile raisesScriptRuntimeLinked. The authoring surface is a build-time compiler emitting a serialized, content-hashed plan.memory-substratetestSynthesized memory uses ordinary store tables for episodes, facts, entities and preferences. The synthesis pipeline runs as an ordinary execution workflow using the same journal, cursor and retry machinery.two-spinestestThe run record describes writes. The request ledger describes reads. No third interface combines these records.
package
Rust package dependencies and the capabilities each compiled binary includes.
crate-maptestTwenty crates compose the workspace.contextful-cliis the binary and wires every adapter per profile by dependency injection.crate-map-drifttestAn unmappedcrates/package orcrate-mapcount differing from the## Shapestree raisesCrateMapDrift. The error names the package or both counts.Why
because a package added without a map entry otherwise passes every other gatedomain-cratetestcontextful-coreholds the pure domain types and the port traits every adapter implements, performs no I/O, and links into every profile.domain-impuritytestAn async runtime, component host, HTTP client or columnar-format implementation incontextful-coreraisesDomainCrateImpurity. The error names the dependency and enabling feature.dependency-directiontestA dependency edge fromcontextful-coretoward an adapter crate raisesTopologyDependencyInversion, naming both crates and the manifest line.profiletestThree profiles compile from the workspace, each a Cargo feature bundle selected at build time:contextful-edge,contextful-full,contextful-control. Each links the dependencies its role names and nothing else.fixed-at-buildtestNo run-time detection widens a running binary into another profile’s feature set. Build, release and automation tooling links into no profile.profile-leaktestA dependency outside a profile’s role raisesProfileRoleLeak, naming the profile and dependency path. Disallowed dependencies include component hosts, execution or evaluation in edge/control; SQL in control; build tooling in any profile.capability-absenttestA subcommand requiring an unlinked capability raisesProfileCapabilityAbsent, naming the subcommand and profile. The command performs no action. A subcommand absent from every profile is a usage error.version-profiletestcontextful --versionprints the workspace version and selected build profile. A build selecting no profile printsdevelopmentas its profile.edge-profiletestcontextful-edgesyncs parts and manifests from a bucket and serves read-only SQL queries. This profile links no scheduler, execution engine, script runtime or component host.edge-eligibilitytestThe edge profile is the one profile a function-class target hosts. Execution on such a deployment runs on a worker target.full-profiletestcontextful-fullincludes the execution engine, in-process scheduler, component host, SQL queries, transforms, full-text and vector sidecars, and tool server.control-profiletestcontextful-controlprovides self-hosted team configuration and operator identity. Apply produces canonical TOML. This profile alone links the CRDT library.control-team-statetestThe control profile stores ingest source, model and access-policy configuration in onelorodocument. Replicas merge concurrent edits. Apply uses this document as the editable configuration.control-identitytestThe control profile identifies an operator byoperator-attestationalone, refusing an apply without one, and keeps no account store.Why
because the console already signs every Admin mutation, and a second account concept is a second identity to provision and revokecanonical-tomltestApply sorts each table’s keys by byte order and places nested tables after values. The format-preserving editor chooses quoting and spacing, preserving comments. Applying canonicalization to canonical text changes no byte.Why
because equal configurations then claim identical bytes, and a comment is the operator’s record of why a value holdsapply-hometestcontextful-snapshotis the one home of the snapshot directorylocal-claimclaims into;contextful-engineandcontextful-controlboth call it, and it links no run-path package.component-hosttestBuilds that link a component host run component connectors: the full profile and container or worker deployments built from that profile.host-missingtestDispatching a component connector without a component host raisesComponentHostMissing, naming the connector and profile. No similarly named native source substitutes.crdt-leaktestThe CRDT library in the resolved dependency graph of the edge or full profile raisesProfileDependencyLeak, naming the profile and the path that pulled it. A daemon or replica reads materialized text.store-write-engine-freetestWithoutread,contextful-contextexcludesduckdb,libduckdb-sys,libsqlite3-sys, async runtimes, HTTP and TLS from normal dependencies on every target. A dependency reaching these raisesStoreWriteLinksEngine, naming the package and path.Why
because a host that lands, folds or scans a store serves no read and opens no connection, so an engine, runtime or network stack is code it never callstransport-optionaltestWithouttransport-ureq,contextful-outboundexcludesureq,hyper,reqwest,rustlsandcurlfrom normal dependencies. A dependency reaching these raisesTransportStackLinked, naming the dependency path.decode-network-freetestcontextful-decodereachingcontextful-outbound,ureq,hyper,reqwest,rustls,curlortokiothrough a normal dependency raisesDecodeLinksNetwork, naming the path that pulled it.exchange-optionaltestcontextful-policylinks the external-assertion stack,jsonwebtokenandrsa, only under its non-defaultexchangefeature, which only the binary may enable, and only alongside wiringsurface. Anothercrates/package whose resolved graph reaches either raisesExchangeDependencyLeak, naming the path.Why
because an embedder admitting credentials with no identity provider then links no RSA code, andrsacarries a timing advisory with no patched releasesqlite-adaptertestcontextful-sqlitealone declares the SQLite binding as a normal dependency and enables no link feature itself; onlycontextful-cliturns on itsbundledfeature. Any other normal-dependency declaration or enablement raisesSqliteLinkForced, naming the manifest line.s3-sync-optionaltestWithouts3-sync,contextful-syncexcludesureq,hyper,reqwest,rustlsandcurlfrom normal dependencies. A dependency reaching these raisesSyncStackLinked, naming the dependency path.Why
because a host syncing through a filesystem bucket, or embedding the sync package without a bucket, links no network stackstore-sqlite-freetestcontextful-contextreachinglibsqlite3-systhrough a normal dependency, with its default features, on any target, raisesStoreLinksSqlite, naming the path that pulled it.interchange-wholetestThe edge profile links the columnar interchange crates whole, as the full profile does; no slimmed interchange build exists.Why
because a slimmed build forks the format code an edge replica reads with from the code the full profile writes with
Each profile selects the capabilities of one contextful executable at compilation.
Deployment diagrams show where that executable runs: directly on Linux or macOS, or inside a Linux container.
unsettled: Does the edge profile pull native connectors through an execution core that links no scheduler? owner: topology affects: topology.package
deploy
Deployment configurations, provider services and comparison against the reference deployment.
unsupported-shapetestA target profile claiming a shape its provider does not provide raisesTargetShapeUnsupported, naming the shape and the missing primitive.wall-clock-captestA target capping per-invocation wall clock at 15 min hosts neither a first-time backfill nor a component connector, and its target profile records both exclusions.paritytestOne declaration produces byte-identical table parts on every control-plane target;contextful-ci deploy paritycompares each target’s object-store parts with the reference target’s.parity-divergencetestA byte difference from the reference output raisesParityDivergence, naming the target, the table and the first differing part.
Provider configurations live under spec/targets/; corpus.targets checks those files.
The generated deployment tables and diagrams identify processes, embedded
libraries and provider services. Diagram groups distinguish process, container and operating-system boundaries.
Native Linux deployment
The Linux reference map below places the scheduler, its run children, HTTP server and
embedded libraries on one host. Each service uses the same project directory, including
contextful.toml, applied control snapshots, execution files and corpus files. The
directory persists across process restarts; the optional console has its own Node.js
process and configured Rust HTTP endpoint.
The examples assume an existing project with an applied snapshot, readable issuer.key,
independently configured issuer public-key pins, and the source credentials its declaration
names. The scheduler and HTTP server inherit that environment. contextful-local is the
example token audience; 4 is the example HTTP concurrency setting. From that project’s
directory, two terminals or two service-manager units run:
contextful pipeline serve --issuer-key issuer.key
contextful serve --http 127.0.0.1:8080 --issuer-key issuer.key \
--audience contextful-local --max-in-flight 4
Linux Docker Engine also runs the image and container commands below, sharing the host’s Linux kernel. Docker Desktop on macOS adds the Linux VM shown in the Docker map.
macOS without Docker
Apple Silicon uses the aarch64-apple-darwin executable; Intel uses
x86_64-apple-darwin. From the repository root, a native source installation selects
the full profile:
cargo install --locked --path crates/contextful-cli \
--no-default-features --features contextful-full
The project’s two startup commands above run directly as macOS processes. Wasmtime,
Tokio, DuckDB and SQLite stay embedded in those processes. For managed startup, launchd supervises the scheduler and HTTP server.
Project state sits on the Mac’s filesystem. This path uses the Darwin binary and a native
Rust/C++ build toolchain, with no Linux VM or Docker daemon. The optional Node.js console
uses http://127.0.0.1:8080 as its configured store endpoint.
macOS with Docker
Docker Desktop supplies a Linux VM; its Docker Engine runs the same Linux image a Linux server uses. The Rust processes and embedded libraries run inside containers in that VM. The browser and optional native Node.js console reach a published localhost port.
The image build runs from the repository root. Apple Silicon selects ARM64 explicitly:
docker build --platform linux/arm64 --build-arg PLATFORM=linux/arm64 \
--build-arg RUST_TARGET=aarch64-unknown-linux-musl -t contextful:local .
Intel Macs select AMD64; this also selects an emulated AMD64 build on Apple Silicon:
docker build --platform linux/amd64 --build-arg PLATFORM=linux/amd64 \
--build-arg RUST_TARGET=x86_64-unknown-linux-musl -t contextful:local .
The Dockerfile’s PLATFORM and RUST_TARGET arguments select matching builder and
executable architectures. --platform alone leaves the Dockerfile’s AMD64 defaults in
place. Emulation adds translation cost; an ARM64 image matches an Apple Silicon Linux VM.
From the existing project directory, these commands use a shared bind mount at /data.
runtime.env supplies the configured public-key pins and source credentials through
Docker’s --env-file; the issuer seed stays in issuer.key. The invoking non-root user’s
UID and GID keep writes owned by the project owner. Each terminal runs one container:
docker run --rm --init --name contextful-scheduler \
--user "$(id -u):$(id -g)" --env-file runtime.env \
--mount "type=bind,src=$PWD,dst=/data" \
contextful:local pipeline serve --issuer-key /data/issuer.key
docker run --rm --init --name contextful-http \
--user "$(id -u):$(id -g)" --env-file runtime.env \
--mount "type=bind,src=$PWD,dst=/data" -p 127.0.0.1:8080:8080 \
contextful:local serve --http 0.0.0.0:8080 --issuer-key /data/issuer.key \
--audience contextful-local --max-in-flight 4
The container listens on 0.0.0.0:8080; Docker publishes it at 127.0.0.1:8080 on
macOS. /data persists in the host project directory when --rm removes a container.
A Docker named volume stores project state when the operator copies configuration and
keys into that volume. Both containers mount the same volume. A project uses one
writer mode at a time: switching between native and Docker stops its scheduler and run
children before reusing the directory.
Declared file and component paths resolve inside the container; files outside /data
need an explicit mount. A source or model endpoint running on the Mac uses
host.docker.internal from Docker Desktop containers. Container localhost names the
container itself; the native console uses the published Mac loopback port.
The Linux VM contains the containers; the project directory remains on macOS through Docker’s file sharing.
unsettled: Which role hosts heavy compute on a provider exposing neither a container primitive nor a long-running function? owner: topology affects: topology.deploy
Deployment targets
AWS
VPC-bound and regulated deployments, and teams already operating on AWS.
The scheduled-fargate target runs every process as the native Linux contextful-full executable in Fargate containers. EventBridge Scheduler starts contextful pipeline serve --cycle as an ECS task on a fixed rate; the cycle takes the cadence lease, runs every due unit and exits. A long-running contextful serve behind an Application Load Balancer answers queries and pulls the bucket every 30 s. The query process embeds DuckDB; component execution embeds Wasmtime. RDS Postgres supplies the shared catalog, the engine journal in it makes every run durable and resumable, and S3 holds corpus objects. The fargate-daemon alternative keeps scheduling in one always-on task for sub-minute cadences.
The OpenTofu configuration under apps/aws-fargate/ provisions either shape: a private VPC with its service endpoints, RDS Postgres, the S3 corpus bucket, the SQS queue with its dead-letter queue and object-arrival notification, the ECS cluster, the cycle task or daemon service, and the serve service behind the load balancer.
| Role | scheduled-fargate (default) | fargate-daemon |
|---|---|---|
| cron tick | EventBridge Scheduler | in-process scheduler |
| reconciler | pipeline serve --cycle in the scheduled Fargate task | in-process reconcile loop |
| durable orchestrator | engine journal in the catalog | engine journal in the catalog |
| single-writer catalog | RDS Postgres via pg-catalog | RDS Postgres via pg-catalog |
| object store | S3 | S3 |
| hot-path query face | ALB → contextful serve on Fargate | ALB → contextful serve on Fargate |
| heavy compute | in-process component host of the scheduled task | in-process component host |
| secrets | Secrets Manager adapter through the task role | Secrets Manager adapter through the task role |
| realtime run projection | Fargate-hosted run projection | — |
| queue | SQS standard queue; S3 event notifications to SQS for object arrival | SQS standard queue; S3 event notifications to SQS for object arrival |
| kind · profiles | orchestrated · full | daemon · full |
| wall-clock cap · excluded | none · nothing | none · nothing |
The scheduled-fargate runtime and process boundaries:
Cloudflare
Cloudflare deployments with a Linux Container, D1 execution metadata and R2 objects.
Cloudflare's default deployment configuration is worker-container-d1. The gateway and the reconciler run as TypeScript in Workers' V8 isolates. Cron Triggers schedule work; Workflows coordinates execution. A Durable Object controls the Linux Container's lifecycle. The Container runs the native contextful-full executable with embedded DuckDB and Wasmtime. Workers and Workflows call the Container through requests.
The Rust processes use CONTEXTFUL_STORAGE_PROFILE=cloudflare-d1. Requests to the private contextful.d1 host call a Worker binding handler. The handler reads and writes execution metadata in D1 and execution blobs in R2. A separate R2 bucket stores corpus parts and manifests through its S3-compatible API. Files inside the Container are a replaceable working copy.
Cloudflare Queues carries receive batches and object-arrival notifications. The Rust engine writes the store. A separate Durable Object manages hibernating WebSocket connections for run updates. Workers Secrets supplies credentials to configured bindings. The optional console server runs on a Node.js host. The React assets provide a static browser preview without that server.
The worker-container-d1-basin configuration adds an optional read face for exported audiences. The Container commits each audience's Iceberg export to Basin Catalog, the Iceberg REST catalog of an R2 bucket that holds that audience alone. The gateway sends a caller whose credential maps to an export namespace to Basin SQL over REST with that audience's read-only token, one Worker secret per audience bucket, and sends every other caller to the Container. Each export table runs with Basin compaction disabled; Basin snapshot expiration removes superseded exports.
The example under apps/cloudflare-execution/ exposes authenticated POST /conformance. Its Container runs contextful-conformance against the packaged Linux contextful binary. This endpoint tests execution storage; it serves no query or orchestration routes. status.md records clause implementation evidence.
| Role | worker-container-d1 (default) | worker-container-d1-basin |
|---|---|---|
| cron tick | Cron Trigger | Cron Trigger |
| reconciler | Worker | Worker |
| durable orchestrator | Workflows | Workflows |
| single-writer catalog | D1 through a private Worker outbound binding | D1 through a private Worker outbound binding |
| object store | R2 | R2, with one Basin Catalog bucket per exported audience |
| hot-path query face | gateway → warm contextful-full Container | gateway → warm contextful-full Container, or Basin SQL for an exported audience |
| heavy compute | Container with cloudflare-d1 storage profile | Container with cloudflare-d1 storage profile, which also runs Iceberg exports |
| secrets | Workers Secrets | Workers Secrets |
| realtime run projection | Durable Object with a hibernating WebSocket | Durable Object with a hibernating WebSocket |
| queue | Cloudflare Queues with a pull consumer; R2 event notifications for object arrival | Cloudflare Queues with a pull consumer; R2 event notifications for object arrival |
| kind · profiles | orchestrated · full | orchestrated · full |
| wall-clock cap · excluded | none · nothing | none · nothing |
The worker-container-d1 runtime and process boundaries:
Linux (reference)
Self-hosted Linux servers, development, and air-gapped single-node deployments.
The contextful-full Linux executable runs natively on x86-64 or ARM64, using the corresponding x86_64-unknown-linux-musl or aarch64-unknown-linux-musl release artifact. A long-lived contextful pipeline serve process owns the scheduler and launches the same executable as contextful pipeline run or contextful job fire children. A separate contextful serve process answers MCP and HTTP requests. A service manager such as systemd supervises these processes with the same project working directory and configured environment.
Wasmtime executes WebAssembly component connectors inside a run child; its Tokio runtime is embedded there. DuckDB executes SQL inside the process serving reads. SQLite is an embedded library for execution state, with files such as machine.sqlite; Parquet parts, manifests and control snapshots live under the project's configured filesystem root. These libraries have no separate service process.
The optional operator console is a separate Node.js 22 process, node apps/console/dist/server.js --http <host:port>, and reaches the Rust HTTP server through its configured store endpoint. The browser executes the React client. The local target uses the filesystem receive spool; bucket sync adds an explicitly configured S3-compatible endpoint.
The repository's Dockerfile packages the Linux executable in a shell-free, non-root image with /data as its project volume. Separate scheduler and HTTP containers share that persistent volume. On macOS, a Darwin executable runs the same process layout directly; Docker runs the Linux image inside a Linux VM. The topology's macOS sections give both execution paths.
| Role | process (default) |
|---|---|
| cron tick | in-process scheduler |
| reconciler | in-process reconcile loop |
| durable orchestrator | engine journal in the catalog |
| single-writer catalog | local catalog file with embedded SQLite execution stores |
| object store | local filesystem under ./.contextful/, optional S3-compatible sync |
| hot-path query face | native contextful serve process with embedded DuckDB |
| heavy compute | native contextful pipeline run or job fire child; embedded Wasmtime for components |
| secrets | environment and configured secret providers |
| realtime run projection | in-process WebSocket |
| queue | — |
| kind · profiles | daemon · full |
| wall-clock cap · excluded | none · nothing |
The process runtime and process boundaries:
publish-hostname
Hostname access policies, deployment checks and requests through the routing service to the query container.
posture-mismatchtestAn unreachable hostname or response outside its declared access policy raisesHostnamePostureMismatch, naming the hostname, policy and observed response.contextful-ci deploy probeexits nonzero.probe-tabletestBefore probing hostnames, the deploy tool compares descriptor and probe-table entries. An entry missing from either set raisesProbeTableDrift, naming the hostname and missing side.empty-probetestAn absent descriptor directory, absent probe table or empty hostname set raisesProbeSetEmpty, naming the path. A passing probe checks at least one hostname.Why
because a probe that checks nothing and exits zero reads as a proven access policy to the pipeline that invoked it.unknown-fieldtestAn unmodelled descriptor property raisesDescriptorUnknownField, naming the key and contract version. The decoder rejects the descriptor.Why
P1unconfigured-gatewaytestA gateway with no store configuration answers503on every route and raisesGatewayUnconfigured.Why
P3issuer-keytestThe HTTP server requires a verification key that resolves and parses. An unusable key raisesIssuerKeyUnusable, naming the input, and prevents startup. The server generates no replacement key.Why
P3container-readinesstestA retrieval container reaches readiness within 8 s of a cold start, before its snapshot-set hydration completes.
coordinate
Conditional metadata updates, writer leases and the databases that store coordination state.
primitivetestCoordination rests on one property: a linearizable conditional write. No component depends on a stronger one, and the tree ships no consensus implementation or external coordination service.inventoryitemSingle-writer operations comprise lease-row updates, cursor compare-and-swap,pointer-commit, lease acquisition, leased pipeline commit-log entries, bucket manifest commits and configuration version claims. No other operation needs a single writer.lease-rowtestA lease key names a pipeline, source partition, table compaction or deployment cadence. One conditional update assigns the row’s holder, expiry instant and fence.catalog-clocktestThe catalog evaluates a lease row’s expiry against its own clock, never a caller-supplied instant.fence-advancestestEach acquisition of a lease row increments its fence, and release keeps the fence, so the fence never repeats for a key.fenced-committestA commit under a lease is a conditional write predicated on the holder’s fence; a predicate matching nothing isstale-fence.cursor-castestA cursor compare-and-swap is one conditional update predicated on the stored version, read back by its affected-row count.cadence-lease-ttltestThe catalog grants a cadence lease for 90 s.cadence-lease-renewaltestThe reconciler renews a cadence lease every 30 s while its applied document schedules a dispatchable unit.cadence-fallbacktestA deployment’s fallback cron fires only after taking the cadence lease itself, so one fire holds the current fence.catalog-porttestEngine code accesses catalog backends through theCatalogtrait without naming a backend.contextful-cliselects the backend through dependency injection.backendstestSingle-node self-hosting uses a local catalog file. Clusters use Postgres throughpg-catalog. Cloudflare execution uses D1 throughd1-catalog.catalog-urltestWithpg-catalog,CONTEXTFUL_CATALOG_URLselects the named Postgres catalog instead of the local file. Withoutpg-catalog, setting this variable raisescapability-absent.Why
because a daemon falling back to its own file while its peers share the database holds leases no peer seespostgres-transporttestBeyond loopback or Unix sockets, PostgreSQL requires TLS with WebPKI roots and hostname verification. Disabling TLS there raisesDatabaseTransportUnprotectedbefore DNS resolution. Preferred TLS mode permits no plaintext fallback there.Why
because remote execution state and database credentials cross an untrusted network boundarypostgres-ca-bundletestsslrootcert=<path>in the connection string names a PEM bundle whose roots replace the WebPKI roots ofpostgres-transportfor that connection; hostname verification still applies.Why
because a managed database such as RDS chains its server certificate to a provider CA that WebPKI omitspostgres-ca-unreadabletestAnsslrootcertpath that is unreadable or holds no certificate raisesDatabaseCaBundleUnreadable, naming the path, before connecting.Why
because a silent fallback to WebPKI roots fails later as an unknown issuer and hides the misconfigured pathpostgres-verify-modestestsslmode=verify-caandsslmode=verify-fullparse and require TLS on every host, verifying the server’s chain and hostname aspostgres-transportdoes.postgres-system-rootstestsslrootcert=systemselects the WebPKI roots ofpostgres-transport, as libpq’ssystemvalue does, and reads no file.weak-backendtestA catalog backend whose conditional update is not linearizable refuses at open withweak-conditional-backend.cluster-availabilitytestCluster availability is the shared database’s availability. The engine adds no replication and no failover protocol between daemons.air-gaptestSingle-node and edge deployments reach no process outside themselves for coordination, and run air-gapped with only their sources reachable.no-clustered-catalogtestClustered availability behind theCatalogport comes from Postgres alone; the tree ships no self-contained clustered catalog.Why
because a separate clustered catalog duplicates Postgres replication
unsettled: Does an encrypted store seal its rows in the Postgres catalog, or keep refusing that pairing at open? owner: topology affects: topology.coordinate
bound-application
Responsibilities of the engine and applications that use the engine.
domain-neutraltestThe engine processes declared data, schemas, policies, time bounds and capabilities. Consumer configuration, connectors, components or explicitly selected evaluation profiles supply domain meanings, business rules and sector-specific metrics.restated-casetestA case with neither a generated artifact nor a callable route stays with the application, which restates the engine rule at its site undermirror-exemption.Why
because a restatement naming its clause id surfaces when the rule changes, and an adapter for one case costs more than it guards
Shapes
The workspace:
crates/
contextful-core/ pure domain types and ports, no I/O
contextful-fs/ exclusive-create publish the store and the journal share
contextful-engine/ runner, journal, catalog, awakeables, cancellation
contextful-outbound/ mediated client, credential resolution, quota metering, inference
contextful-wasm/ sandboxed component host
contextful-connectors/ native sources
contextful-decode/ record decoders: JSON, JSON Lines, delimited text, workbook
contextful-context/ table parts, query face, snapshot commit, retrieval
contextful-memory/ deterministic memory synthesis
contextful-sqlite/ the SQLite adapter behind the catalog and run store ports
contextful-pg/ the Postgres adapter behind the catalog port
contextful-d1/ the D1 adapter behind the execution store ports
contextful-policy/ predicates, masks, redaction, zones, audit chain, token trait
contextful-sync/ bucket push and pull
contextful-agent/ tool server and connector scaffolder
contextful-eval/ read-path quality harness
contextful-snapshot/ control snapshot directory: versions, pointer, drafts, receipts
contextful-control/ control plane; the one CRDT consumer
contextful-cli/ the `contextful` binary; dependency injection per profile
acceptance/ black-box suite driving the built binary
contextful.toml the deployment declaration, identical across providers
The three profiles as feature bundles:
[features]
contextful-edge = ["read-plane", "transport-ureq", "s3-sync"]
contextful-full = ["data-plane", "transport-ureq", "s3-sync", "drive", "component-host", "pg-catalog", "d1-catalog", "aws-secrets-manager", "aws-sqs", "iceberg-export", "otlp"]
contextful-control = ["dep:contextful-control"]
A published-hostname descriptor:
{ "contract": 1, "worker": "contextful-docs", "hostname": "docs.example.org",
"gate": { "_tag": "public", "acknowledged": true } }
A lease row and a fenced cursor commit, as the Catalog port issues them:
-- acquire: the catalog's clock decides expiry; the fence only grows
UPDATE leases SET holder = :me, expires_at = now() + :ttl, fence = fence + 1
WHERE key = :key AND (holder IS NULL OR expires_at < now());
-- commit: one conditional update, predicated on version and on the holder's fence
UPDATE cursors SET position = :pos, version = version + 1
WHERE stream = :stream AND version = :v
AND EXISTS (SELECT 1 FROM leases WHERE key = :key AND fence = :fence);