/** * device lane 的 HTTP 错误码闭集(v2 §4.8 逐字)。**唯一导出点** —— 端点车(A-3/车C)从这里取, * 禁在路由里手抄字面量(§4.8 task-R1-12:「下表即闭集全集,禁四份手抄」)。 * * 🔴 这里只是**码与状态的定义**;把它们登记进 `docs/ASSISTANT-WIRE-CONTRACT.md` 附录 A(以及 * `test/error-code-catalog-live.test.ts` 的目录双向 diff)是**端点车**的义务 —— 那道门扫的是 * `sendError`/`sendJson` 的发送点,本文件没有任何发送点,所以本车不动文档也不会让那道门变红/变假绿。 */ export declare const DEVICE_ERROR_CODES: { /** 会话没有设备绑定(根会话未绑 / 子代继承不到根绑定)。 */ readonly NOT_BOUND: "device.not_bound"; /** 绑定的设备当前不在线(presence 判定 = 连接租约在 DB now 之后)。 */ readonly OFFLINE: "device.offline"; /** 身份失配:换绑/吊销后赎回、绑定设备与请求设备不一致。 */ readonly IDENTITY_MISMATCH: "device.identity_mismatch"; /** 设备已吊销(终态)。 */ readonly REVOKED: "device.revoked"; /** checkpoint 的 workspaceHandle.provider 与本 worker 的 lane 不符(§4.5 步 3)。 */ readonly PROVIDER_MISMATCH: "device.provider_mismatch"; /** 连接租约被别的副本/进程持有(§4.6 lease fencing)。 */ readonly ATTACHED_ELSEWHERE: "device.attached_elsewhere"; /** 并发首绑的输家且请求的是**另一台**设备(§4.3.2 绑定写协议)。 */ readonly CLAIM_CONFLICT: "device.claim_conflict"; /** 单设备在途指令数超 `maxInflightPerDevice`(§11-O3 默认 4)。 */ readonly BUSY: "device.busy"; /** enrollment 拒绝的**唯一**对外码 —— 过期 / 不存在 / principal 不匹配 / token 重用**同形** * (反枚举:细分理由只进审计,不进 wire;否则 token 撞库能问出「这个 token 存在吗」)。 */ readonly ENROLLMENT_INVALID: "device.enrollment_invalid"; /** 反枚举 404:设备不存在 **与** 设备不是你的,逐字节同形。 */ readonly NOT_FOUND: "not_found.device"; /** O4 换绑动词(2026-08-30 v2 稿 §2 步 3):前态 CAS 输 —— 体带当前 rev(`currentRev`,[ref] * currentPending 哲学,壳一跳重定位)。**唯一铸造点 = `POST /v1/devices/sessions/:rootSessionId/rebind`** * (管理面,`http/routes/devices.ts`);adapter/准入链结构上不产它。 */ readonly REBIND_REV_CONFLICT: "device.rebind_rev_conflict"; /** O4 换绑动词(v2 稿 §2 步 4):该根会话名下有非终态 run(durable 判据 = run store 的 session * claim;hub 内存在途计数**不作门**——跨副本不可见)—— 体带 `activeTaskId`。铸造点同上一条。 */ readonly REBIND_ACTIVE_RUN: "device.rebind_active_run"; }; /** {@link DEVICE_ERROR_CODES} 的值联合 —— 闭集(未知码 = 编译错误,[ref] 词表纪律)。 */ export type DeviceErrorCode = (typeof DEVICE_ERROR_CODES)[keyof typeof DEVICE_ERROR_CODES]; /** * 限流拒绝复用**既有**的通用码(`docs/ASSISTANT-WIRE-CONTRACT.md` 已收录,429)。 * * 🔴 刻意不铸 `device.rate_limited`:§4.8 的 device 闭集里没有限流成员,而限流面本仓已有属主 * (`observability/rate-limit.ts` 的 RateGate + `limit.rate_exceeded`)。为一个已有语义的面新铸一个 * 同义码 = 消费端要认两套。`device.busy`(429)留给**在途指令数**上限,不是这里。 */ export declare const DEVICE_RATE_LIMIT_CODE: "limit.rate_exceeded"; /** * 排空/停机拒绝复用**既有**的通用码(`draining`,`docs/ASSISTANT-WIRE-CONTRACT.md` 附录 A 已收录,503)。 * * 🔴 刻意不铸 `device.draining`([ref]/[ref]):§4.8 的 device 闭集**冻结**且无 draining 成员,而排空 * 语义面全部署早有属主词 —— HTTP 计费提交拒臂(`sendError(res, 503, "draining", …)`,SDK spec 逐字引用 * 的冻结字面)、drain 期新 upgrade 拒(503)、WS hello 拒帧 / bye 帧的 `draining` 分支键。为同一语义再铸 * `device.*` 同义码 = 消费端要认两套(上面 rate-limit 条的同一判据);且 `device.draining` 在 HTTP 层 * 零发送点,写进附录 A 即幽灵行(`error-code-catalog-live` 的「文档多码」臂当场红)。 * 与 `device.busy` 的分工自此可判:`device.busy`(429)= 在途满,稍后重试**同设备**;`draining`(503)= * **本副本**在退,重试同副本不会好 —— 该去替换副本/等设备重连。 */ export declare const DEVICE_DRAINING_CODE: "draining"; /** device 拒绝面的码全集 = §4.8 闭集 + 两个借用位(限流 429 / 排空 503)。 */ export type DeviceRejectCode = DeviceErrorCode | typeof DEVICE_RATE_LIMIT_CODE | typeof DEVICE_DRAINING_CODE; /** 拒绝码 → HTTP 状态。**穷举 map**(缺一个成员 = 编译错误),不是运行时 switch 的默认臂。 */ export declare const DEVICE_ERROR_STATUS: Readonly>; /** * `device_audit.event` 的闭集(v2 §6 DDL 的 CHECK 清单 + §4.8「标审计行者」,两处逐字对齐)。 * * ⚠️ §4.8 的**遥测**清单比这张表长(`device_connected`/`device_disconnected`/`device_heartbeat_lost`/ * `device_instruction_dispatched`/`device_outcome_unknown`)—— 那些是**计数器/日志**面,不落审计表。 * 只有下面这 9 个是「安全轴事件,必须可追溯查询」。加成员 = 同时改这里 + 两侧 DDL 的 CHECK * (`AUDIT_EVENT_CHECK` 由本数组生成,DDL 自动跟);**且**存量表的旧 CHECK 不会被 * `CREATE TABLE IF NOT EXISTS` 更新 —— 升级探针见 `device-store-sql.ts` 的 * `assertDeviceAuditRebindEventSchema`(能力探测:PG 强制 CHECK、TiDB 默认不执行,探的是**行为** * 不是元数据 —— memory `capability-detection-not-impl-names` 同判)。 */ export declare const DEVICE_AUDIT_EVENTS: readonly ["device_enrolled", "device_revoked", "device_token_rotated", "device_enroll_token_reuse", "device_frame_unexpected", "device_identity_mismatch", "device_superseded", "device_attached_elsewhere", "session_rebound"]; export type DeviceAuditEvent = (typeof DEVICE_AUDIT_EVENTS)[number]; /** v1 平台闭集(AR-R1-8)。win32 = follow-on(§10 车A-4 的 flavor 注)。 */ export declare const DEVICE_PLATFORM_OS: readonly ["darwin", "linux"]; export type DevicePlatformOs = (typeof DEVICE_PLATFORM_OS)[number]; /** 设备生命周期闭集 —— `revoked` 是**终态**(§8-R4:无自动恢复,重注册 = 新 deviceId)。 */ export declare const DEVICE_STATUSES: readonly ["active", "revoked"]; export type DeviceStatus = (typeof DEVICE_STATUSES)[number]; /** O11 复合身份 —— device lane 本地结构对,**不是**全局 principal(见文件头注)。 */ export interface DeviceOwner { readonly tenant: string; readonly subject: string; } export interface DeviceRow { deviceId: string; owner: DeviceOwner; displayName: string; platformOs: DevicePlatformOs; platformArch: string; /** Ed25519 公钥 b64(AR-R1-1:凭据 = 密钥对,wire 上无长期 bearer)。 */ pubkey: string; pubkeyRotatedAtMs: number | null; /** 设备工作区根(hello 声明,enrollment/管理固定;park 期不静默改,task-R2-4)。 */ workspaceRoot: string; workspaceRootRev: number; status: DeviceStatus; revokedAtMs: number | null; revokedBy: string | null; revokeReason: string | null; /** 设备侧 executor 进程 epoch(§5.4 世代闸的设备侧输入)。 */ connEpoch: string | null; replicaId: string | null; /** 连接 lease 世代(task-R1-7 的 CAS fencing 计数器,单调递增)。 */ connGeneration: number; leaseExpiresAtMs: number | null; connectedAtMs: number | null; lastSeenAtMs: number | null; createdAtMs: number; rev: number; } export interface DeviceSessionRow { /** 键 = **根**会话(task-R1-3:后代继承不另绑)。 */ rootSessionId: string; deviceId: string; owner: DeviceOwner; boundAtMs: number; rev: number; } export interface DeviceAuditRow { id: number; atMs: number; event: DeviceAuditEvent; deviceId: string | null; owner: DeviceOwner | null; detail: unknown; } export interface DeviceEnrollTokenRow { /** sha256(明文) 的小写 hex —— 明文一现,**永不落库**。 */ tokenHash: string; /** 签发时绑定的复合身份;注册时强校验(§7-T5 ②)。 */ target: DeviceOwner; createdBy: string; createdAtMs: number; expiresAtMs: number; consumedAtMs: number | null; consumedDeviceId: string | null; /** AR-R1-2 的判别位:响应丢失重放 vs 真重用,全靠这一列。 */ consumedPubkey: string | null; rev: number; } /** 身份键列的字节上界(全仓 190 家族:`sendfile_link.scope` / `agent_roster.name_key` / …)。 */ export declare const DEVICE_KEY_MAX_BYTES = 190; /** `device_id` 列宽(`dev_` + 26 位 Crockford base32 ULID = 30 字节,64 是三倍余量)。 */ export declare const DEVICE_ID_MAX_BYTES = 64; /** `root_session_id` 列宽(提交入口硬拒 >64 的 sessionId,128 留一倍余量)。 */ export declare const DEVICE_SESSION_ID_MAX_BYTES = 128; /** `pubkey` / `token_hash` 列宽(Ed25519 b64 = 44,sha256 hex = 64)。 */ export declare const DEVICE_PUBKEY_MAX_BYTES = 128; /** 内容列宽度(与两侧 DDL 逐字对齐;改列宽必须同时改这里,否则门与列脱钩)。 */ export declare const DEVICE_TEXT_LIMITS: { readonly displayName: 255; readonly platformArch: 32; readonly workspaceRoot: 1024; readonly actor: 190; readonly revokeReason: 512; readonly connEpoch: 64; readonly replicaId: 128; }; export declare function assertDeviceOwnerShape(owner: DeviceOwner): void; export declare function assertDeviceIdShape(deviceId: string): void; export declare function assertRootSessionIdShape(rootSessionId: string): void; export declare function assertDevicePubkeyShape(pubkey: string): void; /** 注册入参的整形门(**三形共用**:InMemory 与两方言从同一处过,门与列宽同源)。 */ export declare function assertNewDeviceShape(input: NewDeviceInput): void; export declare function assertIssueTokenShape(input: IssueEnrollTokenInput): void; export declare function assertRevokeShape(input: RevokeDeviceInput): void; /** O4 换绑入参的整形门(三形共用)。`expectedRev` 收窄为非负整数:它要进 SQL 等值谓词,小数/负数/NaN * 在两方言的 BIGINT 比对下不是「不匹配」而是各自的隐式转换歧义面 —— 在门口拒,方言分歧面消失。 * ⚠️ HTTP 层对坏形先答 400(业务拒绝);这里的抛是**编程/装配错误**的兜底,不是 wire 分支。 */ export declare function assertRebindShape(input: RebindSessionInput): void; export declare function assertAcquireShape(input: AcquireConnectionInput): void; /** token 摘要必须是**规范形**(小写 sha256 hex):大小写混形在 VARBINARY/COLLATE "C" 下是两个键, * 于是同一个明文 token 能被消费两次 —— 这是 §7-T5 ③「一次性 CAS」的直接跳过面。 */ export declare function assertEnrollTokenHashShape(tokenHash: string): void; /** * 闭集列的**读侧**解码 —— 三个 `parse*` 是这些词表的唯一解码点(SQL 店读回的字符串必须经此)。 * * 🔴 认不出的值 = **抛**,不是静默 cast([ref]:安全轴的未知词是 fail-loud 而不是 fail-open)。 * 一行 `status='disabled'` 的设备如果被 `as DeviceStatus` 静默放行,准入链的 `status === "revoked"` * 判据就对它返回「不是吊销的」⇒ 一台被管理面停用的设备照常领活。写侧不可能产出这些值(入参是 * TS 闭集),所以出现即「有人跳过本店直接写库」或「跨版本降级读新词」,两者都必须响亮。 */ export declare function parseDeviceStatus(v: string): DeviceStatus; export declare function parseDevicePlatformOs(v: string): DevicePlatformOs; export declare function parseDeviceAuditEvent(v: string): DeviceAuditEvent; /** TTL / 租约时长必须是**整秒整数**:两方言的 INTERVAL 对小数的取整规则不同(MySQL 的 * `INTERVAL 0.5 SECOND` 与 PG 的 `0.5 * INTERVAL '1 second'` 不同义)⇒ 在门口收窄,方言分歧面消失。 */ export declare function assertWholeSeconds(what: string, seconds: number): void; /** 明文 enrollment token → 落库摘要。明文只在签发响应里出现一次(§6 注册协议)。 */ export declare function hashEnrollToken(plaintext: string): string; /** 签发一个 128-bit 熵的明文 token(b64url,无填充)。 */ export declare function mintEnrollToken(): string; /** * `dev_` —— server 铸(§6)。ULID 而不是 UUIDv4:时间前缀让主键有序(TiDB 侧避免纯随机主键的 * 写热点分散/PG 侧 btree 局部性),且 26 位 Crockford base32 无大小写歧义(我们恒产大写,读侧字节比)。 */ export declare function mintDeviceId(nowMs?: number): string; export interface NewDeviceInput { deviceId: string; owner: DeviceOwner; displayName?: string; platformOs: DevicePlatformOs; platformArch?: string; pubkey: string; workspaceRoot: string; } export interface IssueEnrollTokenInput { tokenHash: string; target: DeviceOwner; createdBy: string; /** 有效期**时长**(秒,自 DB now 起算)。负值 = 立即过期(测试/管理面强制作废用),见文件头注的时间权威段。 */ ttlSeconds: number; } export interface ConsumeEnrollTokenInput { tokenHash: string; /** 请求方**验证过**的复合身份(不取自可自造头);与 token 的 target 逐字节比对。 */ requester: DeviceOwner; device: NewDeviceInput; } /** * enrollment 消费的判别式结果(§6 注册协议 + AR-R1-2)。 * * 🔴 闭集且**理由分得比 wire 细**:wire 上这些全部收敛成一个 `device.enrollment_invalid`(反枚举), * 细分只服务两件事 —— 审计行的 event 选择,与「幂等重放 vs 真重用」这条**安全判别**本身。 */ export type ConsumeEnrollTokenResult = /** CAS 赢家:token 消费 + devices 行插入在**同一事务**里提交。 */ { outcome: "enrolled"; device: DeviceRow; } /** 同 token + **同 pubkey** 重试(响应丢失重放):返回**同一个** deviceId。`device` 为 null = * token 记着 deviceId 但 devices 行已不在(被删/被清)——调用方按 invalid 收尾,不猜。 */ | { outcome: "replayed"; device: DeviceRow | null; } /** 同 token + **异 pubkey**:真重用 ⇒ 拒 + `device_enroll_token_reuse` 审计行(§7-T5 ④,告警级)。 */ | { outcome: "token_reuse"; } /** token 在,但它绑的复合身份不是请求方的(§7-T5 ②)。 */ | { outcome: "principal_mismatch"; } /** token 在、未消费、已过期(TTL,§7-T5 ①)。 */ | { outcome: "expired"; } /** 没有这个 token(或者从来没有过)。 */ | { outcome: "unknown_token"; } /** `(owner_tenant, pubkey)` 已被同租户内**另一台**设备占用(AR-R3-F21:唯一按租户,不全局)。 * token 消费**随事务回滚**——失败的注册不烧 token。 */ | { outcome: "pubkey_taken"; }; export interface BindSessionInput { rootSessionId: string; deviceId: string; /** 验证过的复合身份;与 devices 行的 owner 在**同一事务**内比对(§4.3.2 首绑)。 */ owner: DeviceOwner; } /** §4.3.2 首绑协议的判别式结果 —— 并发争抢**单赢家,无未定义态**。 */ export type BindSessionResult = { outcome: "bound"; row: DeviceSessionRow; } /** 并发输家或重试,但绑的是**同一台**设备 ⇒ 幂等成功。 */ | { outcome: "idempotent"; row: DeviceSessionRow; } /** 根会话已绑**另一台**设备(或另一个 owner)⇒ `device.claim_conflict`。 */ | { outcome: "conflict"; row: DeviceSessionRow; } /** 设备不存在**或**不是请求方的 —— 反枚举同形(见文件头注)。 */ | { outcome: "device_not_found"; } | { outcome: "device_revoked"; }; /** * O4 显式换绑(2026-08-30 v2 稿 §2)。`owner` = **绑定行的**复合身份(HTTP 层已裁决:属主同域或 * explicit operator;operator 代操作时传的也是行的 owner,不是操作员自己)——店内所有谓词恒带它, * 判别位在店里就消失(反枚举,文件头注)。`expectedRev` = 壳从 S-4 读面拿到的前态(强制显式, * 防两操作员并发换绑互踩)。 */ export interface RebindSessionInput { rootSessionId: string; toDeviceId: string; expectedRev: number; owner: DeviceOwner; } /** * O4 换绑的判别式结果(门序 = v2 稿 §2:目标设备在场 → 幂等短路 → 前态 CAS → 审计;活跃 run 门在 * **HTTP 层**、CAS 之前判 —— run store 是另一只店,CAS 语句不兼职)。 */ export type RebindSessionResult = /** CAS 赢家:`rev+1` + `bound_at` 刷新 + `session_rebound` 审计行**同一事务**提交。 */ { outcome: "rebound"; row: DeviceSessionRow; } /** 真幂等:`toDeviceId` ≡ 当前绑定 ∧ `expectedRev` ≡ 当前 rev ⇒ 回显不加 rev、不写审计。 */ | { outcome: "idempotent"; row: DeviceSessionRow; } /** 前态输(rev 不符,含「toDeviceId 同但 rev 错」——前态错就是错)⇒ 携当前行供壳一跳重定位。 */ | { outcome: "rev_conflict"; row: DeviceSessionRow; } /** 绑定行不在**或**不是该 owner 的 —— 反枚举同形(HTTP 层折 404 同串)。 */ | { outcome: "binding_not_found"; } /** 目标设备不在 / 不是该 owner 的 / **已吊销** —— v2 稿 §2 步 2:三臂同折 404(设备 id 也是 * 不可枚举面);吊销不单列 = 判别位在店里就消失,消费层想泄也泄不出来。 */ | { outcome: "device_not_found"; }; export interface RevokeDeviceInput { deviceId: string; owner: DeviceOwner; revokedBy: string; reason?: string | null; } export type RevokeDeviceResult = { outcome: "revoked"; row: DeviceRow; } | { outcome: "already_revoked"; row: DeviceRow; } | { outcome: "not_found"; }; export interface AcquireConnectionInput { deviceId: string; owner: DeviceOwner; /** 设备侧 executor 进程 epoch(hello 帧携带)。 */ connEpoch: string; replicaId: string; /** 租约时长(秒,自 DB now 起算)。 */ leaseSeconds: number; } export type AcquireConnectionResult = { outcome: "acquired"; generation: number; } /** 活租约在别处(§4.6 步 3:fail loud 不 fail wrong)⇒ `device.attached_elsewhere`。 */ | { outcome: "attached_elsewhere"; generation: number; } | { outcome: "device_not_found"; } | { outcome: "device_revoked"; }; export interface HeartbeatConnectionInput { deviceId: string; /** 世代谓词:影响 0 行 = 本连接已失权 ⇒ 调用方主动断开(§4.6 步 2)。 */ generation: number; leaseSeconds: number; } export interface AppendAuditInput { event: DeviceAuditEvent; deviceId?: string | null; owner?: DeviceOwner | null; detail?: unknown; } export interface ListAuditFilter { deviceId?: string; event?: DeviceAuditEvent; owner?: DeviceOwner; limit?: number; } /** 准入读口的返回 —— presence 由**店**在 SQL 谓词里算(时间权威 = DB),不把租约时间交给调用方比。 */ export interface DeviceAdmissionView { row: DeviceRow; online: boolean; } export interface DeviceStore { issueEnrollToken(input: IssueEnrollTokenInput): Promise; /** §6 注册协议:消费 CAS + INSERT devices **同一事务**;失败臂的判别见 {@link ConsumeEnrollTokenResult}。 */ consumeEnrollToken(input: ConsumeEnrollTokenInput): Promise; /** 管理/测试读口(明文 token 永不落库,所以只能按摘要查)。 */ getEnrollToken(tokenHash: string): Promise; /** 🔴 **恒带 owner 谓词**:他人设备返回 null,与不存在同形(反枚举,见文件头注)。 */ readDeviceForAdmission(deviceId: string, owner: DeviceOwner): Promise; /** 管理面/测试用的无 owner 谓词读口 —— **不得**接到任何 principal-scoped 的 wire 面上。 */ getDeviceUnscoped(deviceId: string): Promise; listDevicesByOwner(owner: DeviceOwner): Promise; /** O4/S-4:全量列表(operator 面专用;`getDeviceUnscoped` 同一条纪律 —— 不得接到任何 * principal-scoped 的 wire 面上,消费点必须先过 explicit operator 门)。 */ listDevicesUnscoped(): Promise; revokeDevice(input: RevokeDeviceInput): Promise; acquireConnection(input: AcquireConnectionInput): Promise; /** 世代谓词命中 = true;0 行 = 已失权。 */ heartbeatConnection(input: HeartbeatConnectionInput): Promise; releaseConnection(deviceId: string, generation: number): Promise; bindSession(input: BindSessionInput): Promise; /** O4 显式换绑(v2 稿 §2 步 2/3/5):目标设备门 + 前态 CAS + `session_rebound` 审计行同一事务。 * 活跃 run 门**不在这里**(run store 是另一只店)—— 由 HTTP 层在调用前判(v2 稿 §2 步 4)。 */ rebindSession(input: RebindSessionInput): Promise; getSessionBinding(rootSessionId: string): Promise; /** purge coordinator 座(§6 注②:core 的会话删除是**逐 store 显式调用**,不是 DB 自动扫; * 只建表不接 purge = 删会话后绑定残留 ⇒ sessionId 重用致旧设备粘连/claim conflict/隐私残留)。 */ deleteByRootSession(rootSessionId: string): Promise; appendAudit(input: AppendAuditInput): Promise; listAudit(filter?: ListAuditFilter): Promise; } declare function sameOwner(a: DeviceOwner, b: DeviceOwner): boolean; declare function cloneOwner(o: DeviceOwner): DeviceOwner; /** * 语义 oracle。**单线程 JS 的原子性**替代 SQL 事务:每个方法体内没有 `await`(除了返回值),所以 * 「读-判-写」在事件循环上不可分割 —— 与 SQL 侧的单事务 CAS 语义等价,这也是为什么并发格 * (两个并发消费同一 token)在这一形上必须给出与真库**同一个**赢家/输家分布。 */ export declare class InMemoryDeviceStore implements DeviceStore { private readonly devices; private readonly sessions; private readonly tokens; private readonly audit; private auditSeq; private readonly now; constructor(opts?: { now?: () => number; }); issueEnrollToken(input: IssueEnrollTokenInput): Promise; consumeEnrollToken(input: ConsumeEnrollTokenInput): Promise; getEnrollToken(tokenHash: string): Promise; readDeviceForAdmission(deviceId: string, owner: DeviceOwner): Promise; getDeviceUnscoped(deviceId: string): Promise; listDevicesByOwner(owner: DeviceOwner): Promise; listDevicesUnscoped(): Promise; revokeDevice(input: RevokeDeviceInput): Promise; acquireConnection(input: AcquireConnectionInput): Promise; heartbeatConnection(input: HeartbeatConnectionInput): Promise; releaseConnection(deviceId: string, generation: number): Promise; bindSession(input: BindSessionInput): Promise; rebindSession(input: RebindSessionInput): Promise; getSessionBinding(rootSessionId: string): Promise; deleteByRootSession(rootSessionId: string): Promise; appendAudit(input: AppendAuditInput): Promise; listAudit(filter?: ListAuditFilter): Promise; private isOnline; private pushAudit; /** 返回**拷贝**:调用方拿到的行改了也不能污染店内状态(SQL 侧天然如此,InMemory 必须显式做)。 */ private snapshotDevice; private snapshotSession; } export { sameOwner as sameDeviceOwner, cloneOwner as cloneDeviceOwner }; //# sourceMappingURL=device-store.d.ts.map