Cloud Todo Boundary
Propodus is the hosted authentication and authorization boundary; Dactyl is
the physical storage boundary. They are not interchangeable. repo.backend = "cloud" selects the cloud backend: Decapod authenticates through Propodus,
opens Dactyl with a versioned storage context, and never silently falls back to
local SQLite.
Dactyl storage contract
The Decapod bridge constructs local Dactyl SQLite or cloud Supabase HTTP
routes by default; Neon HTTP remains an explicitly selected alternative.
It does not set or trust ambient DATASTORE, DATASTORE_ROUTE, or
DATASTORE_TOKEN during connection construction. The cloud route receives
an opaque machine-session bearer and the versioned repository context.
Individual SQL operations do not select a provider or interpret authorization.
The Supabase HTTP capability, enabled in standard builds, is described in
Supabase consumer validation. Decapod consumes the
published dactyl-db 0.11.1 registry release containing the client reviewed in
Dactyl #92. The committed
lockfile supplies its registry checksum; registry publication is not hosted
service proof.
Cloud selects Supabase unless DECAPOD_CLOUD_DATASTORE=neon is set. Local
SQLite behavior is unchanged. Supabase still requires a configured compatible
authenticated service endpoint; no production deployment is implied.
| Operation | Dactyl request | Scope/authentication |
|---|---|---|
| Read/list | POST /query | SQL plus opaque versioned context |
| Add/claim/release/complete | POST /batch | ordered Dactyl operations plus context |
The bearer is sent in the HTTP Authorization header by Dactyl. The context
is forwarded as an opaque JSON object; Propodus resolves its authenticated
principal and repository authorization. Add, claim, release, and complete use
a Dactyl batch containing the conditional state write, the matching event
write, and a task observation. The event insert is conditioned on the state
transition marker. The service must also require a successful preceding
state mutation before writing an event, including when a stale request reuses
a timestamp marker; a timestamp alone is not authorization for an event.
The checked-in tests/cloud_dactyl_boundary.rs proof verifies /query, the
bearer header, the versioned context, and the absence of a per-query backend
field without contacting Vercel or Neon. The older Propodus fixture and
propodus_contract test remain compatibility coverage for the legacy
/api/todos client; they are not the active Decapod storage implementation.
The deployed onboarding/session shape is recorded separately in
tests/fixtures/propodus/onboarding-contract-v1.json: Decapod starts with the
canonical repo_id, prints/opens the one-time URL, polls status, consumes the
ready flow once, exchanges the returned code for a machine session, and
rotates that session through the refresh route. Credentials stay outside the
repository.
The command boundary is covered by tests/cloud_command_path.rs, which proves
that list, add, get, claim, release, and complete are routed through the
backend-neutral TodoStore adapter. Unsupported local-only operations return
an explicit error on the cloud backend.
The production-dispatch proof is tests/cloud_cli_boundary.rs; it uses a
mock Dactyl store factory and exercises the same run_todo_cli composition
used by the binary. It proves config discovery, canonical-origin validation,
credential preflight, list/add/get/show/claim/done routing, not-found behavior,
and the absence of local SQLite initialization for cloud todo commands.
The opt-in live proof is tests/propodus_live.rs. Run it only with
DECAPOD_PROPODUS_LIVE=1, DECAPOD_PROPODUS_API_URL,
DECAPOD_PROPODUS_ACCESS_TOKEN, and a
disposable DECAPOD_PROPODUS_DISPOSABLE_REPO_ID:
DECAPOD_PROPODUS_LIVE=1 \
DECAPOD_PROPODUS_API_URL=https://your-stable-propodus.example \
DECAPOD_PROPODUS_ACCESS_TOKEN=... \
DECAPOD_PROPODUS_DISPOSABLE_REPO_ID=example/decapod-live-deny \
cargo test --test propodus_live -- --ignored --nocapture
The proof creates one uniquely named todo in the canonical repository,
claims it, completes it, and verifies that the disposable repository receives
403 repository_not_authorized. It also verifies that an invalid bearer token
is rejected with a 401 authentication failure. The command-level proof also
uses two Decapod agents to verify shared visibility and rejects a fork before
any request is sent. It does not delete the sentinel because the v1 client
contract has no delete operation; remove it with Propodus operator tooling
after the run. The test is ignored by default, and the CI job is manual,
environment-protected, and gated by the DECAPOD_PROPODUS_LIVE repository
variable.
Propodus also uses 403 organization_seat_required when a valid GitHub bearer
token lacks the required organization seat for the canonical repository.
Backend-neutral storage context
Decapod's versioned core::backend::StorageContext is the handoff from
logical backend selection to a physical driver. A local context contains only
the repository-local SQLite route and has no organization, user, or cloud
repository fields. A remote context contains the opaque service route and the
logical repository scope derived from origin; it requires an authenticated
session bearer, but the bearer is memory-only and is omitted from serialized
context data.
The Dactyl bridge forwards the explicit route, versioned context envelope, and
opaque bearer without interpreting membership or authorization. The bearer
never enters ambient Dactyl environment variables. Context debug formatting
redacts the bearer and endpoint; serialization excludes the bearer.
The versioned context carries the target org/repo scope unchanged. Propodus remains
responsible for resolving the authenticated principal, organization membership,
and repository access. The Decapod bridge opens the canonical local
decapod.db directly through Dactyl's local adapter and opens the cloud route
through Dactyl's Supabase adapter by default (or explicit Neon); it does not create a snapshot or bundled
compatibility database. Existing SQLite state is inspected and migrated by
Decapod through the same Dactyl-backed facade. This keeps one canonical Dactyl
authority and prevents cloud operations from silently falling back to local
storage. See Decapod
#1254, Dactyl
#64, and Propodus
#79.
Credentials
Credentials are never read from .decapod/config.toml. Lookup precedence is:
- an explicit client credential;
DECAPOD_ACCESS_TOKENfor controlled development and CI use;- the selected endpoint-scoped machine session for Supabase, or the legacy
~/.local/share/decapod/session_token.jsononly for explicit Neon.
cloud status checks the same selected session store without network I/O.
Run decapod init --backend cloud as the only human-facing cloud setup step.
It starts or resumes the repository-bound browser handoff through Decapod's
baked-in Propodus endpoint. Agents acquire custody with decapod session acquire, which reuses or refreshes the machine-local session without asking
for another human action. Use decapod cloud status only to check whether a
bearer is configured without printing the token. The deprecated cloud login
compatibility alias remains for existing scripts and is not part of the setup
flow.
Interactive terminals open the URL when a browser launcher is available and
always print it as a fallback; headless callers receive a bounded resume
instruction. A completed exchange is stored machine-locally with restrictive
permissions and expired sessions refresh through the provider. Bearer tokens
are sent only in the Authorization header; Decapod never logs or writes
them to repository configuration, generated governance, URLs, or commits.
Repeatable dogfood setup
From a fresh checkout of the canonical repository:
- Run
decapod init --backend cloud --proofand confirm.decapod/config.tomlcontains only the backend selectionrepo.backend = "cloud"for cloud composition. The Propodus endpoint and provider are binary-owned; no service URL or project identifier is stored in project configuration. - Ensure
originis an unambiguous GitHub remote. Decapod derives the canonical owner/name from it and sends that binding to the provider; the provider decides whether the authenticated session may use the repository. - Run
decapod todo list,add,get,show,claim, ordone. If no machine session exists, first completedecapod init --backend cloud; the initialized machine state then resumes the one-time browser handoff on the next invocation. A controlledDECAPOD_ACCESS_TOKENremains available for protected proofs. - Cloud todo commands use the same
TodoStorecommand boundary as local todo commands, but compose Dactyl storage with the Propodus-authenticated context instead of local SQLite. They do not acquire the local agent session or create/migrate the local todo database. Missing credentials or a canonical GitHub remote fail closed and never fall back to SQLite.
Repository identity
The cloud backend derives a canonical owner/name binding from the origin remote
and rejects non-GitHub or ambiguous remotes. Forks remain distinct identities
and are passed to the provider for authorization; Decapod does not maintain a
repo allowlist or accept a project-configured repository identifier.
Current governance limits
The cloud Dactyl todo slice supports repo-scoped list/add/get/claim/release/
complete operations. get and show use keyed reads through Dactyl rather
than list-and-filter. Add, claim, release, and complete each use one Dactyl
atomic batch for the conditional task transition, matching event, and final
task observation. A compatible service must enforce that a zero-row task
transition cannot create an event, even if a timestamp marker matches an older
operation. Client checks of returned counts occur after the remote transaction
and cannot alone enforce rollback. The Supabase service proof tests this
server-side requirement; timestamps alone are not compare-and-swap authority. A missing item returns status = "not_found".
todo done --validated is intentionally rejected because v1 has no proof
capture or verification-artifact contract. This is an explicit unsupported
boundary, not full remote governance completion; a future proof-contract issue
must define the service-side evidence model before Decapod can compose it;
see Decapod issue #1038.
Delivery boundary
Local SQLite maintenance
Local operators can inspect and maintain the canonical store explicitly:
decapod data db verify
decapod data db backup --destination /same-directory-or-approved-backup/decapod.backup.db
decapod data db recover --preserve-original-at /same-directory/decapod.before-recovery.db
verify is read-only and calls Dactyl v0.10.0's typed native integrity
verification. backup uses Dactyl's SQLite online-backup contract, including
WAL/SHM correctness, and publishes a standalone destination after verification.
recover is an explicit operator action only: it invokes Dactyl's logical
dump/reload workflow, preserves the original at the requested unused sibling
path, and atomically replaces the active database only after the replacement
passes verification. Dactyl reports rollback or recovery failures without
silently repairing the store. The recovered database uses DELETE journal mode
and preserves application rows, schema, user_version, and application_id.
The canonical Decapod sidecar lock is held for each operation. Recovery also requires all cooperating Decapod writers and same-process Dactyl connections to be quiesced; backup requires normal cooperating-client coordination. The archive must not already exist and must be on the same filesystem where Dactyl can perform its atomic replacement. The sidecar does not coordinate arbitrary external SQLite writers or make unreliable network/container mounts safe. Lock contention, unavailable runtimes, malformed/corrupt stores, unsupported capabilities, and recovery/rollback failures remain explicit diagnostic outcomes. Decapod never runs repair during startup, validation, event append, or connection creation.
This Decapod slice activates the explicit cloud todo command path,
remote-derived repository identity, Propodus onboarding/session exchange,
machine-local refresh, Dactyl 0.10.0 adapter-level command and event-atomicity proof, and a wire-level
/query proof without moving hosted authentication, repository authorization,
persistence, or deployment into Decapod. Local mode continues to use the same
backend-neutral todo command boundary, with physical canonical storage owned
by Dactyl. Live Neon/Vercel availability, Propodus/Dactyl service deployment,
cross-organization isolation, schema/migration parity, and hosted event
atomicity remain deployment-dependent proof gates; the client-side batch
contract and local rollback proof do not claim those hosted properties.
For interrupted cloud creation and auth-blocked custody, see Resuming cloud todo creation. A returned operation token is a retry identity, never an authorization grant or local task-store fallback.