import { FileDurableRulePartitionProvider, type RuleApprovalRecord, type RuleApprovalRecordStore, type StaleRuleApprovalRecord } from "@sema-agent/core"; import { type RuleImportTicket, type RuleTicketPurpose, type RuleTicketReclaimResult, type RuleTicketRedeemResult, type RuleTicketSnapshot } from "./permission-rule-store-sql.js"; import type { PermissionRuleStoreBundle } from "../rules-consent.js"; import { type RuleCandidate } from "@sema-agent/core"; /** * `RuleApprovalRecordStore` 的 File 形。 * * CAS 按 `rev`(core 硬条款,理由逐字见 SQL 侧同名类的头注:只比 state 会让批记录的第二个候选上两次 * 并发重试都以为自己赢了)。 */ export declare class FileRuleApprovalRecordStore implements RuleApprovalRecordStore { private readonly rows; private readonly log; private readonly gate; constructor(dir: string, onError?: (message: string) => void); /** 取证/自证用:本面是否因内部损坏而 fail-closed。 */ get corrupt(): boolean; /** 归还本面 eager 持有的日志描述符(束的 `dispose()` 唯一调用点)。`AppendLog` 上没有 finalizer, * 不显式关就是一只跟到进程末尾的 fd —— `LocalBackend` 反复开合(热重载/多根)时按次泄漏。 * 关后写面抛 `log_closed`(core 语义):**不重开**,因为「这只店已经交还」与「这条命还能写」 * 不能两立,静默重开会让一次 dispose 之后的写落进一份没人再读的日志。 */ close(): void; get(id: string): Promise; create(record: RuleApprovalRecord): Promise; cas(id: string, expectRev: number, next: RuleApprovalRecord): Promise; /** 与 SQL 侧同名方法同义(超帽拒绝时收掉 `prepareCcImport` 已落盘的那条 pending 记录)。 * `state === "pending"` 是硬的:已确认/已兑付的记录是一次真人同意的审计事实。 */ discardPendingRecord(recordId: string): Promise; } /** * CC 导入票的 File 形。四条信任边界(principal 绑定 / TTL / 一次性原子消费 / 载荷绑定)与 SQL 形 * **同语义**;唯一不同的是「原子」由谁保证 —— 那边是引擎行锁,这边是单进程 + 单写者(顶注)。 * * 🔴 **每只店对自己那口钟负责**([ref]):SQL 形的 TTL 轴锚在数据库的钟上(多副本共享的唯一一口); * local 形没有那样一口外部钟,它的钟就是本进程的 —— 而进程墙钟**不单调**,一次 NTP 回拨就能让一张 * 真实已经走满 TTL 的票复活(codex 对抗复审 r1 [high],验真后修)。所以本形自己带一枚**单调锚**: * 本进程铸的票,判定钟 = `铸票墙钟 + 自铸票起单调钟走过的量`;重启后重放回来的行没有锚,如实退回墙钟。 */ export declare class FileRuleImportTicketStore { private readonly rows; private readonly log; private readonly gate; /** 单调钟(判定用)。与 {@link now}(墙钟,记账用)是两只不同的钟,别混。 */ private readonly monotonic; constructor(dir: string, now?: () => number, onError?: (message: string) => void, monotonic?: () => number); private readonly now; /** 取证/自证用:本面是否因内部损坏而 fail-closed。 */ get corrupt(): boolean; /** 归还本面 eager 持有的日志描述符(理由与姊妹面 {@link FileRuleApprovalRecordStore.close} 逐字同源)。 */ close(): void; /** * 这一刻的**判定钟**([ref] r1-F1)。 * * 本进程铸的票 ⇒ `铸票墙钟 + 自铸票起单调钟走过的量`:墙钟被前拨不会误杀(真实只过了几秒的票还活着), * 被回拨也不会续命(单调钟照走)。重启后重放回来的行没有锚 ⇒ 退回墙钟(单调钟的零点跨进程无意义)。 * * **给不了票额外寿命**:投影严格按单调钟前进,真实走满 TTL 必然越过 `expiresAtMs`(负控钉在 * `permission-rule-store-file.test.ts` 的 [ref] r1-F1 族)。 */ private decisionNowMs; mint(input: { ticketId: string; principal: string; approvalId: string; purpose: RuleTicketPurpose; candidates: ReadonlyArray; ttlMs: number; }): Promise; /** 一次性**认领**。五条否定项的判序与 SQL 形逐字相同(unknown → wrong-principal → wrong-purpose → * consumed → expired),于是两形的服务端日志归因可比;wire 面把五类折成同一个 404(零存在性 oracle)。 */ consume(ticketId: string, principal: string, purpose: RuleTicketPurpose): Promise; /** 把认领**放回去**。`mustRemainValidMs` = 放回之后至少还要能用多久 —— 撑不过就不算放回成功 * (理由逐字见 SQL 侧 `release` 的头注:承诺一次必然兑现不了的重试比不承诺更坏)。 * `claimId` = A7 的认领围栏(R1-F1):被顶替的旧持有者动不了现持有者的认领。 */ release(ticketId: string, principal: string, claimId: string, mustRemainValidMs?: number): Promise; /** [ref] A7 CLAIMED → **SETTLED**。判据与 SQL 孪生逐字同源(持有**这一代**认领 ∧ 未落定;过期 * **刻意不设门** —— 理由逐字见 SQL 侧 `settle` 的头注:拒绝记录一件已经发生的事只会把真相丢掉)。 */ settle(ticketId: string, principal: string, claimId: string, outcome: unknown): Promise; /** [ref] A7 三态快照。属主门与车道门同源(别人的票 / 别条车道的票 / 不存在的票一律 `undefined`)。 */ peek(ticketId: string, principal: string, purpose: RuleTicketPurpose): Promise; /** [ref] A7 崩溃窗恢复:抢过一次**可证陈旧**的认领(判据与 SQL 孪生逐字同源 —— 含"过期刻意不设门" * 那一条:过期只挡开始一次兑付,不挡做完一次已经开始的)。抢到之后**换一代认领**。 */ reclaim(ticketId: string, principal: string, purpose: RuleTicketPurpose, minClaimAgeMs: number): Promise; } /** local 车道的三面束 + 生命周期。`dispose()` 释放 core File durable 分区 provider 的写锁(见 {@link createFilePermissionRuleStores})。 */ export interface FilePermissionRuleStores extends PermissionRuleStoreBundle { durable: FileDurableRulePartitionProvider; approvals: FileRuleApprovalRecordStore; tickets: FileRuleImportTicketStore; dispose(): void; } /** * 装配 local 三面束。 * * 🔴 `durable` 必须是**单例**(F1):core 的 `FileDurableRulePartitionProvider` 在第一次取写面时对规则 * 目录取一把进程级写锁,每次 `new` 一个就是第二个持有者 —— 而它对第二个持有者是**响亮拒绝**,不是 * 排队。所以束在 `LocalBackend` 上按字段持有,`LocalBackend.close()` 调 `dispose()` 释放 * (不释放 ⇒ 一次优雅重启会被自己上一条命留下的锁挡在门外,与数据根 `root/LOCK` 同一个病)。 * * `onError` 接的是 core 规则文件的**披露面**(读不出来 / 校验和不符 / 撞上符号链接时它答零规则并 * 说出来)。接住它打一条 warn 是**必须**的:那条路径上「零规则」与「真的没有规则」在读面上同形, * 不留痕就变成一次静默的 fail-closed(用户会突然被反复询问,却没有任何线索)。 */ export declare function createFilePermissionRuleStores(root: string, opts?: { onError?: (message: string) => void; now?: () => number; monotonic?: () => number; }): FilePermissionRuleStores; //# sourceMappingURL=permission-rule-store-file.d.ts.map