Rust 领域模型与 SQLite/D1 存储契约

正文由仓库文档生成,保留原始规范状态和示例。RFC 草案及验证计划与当前实现可能有差异;已验证范围见实现记录,加密与用户隔离升级见 安全说明。

项目 值
状态 Approved design:Step 2 已确认,尚未实现
日期 2026-10-12
基线 RFC-0001
性质 P01–P18
已确认 Rust;结构化状态优先 SQLite/D1;Cloudflare 首个部署;公开仓库;Apache-2.0
本文输出 聚合、值对象、关系模型、原子存储接口及待确认的实现选择

本文的数据模型已获确认,尚未创建 Rust 源码、数据库迁移或测试。文中的 Rust 与 SQL 用于说明契约,不代表已经编译或通过生产适配验证。当前进入 Step 3;依其测试契约进入 TDD 实现;用户最新授权免逐步 review。

1. 领域与运行边界

Waypost 保存提交、可读取内容、消费声明与来源记忆版本。身份认证、空间隔离和资源授权是所有操作的共同边界。模型推理、自然语言决策、任务执行及记忆语义合并由参与方承担。

首版采用一个 Rust 项目,领域数据与存储行为不依赖 Worker、HTTP 框架或 MCP SDK。默认 Cloudflare 部署使用 D1 和 R2;本地及自托管提供 SQLite 和文件对象存储。SQLite 适配器不通过 D1 管理 REST API模拟一个本地数据库。

图示源码 · Mermaid
flowchart TB
    HTTP[HTTP 入口] --> Service[公开领域操作]
    MCP[MCP 入口] --> Service
    Service --> Domain[值对象与状态规则]
    Service --> Metadata[MetadataStore 原子操作]
    Service --> Objects[BlobStore 不可变内容]
    Metadata --> SQLite[本地 SQLite]
    Metadata --> D1[Cloudflare D1]
    Objects --> Files[本地文件对象]
    Objects --> R2[Cloudflare R2]
    Client[参考消费者] --> Queue[本机 SQLite 待处理队列]
    Client --> HTTP

不引入微服务、缓存、额外线程池或事件总线。Feed 的事实变化直接进入同一元数据提交;未来通知适配只读取这些事实。异步接口用于适配平台 I/O,不意味着创建多线程执行器。

2. 通用语言与聚合边界

完整术语沿用 CONTEXT.md。以下表明确各个状态由谁管理。

聚合或记录 管理的状态 原子边界
Space / 身份配置 默认收件人、参与者、来源 URI、凭据及授权配置 每次控制面变更原子完成
Submission 幂等键、指纹、预留资源身份、准备状态、稳定结果 唯一键保留;最终结果与业务发布同事务
Thread 会话身份和获准参与者 新会话在消息发布中建立;成员变更由控制面完成
Message 不可变投递、收件人、附件引用、正文引用 与 Submission 和 Feed 同事务发布
Consumer 固定视图、视图版本、过滤、回执及检查点 一次 ACK 的全部回执与检查点同事务
Upload 预期字节、期限、上传状态和清理排他状态 封存与清理条件竞争,只允许一方成功
Blob 已校验、不可变、可引用的资料事实 上传封存或内部资料准备完成后建立
Collection 固定来源和范围、当前 Revision expected_revision CAS 与版本发布同事务
Revision 指定版本的完整条目与删除标记、manifest 不可变,与 Collection 前进及 Feed 同事务

聚合不是独立数据库或服务。发布本身有跨记录原子性,MetadataStore 提供完整业务提交,不把它拆成可以分别成功的公开写操作。

3. 值对象

值对象字段保持私有,由校验构造函数建立。传輸 DTO 先解析,再转换为值对象;数据库读出也校验存储范围。

类型 表示与规则
SpaceId / ActorId / ConsumerId 等 UTF-8 String;协议对象 ID 的 ASCII 字符集及 1–96 长度
RequestId ASCII 字符集包含 .、:;1–128 长度;不作为路径
Revision Rust u64;公开范围 0 至 2^53−1;SQLite INTEGER
VisibleSeq Rust u64;内部范围 1 至 i64::MAX;公开十进制字符串
Sha256 / Fingerprint 内部 [u8; 32];数据库 BLOB;需要时转 64 位小写十六进制
Timestamp 内部 UTC 毫秒整数;公开 RFC 3339;服务时间决定发布事实
RelativePath 相对标识;拒绝绝对路径、.. 路径段、空段与反斜杠
Subject 非空 UTF-8,最多 256 字节
JsonMetadata 受总字节限制的 JSON 对象;不参与身份和授权
OpaqueCursor 完整性保护的外部字符串;调用方不可修改位置

