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):
| Field | Type | Purpose |
|---|---|---|
tenant_id | TEXT PK | Tenant identifier |
runtime_script_name | TEXT NOT NULL UNIQUE | Workers for Platforms script name |
dispatch_namespace | TEXT NOT NULL | Dispatch namespace identifier |
d1_database_id | TEXT NOT NULL | Cloudflare D1 database resource ID |
runtime_version | TEXT NOT NULL | Deployed runtime version |
deployment_status | TEXT NOT NULL | active / deploying / draining / suspended |
region_or_jurisdiction | TEXT | Data residency constraint if applicable |
updated_at | TIMESTAMPTZ 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:
| Metric | Warning threshold | Hard threshold |
|---|---|---|
| Tenant database size | 7 GB | 9 GB (migration required) |
| Account database count | 40,000 databases | 47,500 databases |
Overflow strategy:
- Additional Cloudflare account / account shard
- Migration of high-volume tenant to a compatible Spine adapter
Routing fields:
| Field | Purpose |
|---|---|
control_plane_account_id | Cloudflare account owning this tenant's resources |
dispatch_namespace | Workers for Platforms namespace |
runtime_script_name | Tenant Worker script identity |
d1_database_id | D1 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:
| Service | Owns |
|---|---|
| CapabilityService | capability_registry, capability_grants, capability dependencies, capability schemas |
| ExecutionService | capability_invocations, provider dispatch attempts, execution status, provider acceptance, retry state |
Recommended invocation schema:
| Field | Type | Purpose |
|---|---|---|
invocation_id | TEXT PK | Unique invocation identifier |
command_id | TEXT NOT NULL | Originating command |
capability_key | TEXT NOT NULL | Capability being invoked |
capability_version | TEXT NOT NULL | Resolved version |
connection_id | TEXT | Provider connection |
actor_id | TEXT NOT NULL | Requesting actor |
workspace_id | TEXT | Workspace scope |
request_payload_hash | TEXT NOT NULL | SHA-256 of request |
status | TEXT NOT NULL | pending / dispatched / accepted / completed / failed |
provider_reference_id | TEXT | External reference from provider |
provider_acceptance_json | TEXT | Raw provider response |
started_at | TEXT NOT NULL | Dispatch time |
accepted_at | TEXT | Provider acceptance time |
failed_at | TEXT | Failure time |
failure_class | TEXT | retryable / 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:
| Status | Meaning |
|---|---|
VERIFIED | Provider state matches expected outcome |
PARTIALLY_VERIFIED | Some but not all expected state confirmed |
FAILED | Verification failed, no external state change |
COMPENSATION_REQUIRED | External state changed but outcome incorrect |
COMPENSATED | Attempted action was reversed |
COMPENSATION_FAILED | Reversal 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:
| Component | Value |
|---|---|
| AI Search namespace | integratewise-tenants |
| AI Search instance | tenant-{tenant_id} |
| R2 source | shared 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:
| Field | Purpose |
|---|---|
payload_hash | SHA-256 of canonical payload |
previous_event_hash | Hash-chain predecessor |
signature_algorithm | Algorithm identifier |
signature_key_id | Signing key identifier |
signature | Signature value |
signed_at | Signature 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:
| Surface | Target |
|---|---|
| Product / Workbench | Vercel microfrontends |
| Platform Gateway and runtime | Cloudflare Workers |
| Tenant operational runtime | Workers 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.