Unclassified

Specification Amendments — Implementation Corrections

Status: CANONICAL AMENDMENT
Date: 2026-08-06
Authority: Supersedes conflicting statements in all prior architecture documents
Scope: 15 required corrections to make the frozen architecture internally consistent, compatible with the Cloudflare execution model, and suitable as the implementation baseline


Verdict

The core model is sound:

  • Neon is the control-plane authority.
  • Each tenant receives an isolated D1 operational Spine.
  • The Gateway derives authority server-side.
  • The Workbench consumes projections only.
  • AI proposes; Governance decides; Execution acts; Reconciliation verifies.
  • Provider acceptance is never presented as completed work.

The following corrections are incorporated as implementation-final amendments.


Amendment 1: Per-Tenant D1 Routing Mechanism

Problem

Prior statements implied that platform-gateway resolves tenant_id through KV and dynamically binds the corresponding D1 database. A standard Worker accesses D1 through bindings attached to its deployed environment. It cannot turn a D1 database ID retrieved from KV into a new native D1Database binding during the request.

Correction

Cloudflare's supported dynamic pattern is a Workers for Platforms dispatch namespace: the Gateway dispatches to a tenant runtime Worker, and that Worker has its tenant D1 database statically attached as a binding.

Frozen topology:

Request
  ↓
Platform Gateway
  ├── validates identity
  ├── derives tenant_id
  ├── reads tenant runtime routing record
  └── dispatches by runtime_script_name
          ↓
Tenant Runtime Worker
  ├── static env.SPINE_DB → tenant D1
  ├── tenant runtime configuration
  └── service bindings → shared platform services

Gateway dispatch pattern:

const tenantRuntime = env.TENANT_DISPATCHER.get(runtimeScriptName);
return tenantRuntime.fetch(sanitizedRequest);

tenant_runtime_registry schema (Neon control plane):

FieldTypePurpose
tenant_idTEXT PKTenant identifier
runtime_script_nameTEXT NOT NULL UNIQUEWorkers for Platforms script name
dispatch_namespaceTEXT NOT NULLDispatch namespace identifier
d1_database_idTEXT NOT NULLCloudflare D1 database resource ID
runtime_versionTEXT NOT NULLDeployed runtime version
deployment_statusTEXT NOT NULLactive / deploying / draining / suspended
region_or_jurisdictionTEXTData residency constraint if applicable
updated_atTIMESTAMPTZ NOT NULL DEFAULT now()Last modification

edge_routing_key equals runtime_script_name. The binding name inside every tenant Worker is stable (SPINE_DB); only the attached resource differs.


Amendment 2: Isolation Claim Correction

Problem

Prior wording claimed "cryptographic and physical boundary isolation." Cloudflare documents that D1 data is encrypted at rest and supports database-per-tenant scale-out, but does not establish that each tenant receives dedicated physical hardware or a tenant-specific encryption key.

Correction

Replace all instances of "cryptographic and physical boundary isolation" with:

Independent database-resource isolation with separate D1 namespaces, independent lifecycle controls, and encryption at rest.

The correct guarantee is:

Cross-tenant SQL access is eliminated from normal application query paths because each tenant runtime is bound to a different D1 database resource.

Authorization remains necessary because users and workspaces within the same tenant still require logical isolation.


Amendment 3: D1 Fleet Capacity Policy

Problem

The database-per-tenant decision is valid but has explicit capacity boundaries. Current paid-plan limits include up to 50,000 databases per account, a 10 GB maximum per database, and a total account storage ceiling. No capacity policy was defined.

Correction

D1 Fleet Capacity Policy:

MetricWarning thresholdHard threshold
Tenant database size7 GB9 GB (migration required)
Account database count40,000 databases47,500 databases

Overflow strategy:

  • Additional Cloudflare account / account shard
  • Migration of high-volume tenant to a compatible Spine adapter

Routing fields:

FieldPurpose
control_plane_account_idCloudflare account owning this tenant's resources
dispatch_namespaceWorkers for Platforms namespace
runtime_script_nameTenant Worker script identity
d1_database_idD1 database resource

The logical Spine contract must remain unchanged when a tenant moves between physical storage adapters.


Amendment 4: Catalog Versioning Schemas

Problem