VisibleSeq 可以使用数据库全局序号,Space 授权与过滤决定可见范围;不同空间共享计数器不会使对象变得可读。数据库生成序号到达 i64 上限后返回 RESOURCE_LIMIT,不回绕。

D1 经 JavaScript 边界返回大整数时可能发生精度损失。序号查询使用 CAST(sequence AS TEXT),绑定大序号使用十进制字符串及显式整数转换,不先转 JavaScript Number。Revision 保持 RFC 已规定的安全整数上限。

请求指纹采用 RFC 8785 JCS 与 SHA-256。收件人集合先排序;附件、正文选项等有序列表保持顺序。服务生成的资源 ID、时间、授权凭据不进入请求指纹。省略的默认收件人在首次保留时固定;新建会话的请求仍以“创建新会话”的语义计算指纹,预留会话 ID另行保存。

4. Rust 数据结构

以下代码省略 derive、序列化属性和校验构造函数;不使用一个 serde_json::Value 承载所有领域数据。

4.1 认证上下文

pub struct Principal {
    space_id: SpaceId,
    actor_id: ActorId,
    consumer_id: ConsumerId,
    credential_id: TokenId,
    actor_authority_version: u64,
    credential_authority_version: u64,
    grant_fingerprint: Fingerprint,
    grants: Grants,
}

pub struct Grants {
    scopes: Vec<Scope>,
    recipient_ids: Vec<ActorId>,
    collection_ids: Vec<CollectionId>,
    referenceable_blob_ids: Vec<BlobId>,
}

pub enum Scope {
    MessageSend, MessageRead, SpaceObserve, ReceiptWrite,
    MemoryWrite, MemoryRead, BlobWrite, BlobReference,
    EventSubscribe,
}

集合规范化为排序且唯一的列表。空列表的含义逐项按 RFC 定义,不能一律解释成“全部资源”。BlobReference 是 RFC 9.2 明确的重引用授权,只有控制面能授予。身份与 grant_fingerprint 不由输入正文提供。

Principal 是请求起点的认证事实,不是永久提交许可。最终提交在同一数据库原子操作中检查当前凭据、到期、撤销、Actor 状态、Consumer 视图、资源及所需授权。指纹来自实际权限与资源授予,排除 token ID,使同权限凭据轮换可继续使用游标。

4.2 提交与稳定结果

pub struct SubmissionKey {
    space_id: SpaceId,
    producer_id: ActorId,
    operation: Operation,
    request_id: RequestId,
}

pub struct Submission {
    id: SubmissionId,
    key: SubmissionKey,
    fingerprint: Fingerprint,
    reservation: ReservedResource,
    state: SubmissionState,
}

pub enum SubmissionState {
    Reserved { reserved_at: Timestamp },
    Prepared { artifact: PreparedArtifact },
    Committed { committed_at: Timestamp, result: CommitResult },
    Failed { error: RecordedFailure },
}

pub enum CommitResult {
    Message { message_id: MessageId, thread_id: ThreadId },
    BlobPrepared { upload_id: UploadId, blob_id: BlobId, expires_at: Timestamp },
    BlobSealed { blob_id: BlobId },
    Memory { collection_id: CollectionId, revision: Revision, changed: bool },
    Ack { items: Vec<AckedItem>, checkpoint: Option<Checkpoint> },
}

Operation 使用领域名,覆盖 message.send、receipt.ack、blob.prepare、blob.commit、memory.publish。只读操作没有提交键。控制面创建操作使用独立的管理员幂等账本,不冒充一个普通 Actor。

ReservedResource 明确记录已预留的 Message/Thread、Upload/Blob 或目标 Collection 等身份。并发重试唯一键保留失败后读回已有保留,不能换资源 ID。

