{
"schema_version": "actionist.decision-ledger.v1",
"part": "P09",
"part_title": "Data and state plane",
"run_id": "2026-08-27-sprint-1-fable",
"lane": "S1-L4",
"boundary": {
"research_only": true,
"implementation_authorized": false,
"execution_status": "UNEXECUTED",
"admission_status": "NOT_ADMITTED",
"admitted_blocks": 0,
"no_benchmark_run": true
},
"decision_table_state_ownership": {
"note": "The unit of decision is the state class, not the product. Owner is the single authority for schema, migration and write authority.",
"rows": [
{
"state_class": "owned_transactional",
"definition": "New business state Actionist writes for the client product",
"default_owner": "actionist_host",
"default_binding": "postgres_via_typed_port",
"tenancy": "tenant_id column plus RLS enforced in-engine",
"migration_owner": "actionist",
"rationale": "A08 inferred, ecosystem fit, donor-stack compatibility, platform-owned policy removes the LLM-authored-RLS class",
"confidence": "inferred",
"falsifier": "Loop 2 shows typed-port overhead exceeds direct access value for the pilot workload"
},
{
"state_class": "donor_owned_intact_service",
"definition": "State inside a mature donor kept as a running service",
"default_owner": "donor",
"default_binding": "donor_native_store_plus_api",
"tenancy": "donor-native, host maps canonical to donor IDs explicitly",
"migration_owner": "donor_upstream",
"rationale": "The cost is the fork, not the database (SISOCRM observed). Upstream patches are the reason to stay downstream.",
"confidence": "observed_principle",
"falsifier": "A donor whose operational burden exceeds its fork-avoidance saving"
},
{
"state_class": "donor_owned_absorbed",
"definition": "Donor code copied and owned; storage repointed at host Postgres",
"default_owner": "actionist_host",
"default_binding": "host_postgres_own_schema_or_merged",
"tenancy": "host tenant model applies directly",
"migration_owner": "actionist_single_owner",
"rationale": "SISOCRM 2026-07-31 absorption decision; enables real foreign keys between donor and owned records",
"confidence": "observed_local_decision",
"falsifier": "Data-layer rewrite cost per donor measured in Loop 3 exceeds federation cost"
},
{
"state_class": "document_collaboration",
"definition": "Rich documents, blocks, whiteboards, realtime co-editing",
"default_owner": "donor_engine",
"default_binding": "engine_native_plus_fk_link_to_owned_records",
"tenancy": "engine-native, host holds the linkage",
"migration_owner": "engine",
"rationale": "Deeply engineered; liability to own. Absorption adds the FK the donor lacks (page belongs to a deal permanently, not by folder convention).",
"confidence": "inferred",
"falsifier": "Collaboration engine cannot expose a stable linkage key"
},
{
"state_class": "analytics_read_models",
"definition": "Cross-owner product views and rollups",
"default_owner": "actionist_host",
"default_binding": "event_fed_owned_tables",
"tenancy": "host tenant model",
"migration_owner": "actionist",
"rationale": "A12; the documented answer to losing cross-schema joins. Cross-owner mutation is never the mechanism.",
"confidence": "inferred",
"falsifier": "A workflow where eventual consistency measurably breaks the client outcome"
},
{
"state_class": "search_vector",
"definition": "Full-text and semantic retrieval",
"default_owner": "actionist_host",
"default_binding": "postgres_plus_pgvector_first",
"tenancy": "same tenant predicate as relational",
"migration_owner": "actionist",
"rationale": "Search and vector have converged commercially; one retrieval plane chosen for licence safety. Dedicated engines are licence-mined (Meilisearch MIT AND BUSL, Typesense GPL-3.0, Elasticsearch document-level security inside ELv2-only x-pack).",
"confidence": "inferred",
"falsifier": "Pilot retrieval quality or latency demands a dedicated engine"
},
{
"state_class": "files_objects",
"definition": "Uploads, generated artifacts, exports",
"default_owner": "actionist_host",
"default_binding": "file_object_port_impl_by_deployment_scale",
"tenancy": "naming and credential discipline only; no query semantics",
"migration_owner": "not_applicable",
"rationale": "Object storage OSS is effectively closed (MinIO AGPL and archived, Garage AGPL); SeaweedFS is Apache-2.0 by elimination, a named concentration risk. SISOCRM left this undecided even at single-VPS scale.",
"confidence": "unresolved",
"falsifier": "n/a - this row is an open decision, not a settled default"
},
{
"state_class": "events",
"definition": "Domain events, change feeds, integration triggers",
"default_owner": "actionist_host",
"default_binding": "transactional_outbox_then_bus",
"tenancy": "subject or topic hierarchy mirroring the tenant boundary",
"migration_owner": "actionist",
"rationale": "Outbox removes the enqueue-vs-commit race. NATS Apache-2.0 preferred over Redpanda BSL, whose Additional Use Grant excludes a Streaming or Queuing Service.",
"confidence": "inferred",
"falsifier": "Event volume or ordering needs exceed a Postgres-outbox substrate"
},
{
"state_class": "cache_ephemeral_local",
"definition": "Sessions, locks, derived caches, local tool state",
"default_owner": "capability_local",
"default_binding": "sqlite_libsql_or_memory",
"tenancy": "never a source of truth, so isolation is by lifetime",
"migration_owner": "capability",
"rationale": "SQLite is correct for bounded state owned end to end (SISOCRM observed)",
"confidence": "observed_principle",
"falsifier": "Cache state becomes authoritative anywhere, which is a defect not a variant"
}
]
},
"invariants": [
{
"id": "INV-P09-1",
"statement": "One authoritative owner per stateful resource, per table, per migration lineage, per file namespace, per event stream",
"state": "observed_principle",
"source": "A11 plus SISOCRM"
},
{
"id": "INV-P09-2",
"statement": "Workload shape precedes store choice",
"state": "proposed",
"falsifier": "80 percent of pilot state classes land on Postgres anyway and the port costs more than it saves"
},
{
"id": "INV-P09-3",
"statement": "Postgres is the default for new owned transactional state only, never imposed on donors",
"state": "inferred",
"source": "A08 accepted, A09 rejected"
},
{
"id": "INV-P09-4",
"statement": "Cross-owner product views are event-fed read models, never cross-owner table mutation",
"state": "inferred",
"source": "A12"
},
{
"id": "INV-P09-5",
"statement": "Capabilities consume typed data ports; ORM choice is adapter-local",
"state": "proposed",
"source": "A10 rejected as contract content"
},
{
"id": "INV-P09-6",
"statement": "Migration authority is release-scoped; no release without a migration rollback path",
"state": "proposed"
},
{
"id": "INV-P09-7",
"statement": "Tenant identity is a first-class key on every owned resource from the first migration, with per-tenant key derivation for credential-class data",
"state": "proposed",
"source": "OpenConnector retrofit cost demonstrated"
}
],
"open_decisions": [
{
"id": "P09-D-01",
"question": "One database, schemas, service stores or hybrid per client",
"status": "open",
"depends_on": "reuse shape mix in the chosen pilot",
"note": "SISOCRM answered schemas-per-donor for federation and one-schema for absorption; both were correct in context"
},
{
"id": "P09-D-02",
"question": "What operations must the v1 typed data port support",
"status": "open",
"candidate": "port family (crud, query, tx, files, search, events) with capability-declared needs rather than one wide port"
},
{
"id": "P09-D-03",
"question": "What mechanically prevents a capability writing a table it does not own",
"status": "open",
"candidates": [
"per-capability database roles with grants matching declared tables",
"RLS as the single authorization source binding host service and any API layer alike",
"static analysis of adapter declarations at admission"
],
"note": "raised by this lane, proposed as a measured Loop-2 dimension"
},
{
"id": "P09-D-04",
"question": "Object storage implementation",
"status": "open",
"note": "unresolved locally and license-constrained externally"
},
{
"id": "P09-D-05",
"question": "Host runtime language",
"status": "open",
"blocks": [
"River vs pg-boss",
"sqlc vs Drizzle"
],
"note": "ARCHITECTURE.md argues TypeScript control plane from a stratified 45-repo sample; not re-litigated here"
}
],
"rejected_here": [
{
"claim": "Force every donor onto Postgres and one ORM",
"reason": "A09 and A10 already rejected; externally confirmed that the cost is the fork"
},
{
"claim": "Adopt a multi-tenancy framework off the shelf for a TypeScript host",
"reason": "None exist with real adoption; the only maintained ones are language-locked (Laravel, Django). Tenant isolation is bespoke work Actionist must own and test."
},
{
"claim": "Trust a licence badge or the GitHub API SPDX field",
"reason": "Twelve-plus corrections this run; five surfaces where the API would mislead an automated pipeline"
}
],
"feeds_experiments": [
"knowledge/05-EXPERIMENT-ROADMAP.md Loop 0 contract surgery",
"Loop 2 data-plane bake-off",
"Loop 3 host absorption pilot"
]
}P09 · Host · Rendered from source
decision ledger
Data and state plane