The current schema defines connector_catalog and plugin_catalog with PRIMARY KEY connector_key / PRIMARY KEY plugin_key. A primary key on the unversioned key permits only one row per connector or plugin, preventing version management.

Additionally, BIGINT AUTOINCREMENT is Neon-incompatible syntax.

Correction

Use separate identity and release tables:

-- Connector identity
CREATE TABLE connector_catalog (
    connector_key TEXT PRIMARY KEY,
    display_name TEXT NOT NULL,
    category TEXT NOT NULL,
    developer_id TEXT NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Connector versions
CREATE TABLE connector_versions (
    connector_key TEXT NOT NULL REFERENCES connector_catalog(connector_key),
    version TEXT NOT NULL,
    auth_type TEXT NOT NULL,
    spec_json JSONB NOT NULL,
    is_published BOOLEAN NOT NULL DEFAULT false,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (connector_key, version)
);

Apply the same pattern to plugin_catalog / plugin_versions and optionally mcp_tools / mcp_tool_versions.

Replace all instances of BIGINT AUTOINCREMENT with PostgreSQL-native syntax:

BIGINT GENERATED ALWAYS AS IDENTITY

Amendment 5: Idempotency Persistence

Problem

Cloudflare Queues provides at-least-once delivery, meaning duplicate delivery is possible. The specification relies on the command interface for idempotency without defining durable deduplication state.

Correction

Every event consumer and execution handler must be idempotent. Add an operational table to each tenant D1 Spine:

CREATE TABLE processed_commands (
    command_id TEXT PRIMARY KEY NOT NULL,
    idempotency_key TEXT NOT NULL,
    command_type TEXT NOT NULL,
    actor_id TEXT NOT NULL,
    payload_hash TEXT NOT NULL,
    status TEXT NOT NULL CHECK (
        status IN ('processing', 'completed', 'failed')
    ),
    result_ref TEXT,
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    completed_at TEXT
);

CREATE UNIQUE INDEX idx_processed_commands_idempotency
ON processed_commands(command_type, actor_id, idempotency_key);

A reused idempotency key with a different payload hash must be rejected as a conflict.


Amendment 6: Transactional Outbox

Problem

The requirement that audit or event-publishing failure stops a transaction cannot be guaranteed by publishing directly to an asynchronous queue after a database write.

Correction

Use a transactional outbox pattern:

D1 transaction
  ├── mutate domain record
  ├── create evidence/source link where required
  ├── append local audit event
  └── append outbox event
          ↓
Outbox publisher
          ↓
Cloudflare Queue
          ↓
Domain consumers

D1 batch operations execute as transactions and roll back the batch if a statement fails, making them suitable for atomic domain-write-plus-outbox insertion.

Outbox table (per-tenant D1):

CREATE TABLE event_outbox (
    event_id TEXT PRIMARY KEY NOT NULL,
    event_type TEXT NOT NULL,
    aggregate_type TEXT NOT NULL,
    aggregate_id TEXT NOT NULL,
    workspace_id TEXT,
    payload_json TEXT NOT NULL,
    payload_hash TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending',
    attempt_count INTEGER NOT NULL DEFAULT 0,
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    published_at TEXT
);

Amendment 7: Dedicated WorkService

Problem

The current design makes ExecutionService own tasks, work_items, and assignments. This conflates two different domains: human operational work (tasks, queues, assignments, deadlines, ownership) and machine execution (provider dispatch, invocation attempts, retries, compensation).

Correction

Introduce WorkService:

Commands:

  • CreateTask, UpdateTask, AssignWork, CompleteWork, ReopenWork, RouteQueueItem

Queries:

  • GetTask, ListAssignedWork, ListQueueItems, GetWorkTimeline

Storage:

  • tasks, work_items, assignments, queues, task_dependencies

Events:

  • work.created, work.assigned, work.started, work.completed, work.blocked

ExecutionService retains ownership of execution state and capability_invocations.


Amendment 8: Capability Invocation Ownership

Problem

The spec gives capability_invocations to both CapabilityService and ExecutionService.

Correction

Frozen ownership:

ServiceOwns
CapabilityServicecapability_registry, capability_grants, capability dependencies, capability schemas
ExecutionServicecapability_invocations, provider dispatch attempts, execution status, provider acceptance, retry state

Recommended invocation schema:

FieldTypePurpose
invocation_idTEXT PKUnique invocation identifier
command_idTEXT NOT NULLOriginating command
capability_keyTEXT NOT NULLCapability being invoked
capability_versionTEXT NOT NULLResolved version
connection_idTEXTProvider connection
actor_idTEXT NOT NULLRequesting actor
workspace_idTEXTWorkspace scope
request_payload_hashTEXT NOT NULLSHA-256 of request
statusTEXT NOT NULLpending / dispatched / accepted / completed / failed
provider_reference_idTEXTExternal reference from provider
provider_acceptance_jsonTEXTRaw provider response
started_atTEXT NOT NULLDispatch time
accepted_atTEXTProvider acceptance time
failed_atTEXTFailure time
failure_classTEXTretryable / terminal / ambiguous

CapabilityService validates what may execute. ExecutionService records and manages the actual execution.


Amendment 9: Reconciliation Model Strengthening

Problem

The reconciliation state machine is missing transitions, observation history is mutable, timestamps don't auto-update, and event contracts are incomplete.

Correction

Missing state transitions:

partially_verified → observing
partially_verified → verified
partially_verified → failed

compensation_required → compensated
compensation_required → compensation_failed

Not every failure requires compensation. failed may be terminal when no external state changed.

Observation history table (per-tenant D1):

CREATE TABLE reconciliation_observations (
    observation_id TEXT PRIMARY KEY NOT NULL,
    reconciliation_run_id TEXT NOT NULL,
    attempt_number INTEGER NOT NULL,
    observed_state TEXT,
    provider_status TEXT,
    matches_expected INTEGER NOT NULL DEFAULT 0,
    evidence_ref TEXT,
    observed_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (reconciliation_run_id)
      REFERENCES reconciliation_runs(id)
);

CREATE UNIQUE INDEX idx_reconciliation_observation_attempt
ON reconciliation_observations(reconciliation_run_id, attempt_number);

Timestamp correction: Every reconciliation update must explicitly set updated_at = CURRENT_TIMESTAMP. D1 does not auto-update DEFAULT CURRENT_TIMESTAMP on updates.

Expanded event contract for capability.reconciled:

StatusMeaning
VERIFIEDProvider state matches expected outcome
PARTIALLY_VERIFIEDSome but not all expected state confirmed
FAILEDVerification failed, no external state change
COMPENSATION_REQUIREDExternal state changed but outcome incorrect
COMPENSATEDAttempted action was reversed
COMPENSATION_FAILEDReversal attempt failed

Only VERIFIED produces a normal completed state. COMPENSATED means the attempted action was reversed, not successfully completed.


Amendment 10: Complete Event Envelope

Problem

Cross-service events lack common metadata fields required for deduplication and complete execution tracing.

Correction

Every cross-service event must include:

export interface PlatformEvent<TType extends string, TPayload> {
  readonly eventId: string;
  readonly eventType: TType;
  readonly eventVersion: number;

  readonly tenantId: string;
  readonly workspaceId?: string;

  readonly aggregateType?: string;
  readonly aggregateId?: string;

  readonly causationId?: string;
  readonly correlationId: string;
  readonly commandId?: string;

  readonly actor?: {
    readonly actorId: string;
    readonly actorType: "user" | "agent" | "system" | "mcp_client";
  };

  readonly payload: TPayload;
  readonly occurredAt: string;
  readonly producer: string;
}

Without eventId, eventVersion, correlationId, and causationId, deduplication and complete execution tracing will be unnecessarily difficult.


Amendment 11: Consistent Tenant Identity in D1

Problem

Although each D1 database belongs to one tenant, the current schema inconsistently includes tenant_id.

Correction

Frozen rule:

Every durable D1 business record carries tenant_id, even in a per-tenant database.

Rationale:

  • Defense in depth
  • Portable exports
  • Simpler migration to another Spine adapter
  • Self-identifying evidence
  • Safer telemetry replication
  • Easier forensic inspection

The tenant runtime must inject tenant_id; clients must never supply it as an authoritative value.


Amendment 12: Search Service Isolation

Problem

Prior references to a "Cross-Tenant Unified Vectorize/D1 Indexer" contradict the per-tenant isolation model.

Correction

Rename to Tenant-Scoped Unified Search and Indexing Service.

Cloudflare AI Search supports tenant isolation through separate instances per tenant or a shared instance with mandatory metadata filtering. Separate tenant instances align most closely with the per-tenant D1 isolation model.

Recommended V0:

ComponentValue
AI Search namespaceintegratewise-tenants
AI Search instancetenant-{tenant_id}
R2 sourceshared bucket, /tenants/{tenant_id}/...

SearchService derives tenant from the authenticated runtime, resolves the instance by tenant_id, and never accepts client-selected instance names.

Cross-tenant analytics must use sanitized telemetry, not cross-tenant semantic retrieval.


Amendment 13: Cached Projection Fallback Policy

Problem

The statement "query generation failure falls back to cached materialization" is unsafe — it may silently display stale operational state.

Correction

Explicit fallback policy:

Fresh projection succeeds
  → freshness = CURRENT

Fresh projection fails but policy permits stale read
  → return cached projection
  → freshness = STALE
  → stale_since = <timestamp>
  → stale_reason = <reason>
  → disable unsafe actions as required

No permitted cached projection
  → explicit projection failure

A stale projection must never silently masquerade as current state. The Workbench must display freshness indicators when serving stale data.


Amendment 14: Cryptographic Approval and Audit Semantics

Problem

A SHA-256 checksum verifies content integrity but does not itself provide non-repudiation. Claims of "cryptographic approval record," "non-repudiable audit trail," and "cryptographic signature" were underspecified.

Correction

Required approval record fields:

FieldPurpose
payload_hashSHA-256 of canonical payload
previous_event_hashHash-chain predecessor
signature_algorithmAlgorithm identifier
signature_key_idSigning key identifier
signatureSignature value
signed_atSignature timestamp

Required definitions:

  • Canonical payload encoding (deterministic serialization)
  • Signature algorithm selection and rotation policy
  • Key management and rotation procedure
  • Verification procedure
  • Hash-chain construction rules

Human approval identity must come from the validated session and be bound into the signed canonical payload.


Amendment 15: Frontend Deployment Target

Problem

The repository tree references both Cloudflare Pages and Vercel for the Workbench, creating ambiguity about the canonical deployment target.

Correction

Frozen deployment targets:

SurfaceTarget
Product / WorkbenchVercel microfrontends
Platform Gateway and runtimeCloudflare Workers
Tenant operational runtimeWorkers for Platforms tenant Worker + per-tenant D1 binding

The Product remains replaceable because it communicates only through the Gateway and projection contracts.


Corrected Runtime Topology

┌──────────────────────────────────────────────────────────┐
│                 WORKBENCH / MCP / HOST                   │
└───────────────────────────┬──────────────────────────────┘
                            ▼
┌──────────────────────────────────────────────────────────┐
│                    PLATFORM GATEWAY                      │
│ Auth · Tenant derivation · Rate limits · Zero Trust      │
└───────────────────────────┬──────────────────────────────┘
                            │ tenant runtime dispatch
                            ▼
┌──────────────────────────────────────────────────────────┐
│            WORKERS FOR PLATFORMS DISPATCHER              │
│ env.TENANT_DISPATCHER.get(runtime_script_name)           │
└───────────────────────────┬──────────────────────────────┘
                            ▼
┌──────────────────────────────────────────────────────────┐
│                 TENANT RUNTIME WORKER                    │
│ Static binding: env.SPINE_DB                             │
│ Runtime tenant identity fixed at deployment              │
└───────────────────────────┬──────────────────────────────┘
                            ▼
┌──────────────────────────────────────────────────────────┐
│                  PER-TENANT D1 SPINE                     │
│ Work · entities · projections · memory · governance      │
│ evidence refs · outbox · execution · reconciliation      │
└───────────────────────────┬──────────────────────────────┘
                            │
       ┌────────────────────┼────────────────────────┐
       ▼                    ▼                        ▼
 Shared Workers       Shared Cloudflare       Connected providers
 domain services      infrastructure          SaaS · MCP · APIs

Final Freeze Statement

Neon controls the tenant. A dispatched tenant runtime Worker operates the tenant. Its statically bound D1 database holds the V0 operational Spine. The Spine contract governs all access.

With these amendments, the architecture becomes internally consistent, compatible with the current Cloudflare execution model, and suitable as the baseline for migrations, typed contracts, and the first vertical implementation loop.