PreparedArtifact 引用已验证的正文或完整记忆 manifest、大小和摘要。RecordedFailure 使用领域错误码与明确字段,例如冲突时的 current_revision;临时网络或存储错误不进入永久 failed。

提交结果采用固定字段及结果关联表保存。不能用后来变化的回执或 Collection head重建“原请求结果”。

4.3 消息与 Feed

pub struct Message {
    id: MessageId,
    space_id: SpaceId,
    producer_id: ActorId,
    thread_id: ThreadId,
    in_reply_to: Option<MessageId>,
    recipient_ids: Vec<ActorId>,
    kind: MessageKind,
    subject: Subject,
    body_ref: ObjectRef,
    attachment_ids: Vec<BlobId>,
    committed_at: Timestamp,
}

pub enum MessageKind {
    Info, Question, Result, TodoSuggestion,
    MemoryNotice, ProcessingResult,
}

pub struct FeedItem {
    id: FeedItemId,
    space_id: SpaceId,
    sequence: VisibleSeq,
    producer_id: ActorId,
    recipient_ids: Vec<ActorId>,
    thread_id: Option<ThreadId>,
    committed_at: Timestamp,
    data: FeedData,
}

pub enum FeedData {
    Message { message_id: MessageId, subject: Subject, kind: MessageKind },
    MemoryRevision { collection_id: CollectionId, revision: Revision,
                     parent_revision: Revision, manifest_blob_id: BlobId },
    Receipt { receipt_id: ReceiptId, receipt_version: u64,
              subject_item_id: FeedItemId, subject_resource_id: ResourceId,
              consumer_id: ConsumerId, actor_id: ActorId,
              stage: ReceiptStage, updated_at: Timestamp },
}

MessageBody 使用 text: String、options: Vec<String> 和固定来源字段;不可变正文保留在对象存储。SQL 中保存用于查询的 subject/kind 与引用,不把整个信箱放入 JSON 文件。ObjectRef 是服务生成的内部对象身份,客户端不能指定 bucket 或键。

Feed 的 message 和 memory 摘要可从不可变发布记录读取。Receipt 摘要必须保存每一次状态推进时的快照,不能 join当前回执后把历史阶段变成最新阶段。

4.4 Consumer 与回执

pub struct Consumer {
    id: ConsumerId,
    space_id: SpaceId,
    actor_id: ActorId,
    view: ConsumerView,
    view_revision: u64,
    filter: FeedFilter,
    disabled: bool,
}

pub enum ConsumerView { Inbox, SpaceLog }
pub enum ReceiptStage { Received, Processed }

pub struct Receipt {
    id: ReceiptId,
    consumer_id: ConsumerId,
    subject_item_id: FeedItemId,
    stage: ReceiptStage,
    version: u64,
    updated_at: Timestamp,
}

pub struct Checkpoint {
    consumer_id: ConsumerId,
    view_revision: u64,
    grant_fingerprint: Fingerprint,
    sequence: u64,
}

没有回执行表示 none,不新增第三个“未收到”状态行。processed 可以从 none直接建立。Checkpoint 起点为 0,未必已有 Feed;其位置类型与必须大于 0 的 VisibleSeq 区分。

FeedFilter 仅包含 RFC 允许的 producer_ids、thread_ids、item_types、collection_ids。游标内部包含 Space、Actor、Consumer、视图版本、过滤指纹、有效授权指纹、位置与游标版本。首版采用有版本的固定编码和 HMAC-SHA256,不需要保存所有分页游标。

4.5 Upload 与 Blob

pub struct Upload {
    id: UploadId,
    blob_id: BlobId,
    space_id: SpaceId,
    owner_id: ActorId,
    expected_size: u64,
    expected_sha256: Sha256,
    content_type: String,
    filename: Option<String>,
    object_ref: ObjectRef,
    expires_at: Timestamp,
    state: UploadState,
}

pub enum UploadState { AwaitingContent, Stored, Sealed, Cleaning }

pub struct Blob {
    id: BlobId,
    space_id: SpaceId,
    owner_id: ActorId,
    object_ref: ObjectRef,
    size: u64,
    sha256: Sha256,
    content_type: String,
    sealed_at: Timestamp,
}

