UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

63 lines (62 loc) • 3.82 kB
/** * design/182 §4.5 (F-011) — adopting a local rule bucket into a cloud principal: a RECOVERABLE state * machine, not a rename. * * "Local first, connect later, keep everything" is the arc this helper carries. It is assembled from * facts the store already holds — per-add provenance stores NO principal (so a rebind rewrites zero * rows), the actor is a replica axis orthogonal to identity (so every dot stays valid), and the store * seam is backend-agnostic (so the same contract covers a PG bucket, whose row-migration half lives on * the server). What this file adds is ATOMICITY: an exclusive writer lock plus a durable intent marker * whose `phase` field is a monotonically-advanced position — each mutating stage persists its phase * BEFORE the next stage runs, so a crash anywhere resumes idempotently and no stage can lose bytes. * * ① take the write lock (a concurrent redemption/writer is refused loudly while adoption runs) * ② land the durable intent marker → phase 2 * ③ rename the bucket file to the principal's → phase 3 (complete ⟺ target present ∧ source absent) * ④ flip owner resolution — the marker IS the truth `forLocalOwner` reads, so publishing phase 4 is * itself the switch; host configuration follows the marker, never the reverse → phase 4 * ⑤ first sync round against the (empty) cloud bucket — the full local state rides up → phase 5 * ⑥ rewrite the marker into its PERMANENT terminal record. Never deleted: after phase 4 the marker * is the owner-resolution truth, and deleting it would resolve local-owner back to a retired empty * bucket — rules gone from adjudication, new writes forking into a grave. * * A pre-existing TARGET bucket refuses the whole adoption: merging two existing buckets is a sync * join, deliberately not a rename. Rows the far side refuses during ⑤ move to the local quarantine * area — preserved and disclosed, never deleted (the F-011 "nothing is lost" floor). */ import type { RuleOwner } from "../../core/permission-rule-store.js"; import { type PermissionRuleSyncResult, type PermissionRuleSyncTransport } from "../../core/permission-rule-sync.js"; export type AdoptFileRuleStoreResult = /** The full arc completed (this call, or an earlier one — the terminal marker makes it idempotent). */ { status: "adopted"; syncResult?: PermissionRuleSyncResult; } /** A stage failed. The marker holds the last COMPLETED phase; call again to resume from there. * No bytes were lost — that is the state machine's whole contract. */ | { status: "stalled"; phase: 2 | 3 | 4 | 5; error: string; }; /** * Run (or resume) the adoption state machine for the bucket directory `dir`. * * Contract notes for the calling deployment: * - stop (dispose) any live writer over this directory first — the adoption takes the same exclusive * lock, and a live holder is refused loudly rather than interleaved with; * - after the arc completes, run tasks under the NEW principal: a deployment still declaring * local-owner resolves through the terminal marker to the adopted bucket (fail-closed against the * retired one), but the honest configuration names the principal; * - pending approval records are NOT migrated: their recorded owner stays historical, so an old * ticket redeemed after adoption is refused by the existing owner-binding arm — the person simply * sees the card again. That refusal is the invalidation mechanism, not a defect. */ export declare function adoptFilePermissionRuleStore(opts: { dir: string; from: RuleOwner; toPrincipal: string; transport: PermissionRuleSyncTransport; now?: () => number; onError?: (message: string) => void; }): Promise<AdoptFileRuleStoreResult>;