Upload 的 blob_id 在 prepare时固定,但有效 Blob 行只在封存时建立。零字节内容合法。写入使用 create-only,已存在时比较真实的已校验对象事实;R2 条件写失败不能直接当作成功。

封存后的读取权来自所有权、已发布消息/记忆的引用或明确授权。暂存期限只影响未封存资料。上传与提交租约保存在元数据内,Cleaning只能在没有有效租约及已发布引用时取得;迟到 PUT无法覆盖已有对象。

4.6 Collection、Revision 与条目

pub struct Collection {
    id: CollectionId,
    space_id: SpaceId,
    source_actor_id: ActorId,
    scope: String,
    current_revision: Revision,
}

pub struct PublishedRevision {
    collection_id: CollectionId,
    revision: Revision,
    parent_revision: Revision,
    manifest_blob_id: BlobId,
    state_fingerprint: Fingerprint,
    committed_at: Timestamp,
}

pub struct MemoryEntry {
    item_id: MemoryItemId,
    state: MemoryEntryState,
}

pub enum MemoryEntryState {
    Active { blob_id: BlobId, path: RelativePath, metadata: JsonMetadata },
    Tombstone { deleted_in_revision: Revision },
}

pub enum MemoryInput {
    Inline { items: Vec<MemoryItemInput>, delete_ids: Vec<MemoryItemId> },
    Manifest { blob_id: BlobId },
}

pub struct PublishMemory {
    request_id: RequestId,
    collection_id: CollectionId,
    expected_revision: Revision,
    mode: MemoryMode,
    input: MemoryInput,
}

pub enum MemoryMode { Snapshot, Delta }

MemoryInput 在内部类型中保证两种输入互斥,DTO 校验同时提供或缺失的输入。Snapshot 的 delete_ids 必须为空;Delta 的 upsert与delete互斥。未删除的旧 Tombstone在后续版本保留,明确 upsert才恢复。

每一个已发布 Revision 直接保存完整关系行集合,包括 Tombstone。通过 Collection head选择当前版本;首版不维护另一份可变 current_items,不引入历史回放或结构共享算法。版本 0 是虚拟空集合,没有 manifest或版本行。

完整版本行可能增加历史容量,这是为了直观的固定版本读取而采用的简单实现;容量由部署限额控制。若实际测量证明需要优化,另评估版本行复用,保持公开语义。

状态指纹覆盖按 item_id排序的 active条目与 Tombstone,以及语义 metadata;排除提交时间与正在分配的 revision。需要比较实际语义状态,不能把指纹一致当作绕过 head CAS的条件。

5. SQLite 关系模型

5.1 约定

  • 对象 ID 使用 TEXT;同空间关系使用复合外键 (space_id, object_id),避免只凭一个 ID连接跨空间对象。
  • 摘要使用长度为 32 的 BLOB;布尔和枚举有 CHECK;版本和时间使用 INTEGER。
  • 明确 NOT NULL、唯一约束和外键;本地每条连接启用 foreign_keys,D1 使用其外键机制。
  • metadata是受限 TEXT JSON对象;仅该类可扩展内容采用 JSON列。生产者、权限、状态和关联关系不得塞入该列。
  • FK 不自动跨已发布历史级联删除;v0.1停用身份和逻辑删除不物理移除历史资料。
  • 核心 SQL与关系规则共享,SQLite事务与 D1 batch的执行适配分开。

5.2 表清单

这是目标模型清单,按实施切片逐步创建迁移,不提前建立空表。

表 主键 / 主要唯一约束 主要字段与用途
spaces space_id;source_uri唯一 display_name、default_actor_id、固定来源 URI
actors (space_id, actor_id) kind、display_name、disabled、authority_version
consumers (space_id, consumer_id) actor_id、view、view_revision、disabled
consumer_filters (space_id, consumer_id, dimension, value) 固定过滤集合
tokens token_id;token_digest唯一 space_id、actor_id、consumer_id、授权代次、期限、撤销
token_scopes (token_id, scope) 固定 scope集合
token_recipient_grants (token_id, recipient_actor_id) 获准投递对象
token_collection_grants (token_id, collection_id, action) 获准读取/写入来源记忆
token_blob_reference_grants (token_id, blob_id) 明确的额外重引用范围
threads (space_id, thread_id) created_at
thread_members (space_id, thread_id, actor_id) 所有者批准的参与者
submissions submission_id;(space_id, producer_id, operation, request_id)唯一 指纹、状态、预留身份、准备引用、稳定结果、明确错误、租约
submission_recipients (submission_id, actor_id) 首次固定的有效收件集合
submission_ack_items (submission_id, position) 原 ACK响应的 item、stage和状态版本
admin_submissions (operation, request_id) 管理员创建操作的指纹与稳定资源结果
messages (space_id, message_id);submission_id唯一 producer、thread、reply、subject、kind、正文引用、发布时间
message_recipients (space_id, message_id, actor_id) 投递关系
message_attachments (space_id, message_id, position) blob_id,保留附件顺序
feed_items sequence INTEGER PRIMARY KEY AUTOINCREMENT;item_id唯一 Space、类型、资源、版本、来源、会话、发布时间
feed_recipients (space_id, item_id, actor_id) 消息与回执的收件关系;记忆仍按 Collection授权
feed_receipt_updates (space_id, item_id) 回执推进时的不可变类型专属数据
receipts (space_id, receipt_id);(space_id, consumer_id, subject_item_id)唯一 stage、version、updated_at
consumer_checkpoints (space_id, consumer_id, view_revision, grant_fingerprint) sequence、updated_at
uploads (space_id, upload_id);blob_id唯一 预期与已验证资料、期限、状态、上传/提交租约
blobs (space_id, blob_id);object_ref唯一 所有者、大小、类型、SHA-256、sealed_at
collections (space_id, collection_id) source_actor_id、不可变 scope、current_revision
memory_revisions (space_id, collection_id, revision);submission_id唯一 parent_revision、manifest_blob_id、状态摘要、发布时间
memory_revision_items (space_id, collection_id, revision, item_id) active/tombstone、blob_id、path、metadata、deleted_in_revision

所有 token授予的关联表也保存对应 space_id并使用复合外键,确保所引用的 Actor、Collection或 Blob属于 token空间。表格中的主键简写不省略这一隔离要求。

Space创建同时建立默认 Actor。二者的外键循环使用同一次创建事务内的延迟外键约束;默认 Actor在后续控制面操作中保持不变。普通数据面无修改默认 Actor的方法。

5.3 必要索引

查询 索引或约束
验证 Bearer tokens(token_digest)唯一
查询原提交 submissions唯一幂等键
空间增量分页 feed_items(space_id, sequence)
Actor收件范围 feed_recipients(space_id, actor_id, item_id)
Collection变化范围 feed_items(space_id, item_type, resource_id, sequence)
会话增量过滤 feed_items(space_id, thread_id, sequence)
查消费者回执 receipts(space_id, consumer_id, subject_item_id)唯一
固定版本记忆分页 memory_revision_items的主键顺序
单版本 active路径唯一 memory_revision_items上的 partial UNIQUE索引
检查附件引用 message_attachments(space_id, blob_id)
检查记忆资料引用 memory_revision_items(space_id, blob_id)
可清理上传 uploads(state, expires_at)

仅建立公开操作确实使用的索引。查询继续在权限与视图交集内执行,不能“取所有数据后仅由客户端过滤”。

5.4 代表性结构草图

以下是结构约束示例,不是当前已执行的迁移。完整迁移在 Step 4按已确认模型编写。

CREATE TABLE collections (
    space_id TEXT NOT NULL,
    collection_id TEXT NOT NULL,
    source_actor_id TEXT NOT NULL,
    scope TEXT NOT NULL,
    current_revision INTEGER NOT NULL DEFAULT 0
        CHECK (current_revision BETWEEN 0 AND 9007199254740991),
    PRIMARY KEY (space_id, collection_id),
    FOREIGN KEY (space_id, source_actor_id)
        REFERENCES actors(space_id, actor_id)
);

CREATE TABLE memory_revisions (
    space_id TEXT NOT NULL,
    collection_id TEXT NOT NULL,
    revision INTEGER NOT NULL CHECK (revision > 0),
    parent_revision INTEGER NOT NULL CHECK (parent_revision >= 0),
    submission_id TEXT NOT NULL UNIQUE,
    manifest_blob_id TEXT NOT NULL,
    state_fingerprint BLOB NOT NULL CHECK (length(state_fingerprint) = 32),
    committed_at INTEGER NOT NULL,
    PRIMARY KEY (space_id, collection_id, revision),
    CHECK (revision = parent_revision + 1),
    FOREIGN KEY (space_id, collection_id)
        REFERENCES collections(space_id, collection_id),
    FOREIGN KEY (space_id, manifest_blob_id)
        REFERENCES blobs(space_id, blob_id)
);

CREATE TABLE memory_revision_items (
    space_id TEXT NOT NULL,
    collection_id TEXT NOT NULL,
    revision INTEGER NOT NULL,
    item_id TEXT NOT NULL,
    entry_kind TEXT NOT NULL CHECK (entry_kind IN ('active', 'tombstone')),
    blob_id TEXT,
    path TEXT,
    metadata_json TEXT,
    deleted_in_revision INTEGER,
    PRIMARY KEY (space_id, collection_id, revision, item_id),
    CHECK (
        (entry_kind = 'active' AND blob_id IS NOT NULL AND path IS NOT NULL
         AND metadata_json IS NOT NULL AND deleted_in_revision IS NULL)
        OR
        (entry_kind = 'tombstone' AND blob_id IS NULL AND path IS NULL
         AND metadata_json IS NULL AND deleted_in_revision IS NOT NULL
         AND deleted_in_revision > 0 AND deleted_in_revision <= revision)
    ),
    FOREIGN KEY (space_id, collection_id, revision)
        REFERENCES memory_revisions(space_id, collection_id, revision),
    FOREIGN KEY (space_id, blob_id)
        REFERENCES blobs(space_id, blob_id)
);

CREATE UNIQUE INDEX memory_active_path
ON memory_revision_items(space_id, collection_id, revision, path)
WHERE entry_kind = 'active';

6. 显式存储接口

接口返回领域可解释的结果。StoreError仅代表存储不可用等适配失败;版本冲突、凭据已失效与稳定重试是明确的操作结果,不能伪装成数据库执行成功。

pub trait MetadataStore {
    async fn authenticate(&self, credential: CredentialDigest, now: Timestamp)
        -> Result<Principal, AuthError>;

    async fn reserve_submission(&self, input: ReserveSubmission)
        -> Result<ReservationOutcome, StoreError>;

    async fn load_submission(&self, principal: &Principal, key: SubmissionKey)
        -> Result<Submission, LookupError>;

    async fn mark_prepared(&self, input: MarkPrepared)
        -> Result<PrepareOutcome, StoreError>;

    async fn commit_message(&self, input: CommitMessage)
        -> Result<MessageCommitOutcome, StoreError>;

    async fn commit_revision(&self, input: CommitRevision)
        -> Result<RevisionCommitOutcome, StoreError>;

    async fn acknowledge(&self, input: Acknowledge)
        -> Result<AckOutcome, StoreError>;

    async fn read_feed(&self, input: ReadFeed)
        -> Result<FeedPage, ReadError>;

    async fn read_message(&self, input: ReadMessage)
        -> Result<Message, ReadError>;

    async fn read_memory(&self, input: ReadMemory)
        -> Result<MemoryPage, ReadError>;

    async fn prepare_upload(&self, input: PrepareUpload)
        -> Result<UploadPrepared, StoreError>;

    async fn seal_upload(&self, input: SealUpload)
        -> Result<SealOutcome, StoreError>;
}

上述 MetadataStore 为设计阶段的原子操作草图。实现阶段按 ADR-0001 修订执行边界:共享核心持有具名领域操作和发布规则,SQLite/D1 只实现 crate-private SQL 驱动。原始 SQL 不作为客户端或 Rust 公共存储 API;不建立通用 Repository<T> 或动态插件注册。Future 保持本地,不要求 Send 或 async_trait;Worker 的流式字节处理保留在适配器内。

6.1 参数与结果

参数 必需信息 可观察结果
ReserveSubmission 当前主体、领域操作、原 request_id、规范化指纹、候选预留身份与固定默认值 New、ExistingPending、ExistingCommitted、ExistingFailed、Conflict或Denied
MarkPrepared 原提交、当前主体、实际可读取的不可变正文/manifest事实 Prepared、既有结果、冲突或当前授权失败
CommitMessage 原提交、认证版本、准备资料、固定收件人和会话引用、服务提交时间 Committed、既有稳定结果、Pending、Denied或固定业务失败
CommitRevision 原提交、认证版本、Collection、expected_revision、完整下一状态、manifest或无变化判定 Published、Unchanged、Existing、RevisionConflict或Denied
Acknowledge 原提交、Consumer绑定视图、有效授权指纹、最多20项及可空检查点 原子保存的稳定结果或整批拒绝
ReadFeed 当前主体、Consumer、已验证游标、limit 获准且有序的 FeedPage与下一游标位置
ReadMemory 当前主体、Collection、明确或游标绑定的 revision、tombstone选项、limit 单一固定版本的 MemoryPage
SealUpload 原提交、当前主体、Upload、已核验对象事实和当前时间 固定封存结果、未就绪、到期、清理竞争或授权失败

ReadFeed和ReadMemory仍必须执行当前授权检查。页查询中的权限、过滤与数据读取需要一致的元数据视图;不能只依赖查询前的内存检查。首版不启用 D1读副本,减少初始一致性与撤销边界的复杂度。

BlobStore提供 create_only(object_ref, stream, expected)、inspect(object_ref)、read(object_ref) 与取得清理许可后的 remove(object_ref)。create_only返回 Created或AlreadyPresent,带实际验证的大小、类型和摘要;流的具体类型留在实现契约中固定,不把请求 body缓冲成 Vec<u8>作为唯一 API。服务负责读取/引用授权,BlobStore不接受用户提供的桶地址。

控制面提供 create_space、create_actor、create_consumer、create_collection、issue_token、revoke_token、disable_actor;附加会话成员授权操作由同一管理员边界负责。会话成员授权路径将在传输契约中明确,普通 message.send不扩大参与范围。

7. 原子提交与数据库差异

7.1 提交前准备

  1. 验证 schema与当前授权。
  2. 通过唯一键保留提交,固定指纹、资源身份和默认值。
  3. 准备不可变正文或完整 manifest,校验附件/条目 Blob,记录 prepared。
  4. 执行一个 MetadataStore原子业务操作。
  5. 提交完成后返回稳定结果,响应丢失时查询原键。

这些阶段之间可能重启。恢复所需身份、指纹、准备对象与稳定结果都在耐久存储内。已提交或失败的原结果仍需通过当前授权检查才能返回。

7.2 SQLite

本地原子操作使用短事务,事务内检查授权和原提交、条件推进 Collection、写不可变版本/消息与 Feed、保存稳定结果。需要争取写入边界时使用 BEGIN IMMEDIATE,避免把应用层先读后写当作 CAS。

文件对象存储采用临时写入、内容验证、文件与必要目录同步、create-only发布;其成功表示满足已约定的耐久写契约。它不与 SQLite形成跨存储事务,因此仍遵循 prepare → metadata commit流程。

7.3 D1

D1 batch用于完整原子提交,不能把本地事务闭包或逐次 async BEGIN/UPDATE/COMMIT直接映射到 D1。需要采用一次 batch内的提交条件:

  • 原提交仍属于当前主体且尚未完成。
  • 当前凭据、Actor、所需资源和授权允许操作。
  • Collection head等于 expected_revision,或 ACK绑定视图仍有效。
  • 资料引用仍为已封存状态。

guard未命中时所有业务写入必须无副作用。所有后续写入由本次提交条件保护;一次 UPDATE执行成功但改变0行不等于发布成功。CAS失败记录原提交的稳定冲突结果,不插入新版本或 Feed。无变化操作也在同一 batch条件下保存结果,不增加版本和 Feed。

将 D1执行 SQL的结果与业务成功分开解码。提交响应丢失时先查询原提交,不能重新分配资源或盲目再次追加序号。实际 guard SQL在 Step 4实现,由 Step 3确定的公开性质与适配契约验证。

D1的每次 Worker调用查询预算、每条语句参数数和长度限制同时约束完整版本写入。Free当前每次调用最多50个查询、每条查询最多100个绑定参数;不能假设一次 batch可以绕过这些限制。D1部署通过 describe公布更小的 Collection条数、请求大小和变更上限,并在产生任何可见副作用前整批拒绝超限输入。具体可支持值在 Step 4通过真实适配测量确认,不预先承诺默认10,000条在免费部署上可用。本地 SQLite部署可采用 RFC上限。依据:D1平台限制。

依据:D1 batch与事务、D1结果与 changes。这里的 guard方案是本项目设计,平台文档不替项目证明 CAS正确性。

8. 不可变对象与记忆 manifest

对象存储保存三类内容:消息正文、用户资料、服务生成的完整版本 manifest。每一对象都有服务生成的独立引用和被验证的摘要,首版不做跨空间去重。

记忆输入 manifest保持 RFC已有 {items, delete_ids} 格式。服务生成的完整版本 manifest采用明确的 manifest_version、Collection、revision、parent_revision、完整 active条目和 Tombstone;两者为不同用途的格式,不能将 delta输入当作完整导出版本。

完整版本 manifest与关系行描述同一个已发布状态。关系行是服务分页与授权查询的依据,manifest是可携带内容及对象引用。发布前二者由同一经过校验的状态生成,测试分别读取二者进行一致性验证。

没有内容变化时不创建新有效版本。即使准备阶段留下未引用的内部对象,也不能进入 Feed或伪造版本;清理必须遵循资料保留与租约规则。

9. 参考消费者的本机 SQLite

参考消费者只负责可靠收取和展示待处理状态,不自动执行自然语言中的外部行动。它使用与服务分开的数据库文件。

本机表 关系与含义
local_feed_items item_id唯一,空间、来源、序号、类型、资源和版本
local_messages 原消息身份与固定正文/资料引用;常用正文保存 TEXT
local_receipt_updates 收到的回执推进事实
local_memory_versions 收到的来源 Collection与固定版本
local_work item_id关联待处理、处理中、已处理状态及明确错误
local_checkpoints Consumer、视图和授权指纹对应的可靠保存位置
local_outbound 稳定 request_id、领域操作、规范化待发参数与当前提交结果

一页内容、待处理项和本机检查点先在一笔 SQLite事务中保存,再发 received ACK。ACK响应丢失时仍保留本机记录。processed只在参考客户端明确确认处理后发送。出站参数采用固定列和子表,metadata仍可使用受限 JSON。

具体参考客户端接口与 P17故障点在 Step 3确认;实现的固定列与子表见 SQLite 参考 Consumer。原始入站传输与提交响应可保留为不可变归档,不作为重放参数或处理进度的唯一依据。该客户端不保证其他未遵循保存流程的助手也具有本机可靠性。

10. Dashboard 管理读取扩展

用户新增首版 Dashboard要求,具体契约见 RFC-0002。它复用原有聚合,通过独立管理员身份提供具名读取与已有控制面操作。新增 admin_sessions关系表保存会话摘要与期限;浏览器不获得数据库或对象存储管理凭据。

Inspector展示已发布事实、固定记忆版本与独立回执,不修改 Consumer进度。首版先展示服务已记录的活动;外部执行轨迹的显式上报作为独立扩展,后续明确范围再实施。

11. 已确认的模型决定

用户已确认:

  1. 采用以上聚合和值对象,公开 DTO与领域类型分开。
  2. 服务结构化状态使用 SQLite/D1关系表;正文、资料与版本 manifest使用不可变对象存储。
  3. 历史记忆版本保存完整关系行,当前状态由 head选择,暂不增加可变镜像或历史重放算法。
  4. MetadataStore暴露完整原子业务操作,D1与本地 SQLite分别实现提交机制。
  5. 原始结果、回执变化快照和分视图检查点分别保存,避免用可变现态替代历史事实。
  6. 参考消费者使用单独 SQLite数据库,保存后 ACK;测试限定在已确认的公开边界。
  7. Dashboard使用独立管理会话与关系查询,加入首版的内容检查和身份管理。

Step 3将 P01–P18映射为 Rust proptest命令模型、真实存储契约与确定性故障回归;依其测试契约进入 Step 4;用户最新授权免逐步 review。

12. 参考依据

← 返回 RFC 索引