@kynver-app/runtime 0.2.1 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +107 -59
- package/dist/context-preview/chat-memory-preview.d.ts +33 -0
- package/dist/doctor/local-store-cli.d.ts +11 -0
- package/dist/doctor/local-store.d.ts +35 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +135 -82
- package/dist/instruction-bundle/contract.js +1 -1
- package/dist/local-run-store/dispatch-receipts.d.ts +70 -0
- package/dist/local-run-store/index.d.ts +10 -0
- package/dist/local-run-store/index.js +50 -0
- package/dist/local-run-store/local-run-store.d.ts +95 -0
- package/dist/local-run-store/remote-surface-flags.d.ts +15 -0
- package/dist/local-run-store/sqlite/sqlite-filesystem-policy.d.ts +16 -0
- package/dist/local-run-store/sqlite/sqlite-local-run-store.d.ts +62 -0
- package/dist/local-run-store/sqlite/sqlite-recovery.d.ts +14 -0
- package/dist/local-run-store/sqlite/sqlite-schema.d.ts +13 -0
- package/dist/local-run-store/store-lifecycle.d.ts +38 -0
- package/dist/local-run-store/surface-flags.d.ts +47 -0
- package/dist/local-run-store/tick-posture.d.ts +27 -0
- package/dist/plan-persist/index.d.ts +1 -1
- package/dist/plan-persist/outbox-durability.d.ts +31 -0
- package/dist/plan-persist/outbox-store.d.ts +18 -0
- package/dist/plan-persist/types.d.ts +4 -0
- package/dist/repo-search.d.ts +13 -0
- package/dist/server/cleanup.js +14 -14
- package/dist/server/default-repo.js +1 -1
- package/dist/server/heavy-verification.js +1 -1
- package/dist/server/memory-cost-enforce.js +2 -2
- package/dist/server/memory-cost.js +1 -1
- package/dist/server/monitor.js +7 -7
- package/dist/server/repo-search.d.ts +1 -1
- package/dist/server/repo-search.js +1 -1
- package/dist/server/worker-policy.js +1 -1
- package/dist/util.d.ts +11 -0
- package/package.json +8 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{createHash as u}from"node:crypto";function o(e){return JSON.stringify(i(e))}function i(e){if(Array.isArray(e))return e.map(i);if(e&&typeof e=="object"){let t={};for(let n of Object.keys(e).sort())t[n]=i(e[n]);return t}return e}function k(e){return`ib1-${u("sha256").update(o(e),"utf8").digest("hex").slice(0,16)}`}function L(e){return`ib-${u("sha256").update(Buffer.from(e)).digest("hex").slice(0,12)}`}function a(e){if(!e)return null;let t=e.trim();if(!t)return null;if(/^[0-9a-fA-F]{64}$/.test(t))return new Uint8Array(t.match(/.{2}/g).map(n=>parseInt(n,16)));try{let n=Buffer.from(t,"base64");if(n.length===32)return new Uint8Array(n)}catch{}return null}function s(e){if(!e||typeof e!="object")return!1;let t=e;if(typeof t.signature!="string"||!t.signature||typeof t.publicKeyId!="string"||!t.publicKeyId)return!1;let n=t.bundle;return!(!n||typeof n!="object"||typeof n.version!="string"||!n.version||typeof n.generatedAt!="string"||!Array.isArray(n.personas)||!n.instructions||typeof n.instructions!="object"||!n.policyThresholds||typeof n.policyThresholds!="object")}var c={"ib-3bab6314f0ba":"53a2040646cd479e1f5f1aea9abf7848ce8b62e32001efb8044dfd90e8ab87ed","ib-26e6c695da06":"bb1700f25b2ee7d7cdcdb9f446f0f44ef2cc22a3096bc161232fb658e7cdaf38"};function l(e,t=process.env){let n=a(t.KYNVER_INSTRUCTION_BUNDLE_PUBLIC_KEY);if(n)return n;let r=c[e];return r?a(r):null}import{createPublicKey as f,verify as m}from"node:crypto";var g=Buffer.from("302a300506032b6570032100","hex");function y(e){return f({key:Buffer.concat([g,Buffer.from(e)]),format:"der",type:"spki"})}function d(e,t){if(!s(e))return{ok:!1,reason:"malformed signed bundle payload"};let n;try{n=Buffer.from(e.signature,"base64url")}catch{return{ok:!1,reason:"signature is not base64url"}}if(n.length!==64)return{ok:!1,reason:"signature is not a 64-byte Ed25519 signature"};try{let r=Buffer.from(o(e.bundle),"utf8");return m(null,r,y(t),n)?{ok:!0,bundle:e.bundle}:{ok:!1,reason:"Ed25519 signature mismatch"}}catch(r){return{ok:!1,reason:`signature verification failed: ${r.message}`}}}function h(e,t=process.env){if(!s(e))return{ok:!1,reason:"malformed signed bundle payload"};let n=l(e.publicKeyId,t);return n?d(e,n):{ok:!1,reason:`no verification key for publicKeyId "${e.publicKeyId}" (not pinned; set KYNVER_INSTRUCTION_BUNDLE_PUBLIC_KEY)`}}var p="embedded-1",I=[{slug:"ghost",displayName:"Ghost",description:"Orchestration persona.",dispatchLane:null,defaultRoleLane:"system"},{slug:"astra",displayName:"Astra",description:"Planning persona.",dispatchLane:"implementation",defaultRoleLane:"plan_author"},{slug:"rhea",displayName:"Rhea",description:"Implementation persona.",dispatchLane:"implementation",defaultRoleLane:"implementer"},{slug:"mnemo",displayName:"Mnemo",description:"Implementation persona.",dispatchLane:"implementation",defaultRoleLane:"implementer"},{slug:"sentinel",displayName:"Sentinel",description:"Review persona.",dispatchLane:"review",defaultRoleLane:"deep_reviewer"},{slug:"pixel",displayName:"Pixel",description:"Implementation persona.",dispatchLane:"implementation",defaultRoleLane:"implementer"},{slug:"schema",displayName:"Schema",description:"Implementation persona.",dispatchLane:"implementation",defaultRoleLane:"implementer"},{slug:"atlas",displayName:"Atlas",description:"Implementation persona.",dispatchLane:"implementation",defaultRoleLane:"runtime_verifier"},{slug:"bridge",displayName:"Bridge",description:"Implementation persona.",dispatchLane:"implementation",defaultRoleLane:"implementer"},{slug:"catalyst",displayName:"Catalyst",description:"Implementation persona.",dispatchLane:"implementation",defaultRoleLane:"implementer"},{slug:"lorentz",displayName:"Lorentz",description:"Review persona.",dispatchLane:"review",defaultRoleLane:"report_reviewer"},{slug:"dalton",displayName:"Dalton",description:"Landing persona.",dispatchLane:"landing",defaultRoleLane:"implementer"}],b={"worker.prompt.core_rules":["Structured final result (recommended): record completion as JSON with summary, files, PR URLs, verification, risks, and blockers.","Completion handoff (required): before you stop, summarize the outcome in your last message and append a heartbeat line with phase `complete` and a structured finalResult object. Commit your work cleanly and open a GitHub PR (draft OK) for substantial changes \u2014 never leave uncommitted changes behind without reporting them.","Review-only workers must not open new implementation PRs \u2014 review the existing PR and record a verdict in the final result. Prefer `gh pr view` / `gh pr diff`; if a local checkout is needed, use a disposable ref or detached worktree, not a checked-out `pr-*` branch.","Keep verification targeted to touched paths; avoid full-monorepo verification unless explicitly requested.","Do not run `npm publish`.","If verification fails, append a heartbeat line immediately with the failing command, reason, and next action."].join(`
|
|
1
|
+
import{createHash as u}from"node:crypto";function o(e){return JSON.stringify(i(e))}function i(e){if(Array.isArray(e))return e.map(i);if(e&&typeof e=="object"){let t={};for(let n of Object.keys(e).sort())t[n]=i(e[n]);return t}return e}function k(e){return`ib1-${u("sha256").update(o(e),"utf8").digest("hex").slice(0,16)}`}function L(e){return`ib-${u("sha256").update(Buffer.from(e)).digest("hex").slice(0,12)}`}function a(e){if(!e)return null;let t=e.trim();if(!t)return null;if(/^[0-9a-fA-F]{64}$/.test(t))return new Uint8Array(t.match(/.{2}/g).map(n=>parseInt(n,16)));try{let n=Buffer.from(t,"base64");if(n.length===32)return new Uint8Array(n)}catch{}return null}function s(e){if(!e||typeof e!="object")return!1;let t=e;if(typeof t.signature!="string"||!t.signature||typeof t.publicKeyId!="string"||!t.publicKeyId)return!1;let n=t.bundle;return!(!n||typeof n!="object"||typeof n.version!="string"||!n.version||typeof n.generatedAt!="string"||!Array.isArray(n.personas)||!n.instructions||typeof n.instructions!="object"||!n.policyThresholds||typeof n.policyThresholds!="object")}var c={"ib-3bab6314f0ba":"53a2040646cd479e1f5f1aea9abf7848ce8b62e32001efb8044dfd90e8ab87ed","ib-26e6c695da06":"bb1700f25b2ee7d7cdcdb9f446f0f44ef2cc22a3096bc161232fb658e7cdaf38","ib-49e76cb8052c":"8fc646b5c4f8f402120477452608398f035c3cddd2e7aa8bb328e97b84dcf6d8"};function l(e,t=process.env){let n=a(t.KYNVER_INSTRUCTION_BUNDLE_PUBLIC_KEY);if(n)return n;let r=c[e];return r?a(r):null}import{createPublicKey as f,verify as m}from"node:crypto";var g=Buffer.from("302a300506032b6570032100","hex");function y(e){return f({key:Buffer.concat([g,Buffer.from(e)]),format:"der",type:"spki"})}function d(e,t){if(!s(e))return{ok:!1,reason:"malformed signed bundle payload"};let n;try{n=Buffer.from(e.signature,"base64url")}catch{return{ok:!1,reason:"signature is not base64url"}}if(n.length!==64)return{ok:!1,reason:"signature is not a 64-byte Ed25519 signature"};try{let r=Buffer.from(o(e.bundle),"utf8");return m(null,r,y(t),n)?{ok:!0,bundle:e.bundle}:{ok:!1,reason:"Ed25519 signature mismatch"}}catch(r){return{ok:!1,reason:`signature verification failed: ${r.message}`}}}function h(e,t=process.env){if(!s(e))return{ok:!1,reason:"malformed signed bundle payload"};let n=l(e.publicKeyId,t);return n?d(e,n):{ok:!1,reason:`no verification key for publicKeyId "${e.publicKeyId}" (not pinned; set KYNVER_INSTRUCTION_BUNDLE_PUBLIC_KEY)`}}var p="embedded-1",I=[{slug:"ghost",displayName:"Ghost",description:"Orchestration persona.",dispatchLane:null,defaultRoleLane:"system"},{slug:"astra",displayName:"Astra",description:"Planning persona.",dispatchLane:"implementation",defaultRoleLane:"plan_author"},{slug:"rhea",displayName:"Rhea",description:"Implementation persona.",dispatchLane:"implementation",defaultRoleLane:"implementer"},{slug:"mnemo",displayName:"Mnemo",description:"Implementation persona.",dispatchLane:"implementation",defaultRoleLane:"implementer"},{slug:"sentinel",displayName:"Sentinel",description:"Review persona.",dispatchLane:"review",defaultRoleLane:"deep_reviewer"},{slug:"pixel",displayName:"Pixel",description:"Implementation persona.",dispatchLane:"implementation",defaultRoleLane:"implementer"},{slug:"schema",displayName:"Schema",description:"Implementation persona.",dispatchLane:"implementation",defaultRoleLane:"implementer"},{slug:"atlas",displayName:"Atlas",description:"Implementation persona.",dispatchLane:"implementation",defaultRoleLane:"runtime_verifier"},{slug:"bridge",displayName:"Bridge",description:"Implementation persona.",dispatchLane:"implementation",defaultRoleLane:"implementer"},{slug:"catalyst",displayName:"Catalyst",description:"Implementation persona.",dispatchLane:"implementation",defaultRoleLane:"implementer"},{slug:"lorentz",displayName:"Lorentz",description:"Review persona.",dispatchLane:"review",defaultRoleLane:"report_reviewer"},{slug:"dalton",displayName:"Dalton",description:"Landing persona.",dispatchLane:"landing",defaultRoleLane:"implementer"}],b={"worker.prompt.core_rules":["Structured final result (recommended): record completion as JSON with summary, files, PR URLs, verification, risks, and blockers.","Completion handoff (required): before you stop, summarize the outcome in your last message and append a heartbeat line with phase `complete` and a structured finalResult object. Commit your work cleanly and open a GitHub PR (draft OK) for substantial changes \u2014 never leave uncommitted changes behind without reporting them.","Review-only workers must not open new implementation PRs \u2014 review the existing PR and record a verdict in the final result. Prefer `gh pr view` / `gh pr diff`; if a local checkout is needed, use a disposable ref or detached worktree, not a checked-out `pr-*` branch.","Keep verification targeted to touched paths; avoid full-monorepo verification unless explicitly requested.","Do not run `npm publish`.","If verification fails, append a heartbeat line immediately with the failing command, reason, and next action."].join(`
|
|
2
2
|
`),"worker.prompt.progress.compact":"Plan progress: when planId is set, report progress with `kynver plan progress --plan <planId> --row <rowKey> --role implementer --status in_progress|running|partial|blocked`. Do not mark rows done from the worker CLI.","worker.prompt.progress.full":["Structured plan progress (required when planId is set):","- Report checkpoints with `kynver plan progress --plan <planId> --row <rowKey> --role implementer --status in_progress|running|partial|blocked`.","- When a slice is finished, emit `partial` with evidence (`--evidence pr:<url>`, `--evidence path:<file>`, or `--evidence command:<cmd>`).","- Do not propose or confirm row `done` from the worker CLI."].join(`
|
|
3
3
|
`),"worker.prompt.merge_gate.compact":"Verification cost control: prefer local verification before requesting CI runs; do not push empty commits to re-trigger CI; record verification evidence on the PR.","worker.prompt.merge_gate.full":["Verification cost control:","- Prefer local verification of touched paths before requesting any CI run.","- Do not push empty commits to re-trigger CI.","- Record verification evidence on the PR before requesting review."].join(`
|
|
4
4
|
`),"worker.prompt.plan_artifacts.compact":"Plan artifacts: when authoring or revising plan documents, open a GitHub PR early and iterate from that PR branch; do not leave the canonical plan only in a local worktree.","worker.prompt.plan_artifacts.full":["Plan artifacts (when authoring or revising plan documents):","- Create a feature branch and open a GitHub PR (draft OK) before substantial drafting; commit and push the plan file.","- Iterate on that PR branch and link the PR URL on the related task and progress evidence."].join(`
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type LocalRunStoreSurfaceMode } from "./surface-flags.js";
|
|
2
|
+
export interface DispatchReceiptIdentity {
|
|
3
|
+
taskId: string;
|
|
4
|
+
leaseToken: string;
|
|
5
|
+
}
|
|
6
|
+
export interface DispatchClaimIntent extends DispatchReceiptIdentity {
|
|
7
|
+
runId: string;
|
|
8
|
+
workerName: string;
|
|
9
|
+
provider: string | null;
|
|
10
|
+
}
|
|
11
|
+
export declare function dispatchReceiptsMode(env?: NodeJS.ProcessEnv): LocalRunStoreSurfaceMode;
|
|
12
|
+
export declare function recordDispatchClaimReceipt(intent: DispatchClaimIntent): void;
|
|
13
|
+
export declare function recordDispatchSpawnedReceipt(identity: DispatchReceiptIdentity): void;
|
|
14
|
+
export declare function recordDispatchReleasedReceipt(identity: DispatchReceiptIdentity, reasonClass: string): void;
|
|
15
|
+
export declare function recordCompletionSentReceipt(identity: DispatchReceiptIdentity): void;
|
|
16
|
+
export declare function recordCompletionAckedReceipt(identity: DispatchReceiptIdentity, outcome: "acknowledged" | "rejected"): void;
|
|
17
|
+
/**
|
|
18
|
+
* Shadow invariant: the worker record says this completion still needs to be
|
|
19
|
+
* POSTed, but the store already holds a `completion-acked` receipt — the ack
|
|
20
|
+
* was recorded locally and then lost from worker.json (torn ack / replay).
|
|
21
|
+
* Report-only: the POST still happens (server idempotency absorbs it); the
|
|
22
|
+
* divergence is counted so the shadow window can prove how often replays are
|
|
23
|
+
* store-detectable before D1b short-circuits them.
|
|
24
|
+
*/
|
|
25
|
+
export declare function noteCompletionReplayIfAcked(identity: DispatchReceiptIdentity): boolean;
|
|
26
|
+
/** Reason-class extraction for released receipts (never the full detail text). */
|
|
27
|
+
export declare function releaseReasonClass(failureDetail: string | null | undefined): string;
|
|
28
|
+
export interface DispatchReceiptInvariantScan {
|
|
29
|
+
scannedClaims: number;
|
|
30
|
+
/** Claim receipts past the stall window with no spawned/released receipt and no live worker. */
|
|
31
|
+
strandedClaimCandidates: number;
|
|
32
|
+
/** Bracketed/aged receipt rows removed this scan (retention). */
|
|
33
|
+
prunedReceipts: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Report-only shadow scan (review-N1 hardened): the window is recency-bounded
|
|
37
|
+
* (`recordedAfter`, 7 days) AND self-pruning — bracketed (spawned/released)
|
|
38
|
+
* claim trios older than 24 h are deleted each scan, so old bracketed claims
|
|
39
|
+
* can never pin the window and blind the counter on a busy box.
|
|
40
|
+
*
|
|
41
|
+
* A claim with a LIVE worker is never a candidate — that is the crux case
|
|
42
|
+
* (crash between spawn and the `spawned` receipt), and flagging it would
|
|
43
|
+
* train operators to distrust the counter. A claim whose task already holds a
|
|
44
|
+
* `completion-acked` receipt is never a candidate either (review N4: a
|
|
45
|
+
* crux-crashed claim whose worker went on to finish and report is closed
|
|
46
|
+
* evidence, not a stranding). Liveness is delegated via `hasLiveWorker` so
|
|
47
|
+
* this module stays free of run-store imports.
|
|
48
|
+
*
|
|
49
|
+
* Known false-positive class (documented, closed by D1b's worktree-path
|
|
50
|
+
* probe): a crash between `provider.start` and `saveWorker` leaves a live
|
|
51
|
+
* ORPHAN child with no worker record — the worker.json-based probe cannot see
|
|
52
|
+
* it, so it counts as stranded until the orphan exits.
|
|
53
|
+
*
|
|
54
|
+
* Scope: `spawnClaimed` dispatches only. land_pr dispatches carry no claim
|
|
55
|
+
* receipts (plan D1a item 1); their aborts share `abortClaimedSpawn` and may
|
|
56
|
+
* emit `released` receipts with no matching claim — harmless, the scan
|
|
57
|
+
* iterates claims only.
|
|
58
|
+
*/
|
|
59
|
+
export declare function scanDispatchReceiptInvariants(options?: {
|
|
60
|
+
nowMs?: number;
|
|
61
|
+
strandedAfterMs?: number;
|
|
62
|
+
hasLiveWorker?: (intent: {
|
|
63
|
+
runId: string;
|
|
64
|
+
workerName: string;
|
|
65
|
+
}) => boolean;
|
|
66
|
+
}): DispatchReceiptInvariantScan;
|
|
67
|
+
/** Counters snapshot for the operator-tick posture (report-only). */
|
|
68
|
+
export declare function dispatchReceiptCounters(): Record<string, number>;
|
|
69
|
+
/** Test-only: reset in-memory shadow counters. */
|
|
70
|
+
export declare function __resetDispatchReceiptCountersForTests(): void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { AdmissionReservation, LocalRunStore, OperationReceiptRecord, ProcessIdentityRecord, RecoveryCheckpointRecord, ReserveOutcome, } from "./local-run-store.js";
|
|
2
|
+
export { classifyDatabaseDirectory, type ClassifyOptions, type FilesystemClassification, } from "./sqlite/sqlite-filesystem-policy.js";
|
|
3
|
+
export { LOCAL_SCHEMA_VERSION, SchemaDowngradeRefusedError, } from "./sqlite/sqlite-schema.js";
|
|
4
|
+
export { FilesystemPolicyError, SqliteLocalRunStore, type OpenSqliteLocalRunStoreOptions, } from "./sqlite/sqlite-local-run-store.js";
|
|
5
|
+
export { openSqliteLocalRunStoreWithRecovery, type OpenWithRecoveryResult, } from "./sqlite/sqlite-recovery.js";
|
|
6
|
+
export { __resetLocalRunStoreLifecycleForTests, __setLocalRunStoreLifecycleTestOverrides, closeLocalRunStore, getLocalRunStore, getLocalRunStoreLifecycleStatus, LOCAL_RUN_STORE_OPEN_RETRY_MS, probeLocalRunStore, type LocalRunStoreLifecycleStatus, } from "./store-lifecycle.js";
|
|
7
|
+
export { __resetDispatchReceiptCountersForTests, dispatchReceiptCounters, dispatchReceiptsMode, noteCompletionReplayIfAcked, recordCompletionAckedReceipt, recordCompletionSentReceipt, recordDispatchClaimReceipt, recordDispatchReleasedReceipt, recordDispatchSpawnedReceipt, releaseReasonClass, scanDispatchReceiptInvariants, type DispatchClaimIntent, type DispatchReceiptIdentity, type DispatchReceiptInvariantScan, } from "./dispatch-receipts.js";
|
|
8
|
+
export { anyLocalRunStoreSurfaceActive, LOCAL_RUN_STORE_SURFACES, parseLocalRunStoreSurfaceMode, resolveLocalRunStoreSurfaceFlags, type LocalRunStoreSurfaceDefinition, type LocalRunStoreSurfaceFlags, type LocalRunStoreSurfaceKey, type LocalRunStoreSurfaceMode, type ResolvedLocalRunStoreSurfaceFlags, } from "./surface-flags.js";
|
|
9
|
+
export { buildLocalRunStoreTickPosture, type LocalRunStoreTickPosture, } from "./tick-posture.js";
|
|
10
|
+
export { __resetRemoteSurfaceFlagsCacheForTests, extractLocalRunStoreFlagsFromOperatorTickResponse, getRemoteSurfaceFlags, persistRemoteSurfaceFlags, remoteSurfaceFlagsPath, type RemoteSurfaceModeMap, } from "./remote-surface-flags.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
var a=(r,e,t)=>()=>{if(t)throw t[0];try{return r&&(e=r(r=0)),e}catch(n){throw t=[n],n}};import{existsSync as Cn,mkdirSync as Qr,readFileSync as Zr,readdirSync as An,renameSync as et,rmSync as rt,statSync as On,writeFileSync as tt}from"node:fs";import{randomBytes as nt}from"node:crypto";import ot from"node:path";function De(r){console.error(r),process.exit(1)}function N(r,e){try{return JSON.parse(Zr(r,"utf8"))}catch(t){if(arguments.length>1)return e;De(`failed to read ${r}: ${t.message}`)}}function oe(r,e){Qr(ot.dirname(r),{recursive:!0});let t=`${r}.${process.pid}.${nt(4).toString("hex")}.tmp`;try{tt(t,`${JSON.stringify(e,null,2)}
|
|
2
|
+
`),et(t,r)}catch(n){try{rt(t,{force:!0})}catch{}throw n}}var p=a(()=>{"use strict"});var st,Nn,Me=a(()=>{"use strict";st=["ANTHROPIC_API_KEY","ANALYST_API_KEY","RECRUITER_API_KEY","AUTH_SECRET","NEXTAUTH_SECRET","DATABASE_URL","PRODUCTION_DATABASE_URL","KYNVER_PRODUCTION_DATABASE_URL","REDIS_URL","GOOGLE_CLIENT_SECRET","GITHUB_CLIENT_SECRET","KYNVER_API_KEY","KYNVER_SERVICE_SECRET","KYNVER_RUNTIME_SECRET","KYNVER_CRON_SECRET","OPENCLAW_CRON_SECRET","QSTASH_TOKEN","QSTASH_CURRENT_SIGNING_KEY","QSTASH_NEXT_SIGNING_KEY","TOOL_SECRETS_KEK","TOOL_EXECUTOR_DISPATCH_SECRET","CLOUDFLARE_API_TOKEN","STRIPE_SECRET_KEY","STRIPE_WEBHOOK_SECRET","STRIPE_IDENTITY_WEBHOOK_SECRET","VOYAGE_API_KEY","PERPLEXITY_API_KEY","FRED_API_KEY","FMP_API_KEY","CURSOR_API_KEY"],Nn=new Set(st)});var se=a(()=>{"use strict";p();Me()});import{homedir as Ue}from"node:os";import We from"node:path";function it(r){return r==="~"?Ue():r.startsWith("~/")||r.startsWith("~\\")?We.join(Ue(),r.slice(2)):r}function ie(r){return We.resolve(it(r))}var P=a(()=>{"use strict"});var ae=a(()=>{"use strict";se();P()});function x(r){let e=(r??"").trim().toLowerCase();return e==="ghost"||e==="forge"?e:e.includes("forge")?"forge":e.includes("ghost")||e.includes("openclaw")?"ghost":"forge"}function B(r,e){return r[e]?.trim()||null}function Ke(r){let e=r?.split(":")[0]?.trim();return e?x(e):null}function O(r=process.env,e={}){let t=[],n=Ke(e.boxId),o=Ke(B(r,"KYNVER_BOX_ID")),s=e.boxKind?.trim();if(s)return n&&n!==x(s)&&t.push(`config.boxId=${e.boxId} conflicts with boxKind=${s}; using explicit boxKind=${s}`),{boxKind:x(s),source:"config",slugInferenceBlocked:!1,warnings:t};let i=B(r,"KYNVER_BOX_KIND");if(i)return o&&o!==x(i)&&t.push(`KYNVER_BOX_ID=${B(r,"KYNVER_BOX_ID")} conflicts with KYNVER_BOX_KIND=${i}; using explicit KYNVER_BOX_KIND=${i}`),{boxKind:x(i),source:"env",slugInferenceBlocked:!1,warnings:t};if(n)return{boxKind:n,source:"config",slugInferenceBlocked:!1,warnings:t};if(o)return{boxKind:o,source:"env",slugInferenceBlocked:!1,warnings:t};let c=B(r,"KYNVER_AGENT_OS_SLUG");return c&&t.push(`KYNVER_AGENT_OS_SLUG=${c} is a workspace slug, not box identity \u2014 set boxKind via \`kynver setup --box-kind forge|ghost\` or KYNVER_BOX_KIND (defaulting box kind to forge)`),{boxKind:"forge",source:"default",slugInferenceBlocked:!!c,warnings:t}}var _=a(()=>{"use strict"});function ce(r,e){return r==="forge"?4:Math.max(1,e)}function F(r,e){return r==="forge"?Math.min(Math.max(1,e),4):Math.max(1,e)}var $=a(()=>{"use strict"});var Be=a(()=>{"use strict"});var zn,Xn,le=a(()=>{"use strict";zn=25*1024*1024*1024,Xn=12*1024*1024*1024});var to,no,Fe=a(()=>{"use strict";le();to=5*1024*1024*1024,no=10*1024*1024*1024});var lo,uo,ue=a(()=>{"use strict";Fe();le();lo=30*1024*1024*1024,uo=15*1024*1024*1024});var me=a(()=>{"use strict";pe();p()});var $e=a(()=>{"use strict";me();p()});var He=a(()=>{"use strict"});var je=a(()=>{"use strict";He();p()});var Ye=a(()=>{"use strict"});var Ve=a(()=>{"use strict";Ye()});var Ge=a(()=>{"use strict";Ve();p()});var qe=a(()=>{"use strict"});var Je=a(()=>{"use strict"});var ze=a(()=>{"use strict"});var fe=a(()=>{"use strict"});var Xe=a(()=>{"use strict"});var Qe=a(()=>{"use strict";fe();Xe()});var Ze=a(()=>{"use strict";je();Ge();qe();Je();se();ze();Qe();fe();p()});var er=a(()=>{"use strict";Ze()});function rr(r,e={}){let t=e.perWorkerMemBytes??ge,n=e.memReserveBytes??ye,o=e.memUtilization??he,s=Math.max(0,Math.floor(r*o)-n),i=Math.max(1,Math.floor(s/t));return Math.min(i,H)}var ge,ye,he,H,tr=a(()=>{"use strict";Be();v();_();$();j();ue();me();$e();er();p();ge=500*1024*1024,ye=4*1024*1024*1024,he=.85,H=64});function L(r,e){let t=Number(r);return!Number.isFinite(t)||t<=0?e:Math.floor(t)}function Re(r){let e=r.config??{},t=r.env??process.env,n=L(e.perWorkerMemBytes,ge),o=L(e.memReserveBytes,ye),s=Math.min(1,Math.max(.1,Number(e.memUtilization)>0?Number(e.memUtilization):he)),i=rr(r.totalMemBytes,{perWorkerMemBytes:n,memReserveBytes:o,memUtilization:s});if(r.configuredMaxWorkersOverride!==void 0&&r.configuredMaxWorkersOverride!==null)return{configuredMaxWorkers:L(r.configuredMaxWorkersOverride,i),autoCap:i,workerCapSource:"workspace_override"};if(e.maxConcurrentWorkers!==void 0&&e.maxConcurrentWorkers!==null){let u=L(e.maxConcurrentWorkers,0)||null;if(u)return{configuredMaxWorkers:Math.min(u,H),autoCap:i,workerCapSource:"config"}}let l=(t.KYNVER_MAX_WORKERS_INTENTIONAL==="1"||t.KYNVER_MAX_WORKERS_INTENTIONAL==="true")&&t.KYNVER_MAX_WORKERS&&L(t.KYNVER_MAX_WORKERS,0)||null;return l?{configuredMaxWorkers:Math.min(l,H),autoCap:i,workerCapSource:"env"}:{configuredMaxWorkers:null,autoCap:i,workerCapSource:"auto"}}var j=a(()=>{"use strict";tr()});import{homedir as dt}from"node:os";import mt from"node:path";var nr,Se=a(()=>{"use strict";nr=mt.join(dt(),".kynver",".env")});var or=a(()=>{"use strict"});var sr=a(()=>{"use strict";or()});var ir=a(()=>{"use strict"});var be=a(()=>{"use strict";sr();ir()});var ar=a(()=>{"use strict"});var cr=a(()=>{"use strict";ar()});var Y=a(()=>{"use strict";Se();P();v();be();cr()});var lr=a(()=>{"use strict";_();_()});var ur=a(()=>{"use strict";Se();be();Y()});import{existsSync as y,mkdirSync as yt,readdirSync as V,renameSync as dr,statSync as ht}from"node:fs";import h from"node:path";function pr(r){try{return ht(r).isDirectory()}catch{return!1}}function Rt(r){if(!y(r))return!0;for(let e of mr){let t=h.join(r,e);if(y(t)&&(!pr(t)||V(t).length>0))return!1}return!0}function St(r){let e=h.join(r,"runs");if(!y(e)||!pr(e))return!1;for(let t of V(e,{withFileTypes:!0})){if(!t.isDirectory()||t.name==="runs")continue;let n=h.join(e,t.name,"run.json");if(N(n,void 0)?.id)return!0}return!1}function bt(r){let e=h.join(r,"runs");if(!y(e))return!1;for(let t of V(e,{withFileTypes:!0})){if(!t.isDirectory())continue;if(N(h.join(e,t.name,"run.json"),void 0)?.boxKind==="ghost")return!0}return!1}function kt(r){let e=h.join(r,"ghost");return y(e)?!y(h.join(e,"runs")):bt(r)}function fr(r){return St(r.sharedRoot)?Rt(r.forgeRoot)?kt(r.sharedRoot)?{action:"blocked",blocker:"Forge legacy harness migration blocked: Ghost pool is co-hosted on this host (ghost namespace or ghost-attributed runs under ~/.kynver/harness). Drain in-flight Ghost runs from the shared root, or get operator sign-off for a fresh Forge cutover before enabling box-scoped isolation."}:{action:"migrate"}:{action:"none"}:{action:"none"}}function gr(r,e){yt(e,{recursive:!0});for(let t of mr){let n=h.join(r,t),o=h.join(e,t);if(y(n)){if(y(o)&&V(o).length>0)throw new Error(`Forge legacy harness migration failed: ${o} is not empty`);if(y(o)){dr(n,o);continue}dr(n,o)}}}var mr,yr=a(()=>{"use strict";p();mr=["runs","worktrees"]});import{existsSync as m}from"node:fs";import{homedir as xt}from"node:os";import d from"node:path";function ke(r=process.env){return r.HOME?.trim()||xt()}function xe(r=process.env){return d.join(ke(r),".kynver","harness")}function G(r){let e=d.resolve(ie(r.trim()));for(;_t.has(d.basename(e));)e=d.dirname(e);return e}function vt(r,e=process.env){return d.join(xe(e),r)}function Et(r=process.env){let e=xe(r);if(m(d.join(e,"runs")))return!0;if(m(e)&&!m(d.join(e,"ghost"))&&!m(d.join(e,"forge")))return m(e);let t=d.join(ke(r),hr);return m(t)}function q(r){let e=r.env??process.env,t=e.KYNVER_HARNESS_ROOT?.trim()||e.OPUS_HARNESS_ROOT?.trim();if(t)return{harnessRoot:G(t),source:"env"};let n=r.configuredHarnessRoot?.trim();if(n)return{harnessRoot:G(n),source:"config"};let o=vt(r.boxKind,e);if(m(d.join(o,"runs"))||m(o)&&!Ct(o))return{harnessRoot:o,source:"box_scoped"};let i=xe(e);if(r.boxKind==="forge"){let c=fr({sharedRoot:i,forgeRoot:o});if(c.action==="blocked")return{harnessRoot:o,source:"box_scoped",migrationBlocker:c.blocker};if(c.action==="migrate")return r.allowForgeLegacyMigration!==!1&&gr(i,o),{harnessRoot:o,source:"legacy_migrated"}}if(r.boxKind==="ghost"&&Et(e)){if(m(d.join(i,"runs")))return{harnessRoot:i,source:"legacy_shared"};if(m(i)&&!m(d.join(i,"ghost"))&&!m(d.join(i,"forge")))return{harnessRoot:i,source:"legacy_shared"};let l=d.join(ke(e),hr);if(m(l))return{harnessRoot:l,source:"legacy_shared"}}return{harnessRoot:o,source:"box_scoped"}}function Ct(r){if(!m(r))return!0;for(let e of["runs","worktrees"]){let t=d.join(r,e);if(m(t))return!1}return!0}var hr,_t,_e=a(()=>{"use strict";P();yr();hr=d.join(".openclaw","harness");_t=new Set(["runs","worktrees"])});import Rr from"node:os";function J(r){return r?.trim()?r.trim():null}function At(r,e){let t=e.trim().toLowerCase()||"host";return`${r}-${t}`.replace(/[^a-z0-9._-]+/g,"-")}function Ot(r){return r.KYNVER_DISPATCH_STRICT_IDENTITY==="1"||r.KYNVER_DISPATCH_STRICT_IDENTITY==="true"||r.KYNVER_DAEMON_STRICT_IDENTITY==="1"||r.KYNVER_DAEMON_STRICT_IDENTITY==="true"}function z(r=S(),e=process.env,t={}){let n=O(e,r),o=[...n.warnings],s=[],i=!!(r.boxKind?.trim()||J(e.KYNVER_BOX_KIND)),c=t.strict===!0||Ot(e);if((t.requireExplicitBoxKind||c)&&!i&&s.push("boxKind is not persisted in ~/.kynver/config.json and KYNVER_BOX_KIND is unset \u2014 run `kynver setup --box-kind forge|ghost` before dispatch"),n.slugInferenceBlocked&&!i){let Z="ambiguous box identity: KYNVER_AGENT_OS_SLUG is set without KYNVER_BOX_KIND or config.boxKind";c||t.requireExplicitBoxKind?s.push(Z):o.push(`${Z} \u2014 defaulting box kind to forge`)}let l=J(r.runnerId),u=J(e.KYNVER_RUNTIME_ID)??J(e.OPENCLAW_RUNTIME_ID),g=l??u??At(n.boxKind,Rr.hostname()),C=l?"config":u?"env":"derived";C==="derived"&&o.push(`runnerId not persisted \u2014 using derived id "${g}"; run \`kynver setup --box-kind ${n.boxKind}\` to persist runner identity`);let I=Re({config:r,env:e,totalMemBytes:Rr.totalmem()}),Q=F(n.boxKind,I.configuredMaxWorkers??ce(n.boxKind,I.autoCap)),k=q({boxKind:n.boxKind,configuredHarnessRoot:r.harnessRoot,env:e});return k.migrationBlocker&&s.push(k.migrationBlocker),s.length>0?{ok:!1,identity:null,errors:s,warnings:o}:{ok:!0,identity:{boxKind:n.boxKind,boxKindSource:n.source,runnerId:g,runnerIdSource:C,maxConcurrentWorkers:Q,workerCapSource:I.workerCapSource,harnessRoot:k.harnessRoot,harnessRootSource:k.source},errors:s,warnings:o}}var X=a(()=>{"use strict";v();_();$();j();_e()});var Sr=a(()=>{"use strict";ae();_();lr();v();Y();ur();p();X()});import{existsSync as Nt,mkdirSync as ca,readFileSync as Pt,writeFileSync as la}from"node:fs";import{homedir as Lt,totalmem as da}from"node:os";import ve from"node:path";function S(){if(!Nt(br))return{};try{return JSON.parse(Pt(br,"utf8"))}catch{return{}}}var kr,br,_a,va,Ea,v=a(()=>{"use strict";ae();P();p();_();$();j();ue();Y();Sr();X();kr=ve.join(Lt(),".kynver"),br=ve.join(kr,"config.json"),_a=ve.join(kr,"credentials");va=500*1024*1024,Ea=4*1024*1024*1024});function xr(r){return G(r)}function _r(){let r=process.env.KYNVER_HARNESS_ROOT||process.env.OPUS_HARNESS_ROOT;if(r)return xr(r);let e=S(),t=e.harnessRoot?.trim();if(t)return xr(t);let n=z(e,process.env);return n.identity?n.identity.harnessRoot:q({boxKind:"forge",configuredHarnessRoot:null}).harnessRoot}var pe=a(()=>{"use strict";v();_e();X();p()});import{existsSync as Nr,realpathSync as Pr,readFileSync as Lr}from"node:fs";import Pe from"node:path";var Dr=new Set(["ext4","ext3","ext2","btrfs","xfs","f2fs","zfs","tmpfs","overlay","overlayfs"]),Mr=new Set(["9p","drvfs","nfs","nfs4","cifs","smb","smbfs","smb3","sshfs","fuse.sshfs","davfs","davfs2","glusterfs","ceph","lustre","afs","vboxsf","prl_fs","virtiofs"]);function Ur(){try{return Lr("/proc/self/mounts","utf8")}catch{return null}}function Wr(r){let e=Pe.resolve(r);for(let t=0;t<64;t+=1){if(Nr(e))try{return Pr.native(e)}catch{return e}let n=Pe.dirname(e);if(n===e)return e;e=n}return e}function Kr(r){return r.replace(/\\(\d{3})/g,(e,t)=>String.fromCharCode(Number.parseInt(t,8)))}function Br(r){let e=[];for(let t of r.split(`
|
|
3
|
+
`)){let n=t.trim().split(/\s+/);n.length<3||e.push({mountPoint:Kr(n[1]),fsType:n[2]})}return e}function Fr(r,e){let t=null;for(let n of e){let o=n.mountPoint.endsWith("/")?n.mountPoint:`${n.mountPoint}/`;(r===n.mountPoint||`${r}/`.startsWith(o))&&(!t||n.mountPoint.length>t.mountPoint.length)&&(t=n)}return t}function re(r,e){let n=(e?.resolveRealPath??Wr)(r),o=(e?.mountsSource??Ur)();if(o===null)return{verdict:"rejected",reason:"Mount information unavailable \u2014 the filesystem type cannot be established safely, so WAL mode is refused (fail closed).",filesystemType:null,mountPoint:null,resolvedPath:n};let s=Fr(n,Br(o));if(!s)return{verdict:"rejected",reason:"No mount entry covers the resolved database path \u2014 refusing WAL mode (fail closed).",filesystemType:null,mountPoint:null,resolvedPath:n};let i=s.fsType.toLowerCase();return Mr.has(i)||i.startsWith("fuse.")?{verdict:"rejected",reason:`Filesystem '${i}' is network-like or crosses an OS boundary; SQLite WAL locking is not safe there.`,filesystemType:i,mountPoint:s.mountPoint,resolvedPath:n}:Dr.has(i)?{verdict:"accepted",reason:`Local filesystem '${i}' accepted for WAL mode.`,filesystemType:i,mountPoint:s.mountPoint,resolvedPath:n}:{verdict:"rejected",reason:`Filesystem '${i}' is unknown to the qualification policy; WAL mode is refused rather than guessed (fail closed).`,filesystemType:i,mountPoint:s.mountPoint,resolvedPath:n}}var te=1,K=class extends Error{constructor(e,t){super(`local-run-store schema version ${e} is newer than this runtime supports (${t}); refusing to open. Upgrade the runtime or quarantine the database deliberately.`),this.name="SchemaDowngradeRefusedError"}},$r=[`CREATE TABLE IF NOT EXISTS local_meta (
|
|
4
|
+
key TEXT PRIMARY KEY,
|
|
5
|
+
value TEXT NOT NULL
|
|
6
|
+
) STRICT`,`CREATE TABLE IF NOT EXISTS process_identity (
|
|
7
|
+
process_key TEXT PRIMARY KEY,
|
|
8
|
+
pid INTEGER NOT NULL,
|
|
9
|
+
runtime_version TEXT NOT NULL,
|
|
10
|
+
node_version TEXT NOT NULL,
|
|
11
|
+
started_at TEXT NOT NULL,
|
|
12
|
+
registered_at TEXT NOT NULL
|
|
13
|
+
) STRICT`,`CREATE TABLE IF NOT EXISTS admission_reservations (
|
|
14
|
+
reservation_key TEXT PRIMARY KEY,
|
|
15
|
+
owner TEXT NOT NULL,
|
|
16
|
+
expires_at TEXT NOT NULL,
|
|
17
|
+
created_at TEXT NOT NULL
|
|
18
|
+
) STRICT`,`CREATE TABLE IF NOT EXISTS operation_receipts (
|
|
19
|
+
idempotency_key TEXT PRIMARY KEY,
|
|
20
|
+
operation TEXT NOT NULL,
|
|
21
|
+
result_json TEXT,
|
|
22
|
+
recorded_at TEXT NOT NULL
|
|
23
|
+
) STRICT`,`CREATE TABLE IF NOT EXISTS recovery_checkpoints (
|
|
24
|
+
scope TEXT NOT NULL,
|
|
25
|
+
seq INTEGER NOT NULL,
|
|
26
|
+
payload_json TEXT NOT NULL,
|
|
27
|
+
created_at TEXT NOT NULL,
|
|
28
|
+
PRIMARY KEY (scope, seq)
|
|
29
|
+
) STRICT`];function Hr(r){if(!r.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'local_meta'").get())return null;let t=r.prepare("SELECT value FROM local_meta WHERE key = 'schema_version'").get();if(!t?.value)return null;let n=Number.parseInt(t.value,10);return Number.isFinite(n)?n:null}function Le(r,e){let t=Hr(r);if(t!==null&&t>1)throw new K(t,1);r.exec("BEGIN IMMEDIATE");try{for(let n of $r)r.exec(n);r.prepare("INSERT INTO local_meta (key, value) VALUES ('schema_version', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(String(1)),r.prepare("INSERT INTO local_meta (key, value) VALUES ('schema_applied_at', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(e),r.exec("COMMIT")}catch(n){throw r.exec("ROLLBACK"),n}}import{mkdirSync as jr}from"node:fs";import Yr from"node:path";import{DatabaseSync as Vr}from"node:sqlite";var T=class extends Error{constructor(e){super(`local-run-store refused: ${e}`),this.name="FilesystemPolicyError"}},Gr="local-run-store.sqlite",qr=5e3,A=class r{db;now;databasePath;constructor(e,t,n){this.db=e,this.databasePath=t,this.now=n}static open(e){let t=re(e.directory,e.classifyOptions);if(t.verdict!=="accepted")throw new T(t.reason);jr(e.directory,{recursive:!0,mode:448});let n=Yr.join(e.directory,e.filename??Gr),o=new Vr(n,{timeout:e.busyTimeoutMs??qr}),s=e.now??(()=>new Date);try{o.exec("PRAGMA journal_mode = WAL");let i=o.prepare("PRAGMA journal_mode").get();if(i?.journal_mode?.toLowerCase()!=="wal")throw new T(`journal_mode did not switch to WAL (got '${i?.journal_mode}') \u2014 filesystem unsuitable.`);o.exec("PRAGMA synchronous = NORMAL"),Le(o,s().toISOString())}catch(i){throw o.close(),i}return new r(o,n,s)}inImmediateTransaction(e){this.db.exec("BEGIN IMMEDIATE");try{let t=e();return this.db.exec("COMMIT"),t}catch(t){throw this.db.exec("ROLLBACK"),t}}registerProcessIdentity(e){let t=this.now().toISOString();return this.inImmediateTransaction(()=>{this.db.prepare(`INSERT INTO process_identity
|
|
30
|
+
(process_key, pid, runtime_version, node_version, started_at, registered_at)
|
|
31
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
32
|
+
ON CONFLICT(process_key) DO UPDATE SET
|
|
33
|
+
pid = excluded.pid,
|
|
34
|
+
runtime_version = excluded.runtime_version,
|
|
35
|
+
node_version = excluded.node_version,
|
|
36
|
+
started_at = excluded.started_at,
|
|
37
|
+
registered_at = excluded.registered_at`).run(e.processKey,e.pid,e.runtimeVersion,e.nodeVersion,e.startedAt,t)}),{...e,registeredAt:t}}getProcessIdentity(e){let t=this.db.prepare(`SELECT process_key, pid, runtime_version, node_version, started_at, registered_at
|
|
38
|
+
FROM process_identity WHERE process_key = ?`).get(e);return t?{processKey:t.process_key,pid:t.pid,runtimeVersion:t.runtime_version,nodeVersion:t.node_version,startedAt:t.started_at,registeredAt:t.registered_at}:null}tryReserveAdmission(e,t,n,o){let s=o??this.now(),i=new Date(s.getTime()+n).toISOString();return this.inImmediateTransaction(()=>{let c=this.db.prepare("SELECT reservation_key, owner, expires_at FROM admission_reservations WHERE reservation_key = ?").get(e);return c&&c.expires_at>s.toISOString()&&c.owner!==t?{acquired:!1,current:{key:e,owner:c.owner,expiresAt:c.expires_at}}:(this.db.prepare(`INSERT INTO admission_reservations (reservation_key, owner, expires_at, created_at)
|
|
39
|
+
VALUES (?, ?, ?, ?)
|
|
40
|
+
ON CONFLICT(reservation_key) DO UPDATE SET
|
|
41
|
+
owner = excluded.owner,
|
|
42
|
+
expires_at = excluded.expires_at`).run(e,t,i,s.toISOString()),{acquired:!0,current:{key:e,owner:t,expiresAt:i}})})}releaseAdmission(e,t){return this.inImmediateTransaction(()=>{let n=this.db.prepare("DELETE FROM admission_reservations WHERE reservation_key = ? AND owner = ?").run(e,t);return Number(n.changes)>0})}recordOperationReceipt(e){return this.inImmediateTransaction(()=>{let t=this.db.prepare(`INSERT INTO operation_receipts (idempotency_key, operation, result_json, recorded_at)
|
|
43
|
+
VALUES (?, ?, ?, ?)
|
|
44
|
+
ON CONFLICT(idempotency_key) DO NOTHING`).run(e.idempotencyKey,e.operation,e.resultJson??null,this.now().toISOString());return{deduplicated:Number(t.changes)===0}})}listOperationReceipts(e){let t=Math.floor(e?.limit??200),n=Number.isFinite(t)?Math.max(1,Math.min(1e3,t)):200,o=[],s=[];if(e?.operation&&(o.push("operation = ?"),s.push(e.operation)),e?.keyPrefix){let l=e.keyPrefix.replace(/[\\%_]/g,u=>`\\${u}`);o.push("idempotency_key LIKE ? ESCAPE '\\'"),s.push(`${l}%`)}e?.recordedAfter&&(o.push("recorded_at > ?"),s.push(e.recordedAfter));let i=o.length?`WHERE ${o.join(" AND ")}`:"";return this.db.prepare(`SELECT idempotency_key, operation, result_json, recorded_at
|
|
45
|
+
FROM operation_receipts ${i}
|
|
46
|
+
ORDER BY recorded_at ASC, idempotency_key ASC
|
|
47
|
+
LIMIT ?`).all(...s,n).map(l=>({idempotencyKey:l.idempotency_key,operation:l.operation,resultJson:l.result_json,recordedAt:l.recorded_at}))}deleteOperationReceiptsByKeys(e){return e.length===0?0:this.inImmediateTransaction(()=>{let t=this.db.prepare("DELETE FROM operation_receipts WHERE idempotency_key = ?"),n=0;for(let o of e)n+=Number(t.run(o).changes);return n})}getOperationReceipt(e){let t=this.db.prepare(`SELECT idempotency_key, operation, result_json, recorded_at
|
|
48
|
+
FROM operation_receipts WHERE idempotency_key = ?`).get(e);return t?{idempotencyKey:t.idempotency_key,operation:t.operation,resultJson:t.result_json,recordedAt:t.recorded_at}:null}saveRecoveryCheckpoint(e){return this.inImmediateTransaction(()=>{let t=this.db.prepare("SELECT COALESCE(MAX(seq), 0) AS max_seq FROM recovery_checkpoints WHERE scope = ?").get(e.scope),n=Number(t.max_seq)+1;return this.db.prepare(`INSERT INTO recovery_checkpoints (scope, seq, payload_json, created_at)
|
|
49
|
+
VALUES (?, ?, ?, ?)`).run(e.scope,n,e.payloadJson,this.now().toISOString()),{seq:n}})}latestRecoveryCheckpoint(e){let t=this.db.prepare(`SELECT scope, seq, payload_json, created_at FROM recovery_checkpoints
|
|
50
|
+
WHERE scope = ? ORDER BY seq DESC LIMIT 1`).get(e);return t?{scope:t.scope,seq:t.seq,payloadJson:t.payload_json,createdAt:t.created_at}:null}localSchemaVersion(){return 1}checkpointTruncate(){this.db.exec("PRAGMA wal_checkpoint(TRUNCATE)")}integrityCheck(){return this.db.prepare("PRAGMA integrity_check").get()?.integrity_check==="ok"}pragma(e){let t=this.db.prepare(`PRAGMA ${e}`).get();return String(Object.values(t)[0])}close(){this.db.close()}};import{existsSync as Jr,renameSync as zr}from"node:fs";function Xr(r,e){let t=`${r}.quarantine-${e}`;for(let n of["","-wal","-shm"]){let o=`${r}${n}`;Jr(o)&&zr(o,`${t}${n}`)}return t}function ne(r){let e=r.quarantineStamp??(r.now??(()=>new Date))().toISOString().replace(/[:.]/g,"-"),t=null;try{let s=A.open(r);if(s.integrityCheck())return{store:s,recovered:!1,quarantinedTo:null};s.close()}catch(s){if(s.name==="SchemaDowngradeRefusedError"||s.name==="FilesystemPolicyError")throw s;t=s}let n=`${r.directory}/${r.filename??"local-run-store.sqlite"}`,o=Xr(n,e);try{return{store:A.open(r),recovered:!0,quarantinedTo:o}}catch(s){throw new AggregateError([t,s].filter(Boolean),"local-run-store rebuild after quarantine failed")}}pe();function Dt(){if(process.env.KYNVER_LRS_TEST_CLASSIFY_BYPASS==="1")return{mountsSource:()=>"/dev/vda / ext4 rw 0 0",resolveRealPath:()=>"/tmp/kynver-lrs-test/db"}}function Mt(){return vr.classifyOptions??Dt()}var vr={};function Ut(r){vr=r}var Er=300*1e3,R=null,D=0;function Wt(r){D+=1;try{let e=Mt(),t=ne({directory:r,...e?{classifyOptions:e}:{}});return{store:t.store,status:{state:"open",harnessRoot:r,databasePath:t.store.databasePath,schemaVersion:t.store.localSchemaVersion(),recovered:t.recovered,quarantinedTo:t.quarantinedTo,reason:null,openAttempts:D},failedAtMs:null,permanentRefusal:!1}}catch(e){return{store:null,status:{state:"unavailable",harnessRoot:r,databasePath:null,schemaVersion:null,recovered:!1,quarantinedTo:null,reason:`${e.name}: ${e.message}`,openAttempts:D},failedAtMs:Date.now(),permanentRefusal:e.name==="FilesystemPolicyError"||e.name==="SchemaDowngradeRefusedError"}}}function Kt(r,e){return r.store||r.permanentRefusal?!1:r.failedAtMs===null?!0:e-r.failedAtMs>=Er}function E(r=Date.now()){let e=_r();return(!R||R.status.harnessRoot!==e||Kt(R,r))&&(Ce(),R=Wt(e)),R.store}function Cr(){return R?R.status:{state:"unopened",harnessRoot:null,databasePath:null,schemaVersion:null,recovered:!1,quarantinedTo:null,reason:null,openAttempts:D}}function Ee(){return E(),Cr()}function Ce(){try{R?.store?.close()}catch{}R=null}function Bt(){Ce(),D=0}v();p();import{existsSync as Ft,readFileSync as $t}from"node:fs";import{homedir as Ht}from"node:os";import jt from"node:path";var Yt=3e4,f=null;function Ae(r){let e=r.replace(/[^A-Za-z0-9_-]/g,"_");return jt.join(Ht(),".kynver",`local-run-store-flags-${e}.json`)}function Oe(r){if(!r||typeof r!="object"||Array.isArray(r))return{};let e=r,t={},n=["dispatchReceipts","admission","planOutbox","keeper","meshLease","quota"];for(let o of n){let s=e[o];(s==="off"||s==="shadow"||s==="enforce")&&(t[o]=s)}return t}function Vt(r){if(!r||typeof r!="object"||Array.isArray(r))return null;let e=r.response;if(!e||typeof e!="object"||Array.isArray(e))return null;let t=e.localRunStoreFlags;return t===void 0?null:Oe(t)}function Gt(r,e){try{let t=Ae(r),n={contractVersion:1,agentOsId:r,flags:Oe(e),deliveredAt:new Date().toISOString()};oe(t,n),f={flags:n.flags,loadedAtMs:Date.now(),file:t}}catch{}}function we(r=Date.now()){if(process.env.KYNVER_LRS_REMOTE_FLAGS_DISABLED==="1")return{};if(f&&r-f.loadedAtMs<Yt)return f.flags;try{let e=S().agentOsId?.trim();if(!e)return f={flags:{},loadedAtMs:r,file:null},f.flags;let t=Ae(e);if(!Ft(t))return f={flags:{},loadedAtMs:r,file:t},f.flags;let n=JSON.parse($t(t,"utf8")),o=n?.contractVersion===1?Oe(n.flags):{};return f={flags:o,loadedAtMs:r,file:t},o}catch{return f={flags:{},loadedAtMs:r,file:null},f.flags}}function qt(){f=null}var Ar=[{key:"dispatchReceipts",env:"KYNVER_LRS_DISPATCH_RECEIPTS",milestone:"D1"},{key:"admission",env:"KYNVER_LRS_ADMISSION",milestone:"D2"},{key:"planOutbox",env:"KYNVER_LRS_PLAN_OUTBOX",milestone:"D3"},{key:"keeper",env:"KYNVER_LRS_KEEPER",milestone:"D4"},{key:"meshLease",env:"KYNVER_LRS_MESH_LEASE",milestone:"D4"},{key:"quota",env:"KYNVER_LRS_QUOTA",milestone:"D4"}];function Or(r){let e=r?.trim().toLowerCase()??"";return e===""?{mode:"off",unknown:!1}:e==="off"||e==="shadow"||e==="enforce"?{mode:e,unknown:!1}:{mode:"off",unknown:!0}}function w(r=process.env){let e={},t={},n=[],o=we();for(let s of Ar){let i=r[s.env];if(typeof i=="string"&&i.trim()!==""){let u=Or(i);e[s.key]=u.mode,t[s.key]="env",u.unknown&&n.push(`${s.env}='${i}' is not off|shadow|enforce \u2014 treating as off`);continue}let l=o[s.key];if(l){e[s.key]=l,t[s.key]="server";continue}e[s.key]="off",t[s.key]="default"}return{flags:e,sources:t,warnings:n}}function Jt(r){return Object.values(r).some(e=>e!=="off")}var b={storeFailures:0,completionReplaysObserved:0};function wr(r=process.env){return w(r).flags.dispatchReceipts}function Ie(r){return wr(r)!=="off"}function Ir(r,e){return`dispatch:${r}:${e.taskId}:${e.leaseToken}`}function M(r,e,t){if(!Ie())return;let n=E();if(n)try{n.recordOperationReceipt({idempotencyKey:Ir(r,e),operation:`dispatch-${r}`,resultJson:t===void 0?null:JSON.stringify(t)})}catch{b.storeFailures+=1}}function zt(r){M("claim",r,{runId:r.runId,workerName:r.workerName,provider:r.provider})}function Xt(r){M("spawned",r)}function Qt(r,e){M("released",r,{reasonClass:e})}function Zt(r){M("completion-sent",r)}function en(r,e){M("completion-acked",r,{outcome:e})}function rn(r){if(!Ie())return!1;let e=E();if(!e)return!1;try{let t=e.getOperationReceipt(Ir("completion-acked",r));if(t){let n=null;try{n=t.resultJson?JSON.parse(t.resultJson).outcome??null:null}catch{n=null}if(n==="acknowledged")return b.completionReplaysObserved+=1,!0}}catch{b.storeFailures+=1}return!1}function tn(r){let e=String(r??"").split(":")[0]?.trim()??"";return e.length>0?e.slice(0,64):"unknown"}var nn=900*1e3,on=10080*60*1e3,sn=1440*60*1e3,an=500,cn=300;function Te(r){let e={scannedClaims:0,strandedClaimCandidates:0,prunedReceipts:0};if(!Ie())return e;let t=E();if(!t)return e;let n=r?.nowMs??Date.now(),o=r?.strandedAfterMs??nn;try{let s=t.listOperationReceipts({operation:"dispatch-claim",recordedAfter:new Date(n-on).toISOString(),limit:an}),i=0,c=[];for(let u of s){let g=u.idempotencyKey.slice(15),C=Date.parse(u.recordedAt);if(!Number.isFinite(C)||n-C<o)continue;let I=t.getOperationReceipt(`dispatch:spawned:${g}`),Q=t.getOperationReceipt(`dispatch:released:${g}`);if(I||Q){n-C>=sn&&c.length+5<=cn&&c.push(u.idempotencyKey,`dispatch:spawned:${g}`,`dispatch:released:${g}`,`dispatch:completion-sent:${g}`,`dispatch:completion-acked:${g}`);continue}let k=g.split(":")[0]??"";if((k?t.listOperationReceipts({keyPrefix:`dispatch:completion-acked:${k}:`,limit:1}):[]).length>0)continue;let ee=!1;if(r?.hasLiveWorker&&u.resultJson)try{let W=JSON.parse(u.resultJson);W.runId&&W.workerName&&(ee=r.hasLiveWorker({runId:W.runId,workerName:W.workerName}))}catch{ee=!1}ee||(i+=1)}let l=c.length>0?t.deleteOperationReceiptsByKeys(c):0;return{scannedClaims:s.length,strandedClaimCandidates:i,prunedReceipts:l}}catch{return b.storeFailures+=1,e}}function Ne(){return{"dispatchReceipts.storeFailures":b.storeFailures,"dispatchReceipts.completionReplaysObserved":b.completionReplaysObserved}}function ln(){b.storeFailures=0,b.completionReplaysObserved=0}var qa=600*1e3,U={storeFailures:0,corruptItemsSeen:0,corruptItemsQuarantined:0,duplicateDrainsObserved:0,halfArchivesCompleted:0};function Tr(){return{"planOutbox.storeFailures":U.storeFailures,"planOutbox.corruptItemsSeen":U.corruptItemsSeen,"planOutbox.corruptItemsQuarantined":U.corruptItemsQuarantined,"planOutbox.duplicateDrainsObserved":U.duplicateDrainsObserved,"planOutbox.halfArchivesCompleted":U.halfArchivesCompleted}}function un(r){let e=Ee(),t=w(r?.env??process.env),n=(r?.now??(()=>new Date))().getTime(),o={};if(t.flags.dispatchReceipts!=="off"){Object.assign(o,Ne());let s=Te({nowMs:n,hasLiveWorker:r?.hasLiveWorker});o["dispatchReceipts.strandedClaimCandidates"]=s.strandedClaimCandidates}return t.flags.planOutbox!=="off"&&Object.assign(o,Tr()),{contractVersion:1,observedAt:new Date(n).toISOString(),state:e.state,schemaVersion:e.schemaVersion,recovered:e.recovered,unavailableClass:e.reason?e.reason.split(":")[0]??null:null,flags:t.flags,flagSources:t.sources,flagWarningCount:t.warnings.length,divergenceCounters:o}}export{T as FilesystemPolicyError,Er as LOCAL_RUN_STORE_OPEN_RETRY_MS,Ar as LOCAL_RUN_STORE_SURFACES,te as LOCAL_SCHEMA_VERSION,K as SchemaDowngradeRefusedError,A as SqliteLocalRunStore,ln as __resetDispatchReceiptCountersForTests,Bt as __resetLocalRunStoreLifecycleForTests,qt as __resetRemoteSurfaceFlagsCacheForTests,Ut as __setLocalRunStoreLifecycleTestOverrides,Jt as anyLocalRunStoreSurfaceActive,un as buildLocalRunStoreTickPosture,re as classifyDatabaseDirectory,Ce as closeLocalRunStore,Ne as dispatchReceiptCounters,wr as dispatchReceiptsMode,Vt as extractLocalRunStoreFlagsFromOperatorTickResponse,E as getLocalRunStore,Cr as getLocalRunStoreLifecycleStatus,we as getRemoteSurfaceFlags,rn as noteCompletionReplayIfAcked,ne as openSqliteLocalRunStoreWithRecovery,Or as parseLocalRunStoreSurfaceMode,Gt as persistRemoteSurfaceFlags,Ee as probeLocalRunStore,en as recordCompletionAckedReceipt,Zt as recordCompletionSentReceipt,zt as recordDispatchClaimReceipt,Qt as recordDispatchReleasedReceipt,Xt as recordDispatchSpawnedReceipt,tn as releaseReasonClass,Ae as remoteSurfaceFlagsPath,w as resolveLocalRunStoreSurfaceFlags,Te as scanDispatchReceiptInvariants};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node 24 migration N4.1 — the replaceable LocalRunStore adapter contract.
|
|
3
|
+
*
|
|
4
|
+
* Covers ONLY host-local concerns the durable-harness plan needs:
|
|
5
|
+
* attempt/process identity, admission reservations, operation
|
|
6
|
+
* receipts/idempotency, recovery checkpoints, and the local schema version.
|
|
7
|
+
* It deliberately does NOT model global AgentOS task authority —
|
|
8
|
+
* AgentOS/PostgreSQL stays authoritative; nothing here may become a source
|
|
9
|
+
* of truth for tasks, plans, or audit.
|
|
10
|
+
*
|
|
11
|
+
* Implementations: sqlite/ (qualification under N4). A future fallback
|
|
12
|
+
* implementation must pass the same contract suite
|
|
13
|
+
* (__tests__/local-run-store/contract-suite.ts).
|
|
14
|
+
*/
|
|
15
|
+
export interface ProcessIdentityRecord {
|
|
16
|
+
processKey: string;
|
|
17
|
+
pid: number;
|
|
18
|
+
runtimeVersion: string;
|
|
19
|
+
nodeVersion: string;
|
|
20
|
+
startedAt: string;
|
|
21
|
+
registeredAt: string;
|
|
22
|
+
}
|
|
23
|
+
export interface AdmissionReservation {
|
|
24
|
+
key: string;
|
|
25
|
+
owner: string;
|
|
26
|
+
expiresAt: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ReserveOutcome {
|
|
29
|
+
acquired: boolean;
|
|
30
|
+
/** The live reservation after the attempt (the winner's, not necessarily the caller's). */
|
|
31
|
+
current: AdmissionReservation;
|
|
32
|
+
}
|
|
33
|
+
export interface OperationReceiptRecord {
|
|
34
|
+
idempotencyKey: string;
|
|
35
|
+
operation: string;
|
|
36
|
+
resultJson: string | null;
|
|
37
|
+
recordedAt: string;
|
|
38
|
+
}
|
|
39
|
+
export interface RecoveryCheckpointRecord {
|
|
40
|
+
scope: string;
|
|
41
|
+
seq: number;
|
|
42
|
+
payloadJson: string;
|
|
43
|
+
createdAt: string;
|
|
44
|
+
}
|
|
45
|
+
export interface LocalRunStore {
|
|
46
|
+
/** Upsert this process's identity row (idempotent per processKey). */
|
|
47
|
+
registerProcessIdentity(input: {
|
|
48
|
+
processKey: string;
|
|
49
|
+
pid: number;
|
|
50
|
+
runtimeVersion: string;
|
|
51
|
+
nodeVersion: string;
|
|
52
|
+
startedAt: string;
|
|
53
|
+
}): ProcessIdentityRecord;
|
|
54
|
+
getProcessIdentity(processKey: string): ProcessIdentityRecord | null;
|
|
55
|
+
/**
|
|
56
|
+
* Try to acquire a keyed admission slot. An unexpired reservation held by
|
|
57
|
+
* another owner loses; an expired one is replaced. Re-acquiring your own
|
|
58
|
+
* live reservation refreshes it.
|
|
59
|
+
*/
|
|
60
|
+
tryReserveAdmission(key: string, owner: string, ttlMs: number, now?: Date): ReserveOutcome;
|
|
61
|
+
releaseAdmission(key: string, owner: string): boolean;
|
|
62
|
+
/** Record an operation receipt; a duplicate idempotency key is reported, never overwritten. */
|
|
63
|
+
recordOperationReceipt(input: {
|
|
64
|
+
idempotencyKey: string;
|
|
65
|
+
operation: string;
|
|
66
|
+
resultJson?: string | null;
|
|
67
|
+
}): {
|
|
68
|
+
deduplicated: boolean;
|
|
69
|
+
};
|
|
70
|
+
getOperationReceipt(idempotencyKey: string): OperationReceiptRecord | null;
|
|
71
|
+
/**
|
|
72
|
+
* Bounded receipt scan for invariant checks (durable-harness D1 shadow):
|
|
73
|
+
* filter by exact `operation`, key prefix, and/or a recency floor
|
|
74
|
+
* (`recordedAfter`, ISO — strictly newer than), oldest first. `limit`
|
|
75
|
+
* defaults to a small bound — this is a diagnostic surface, not a query
|
|
76
|
+
* engine.
|
|
77
|
+
*/
|
|
78
|
+
listOperationReceipts(input?: {
|
|
79
|
+
operation?: string;
|
|
80
|
+
keyPrefix?: string;
|
|
81
|
+
recordedAfter?: string;
|
|
82
|
+
limit?: number;
|
|
83
|
+
}): OperationReceiptRecord[];
|
|
84
|
+
/** Delete receipts by exact key (retention pruning). Returns rows removed. */
|
|
85
|
+
deleteOperationReceiptsByKeys(keys: string[]): number;
|
|
86
|
+
saveRecoveryCheckpoint(input: {
|
|
87
|
+
scope: string;
|
|
88
|
+
payloadJson: string;
|
|
89
|
+
}): {
|
|
90
|
+
seq: number;
|
|
91
|
+
};
|
|
92
|
+
latestRecoveryCheckpoint(scope: string): RecoveryCheckpointRecord | null;
|
|
93
|
+
localSchemaVersion(): number;
|
|
94
|
+
close(): void;
|
|
95
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { LocalRunStoreSurfaceKey, LocalRunStoreSurfaceMode } from "./surface-flags.js";
|
|
2
|
+
export type RemoteSurfaceModeMap = Partial<Record<LocalRunStoreSurfaceKey, LocalRunStoreSurfaceMode>>;
|
|
3
|
+
export declare function remoteSurfaceFlagsPath(agentOsId: string): string;
|
|
4
|
+
/** Pull the delivered flag map off an operator-tick response (tolerant). */
|
|
5
|
+
export declare function extractLocalRunStoreFlagsFromOperatorTickResponse(operatorTick: unknown): RemoteSurfaceModeMap | null;
|
|
6
|
+
/** Persist a delivery atomically and refresh the in-process cache. Never throws. */
|
|
7
|
+
export declare function persistRemoteSurfaceFlags(agentOsId: string, flags: RemoteSurfaceModeMap): void;
|
|
8
|
+
/**
|
|
9
|
+
* Server-delivered modes for this box (cached). Empty map when nothing has
|
|
10
|
+
* ever been delivered, the file is unreadable, or no workspace is configured.
|
|
11
|
+
* Never throws.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getRemoteSurfaceFlags(nowMs?: number): RemoteSurfaceModeMap;
|
|
14
|
+
/** Test-only: drop the cache so the next read hits disk. */
|
|
15
|
+
export declare function __resetRemoteSurfaceFlagsCacheForTests(): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface FilesystemClassification {
|
|
2
|
+
verdict: "accepted" | "rejected";
|
|
3
|
+
/** Bounded operator-facing reason; never environment values. */
|
|
4
|
+
reason: string;
|
|
5
|
+
filesystemType: string | null;
|
|
6
|
+
mountPoint: string | null;
|
|
7
|
+
/** Realpath-resolved nearest existing ancestor the classification applied to. */
|
|
8
|
+
resolvedPath: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ClassifyOptions {
|
|
11
|
+
/** Injectable mounts content (tests); default reads /proc/self/mounts. Return null when unavailable. */
|
|
12
|
+
mountsSource?: () => string | null;
|
|
13
|
+
/** Injectable realpath (tests); default resolves via the filesystem. */
|
|
14
|
+
resolveRealPath?: (p: string) => string;
|
|
15
|
+
}
|
|
16
|
+
export declare function classifyDatabaseDirectory(directory: string, options?: ClassifyOptions): FilesystemClassification;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { LocalRunStore, OperationReceiptRecord, ProcessIdentityRecord, RecoveryCheckpointRecord, ReserveOutcome } from "../local-run-store.js";
|
|
2
|
+
import { type ClassifyOptions } from "./sqlite-filesystem-policy.js";
|
|
3
|
+
export declare class FilesystemPolicyError extends Error {
|
|
4
|
+
constructor(reason: string);
|
|
5
|
+
}
|
|
6
|
+
export interface OpenSqliteLocalRunStoreOptions {
|
|
7
|
+
directory: string;
|
|
8
|
+
filename?: string;
|
|
9
|
+
/** Busy timeout in ms (writer contention wait). */
|
|
10
|
+
busyTimeoutMs?: number;
|
|
11
|
+
/** Test injection for the filesystem policy. */
|
|
12
|
+
classifyOptions?: ClassifyOptions;
|
|
13
|
+
now?: () => Date;
|
|
14
|
+
}
|
|
15
|
+
export declare class SqliteLocalRunStore implements LocalRunStore {
|
|
16
|
+
private readonly db;
|
|
17
|
+
private readonly now;
|
|
18
|
+
readonly databasePath: string;
|
|
19
|
+
private constructor();
|
|
20
|
+
static open(options: OpenSqliteLocalRunStoreOptions): SqliteLocalRunStore;
|
|
21
|
+
/** Writer helper — every mutation runs inside BEGIN IMMEDIATE. */
|
|
22
|
+
private inImmediateTransaction;
|
|
23
|
+
registerProcessIdentity(input: {
|
|
24
|
+
processKey: string;
|
|
25
|
+
pid: number;
|
|
26
|
+
runtimeVersion: string;
|
|
27
|
+
nodeVersion: string;
|
|
28
|
+
startedAt: string;
|
|
29
|
+
}): ProcessIdentityRecord;
|
|
30
|
+
getProcessIdentity(processKey: string): ProcessIdentityRecord | null;
|
|
31
|
+
tryReserveAdmission(key: string, owner: string, ttlMs: number, now?: Date): ReserveOutcome;
|
|
32
|
+
releaseAdmission(key: string, owner: string): boolean;
|
|
33
|
+
recordOperationReceipt(input: {
|
|
34
|
+
idempotencyKey: string;
|
|
35
|
+
operation: string;
|
|
36
|
+
resultJson?: string | null;
|
|
37
|
+
}): {
|
|
38
|
+
deduplicated: boolean;
|
|
39
|
+
};
|
|
40
|
+
listOperationReceipts(input?: {
|
|
41
|
+
operation?: string;
|
|
42
|
+
keyPrefix?: string;
|
|
43
|
+
recordedAfter?: string;
|
|
44
|
+
limit?: number;
|
|
45
|
+
}): OperationReceiptRecord[];
|
|
46
|
+
deleteOperationReceiptsByKeys(keys: string[]): number;
|
|
47
|
+
getOperationReceipt(idempotencyKey: string): OperationReceiptRecord | null;
|
|
48
|
+
saveRecoveryCheckpoint(input: {
|
|
49
|
+
scope: string;
|
|
50
|
+
payloadJson: string;
|
|
51
|
+
}): {
|
|
52
|
+
seq: number;
|
|
53
|
+
};
|
|
54
|
+
latestRecoveryCheckpoint(scope: string): RecoveryCheckpointRecord | null;
|
|
55
|
+
localSchemaVersion(): number;
|
|
56
|
+
/** Explicit checkpoint policy: truncate the WAL back into the main db. */
|
|
57
|
+
checkpointTruncate(): void;
|
|
58
|
+
integrityCheck(): boolean;
|
|
59
|
+
/** Read-only config readback used by qualification assertions. */
|
|
60
|
+
pragma(name: "journal_mode" | "synchronous"): string;
|
|
61
|
+
close(): void;
|
|
62
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SqliteLocalRunStore, type OpenSqliteLocalRunStoreOptions } from "./sqlite-local-run-store.js";
|
|
2
|
+
export interface OpenWithRecoveryResult {
|
|
3
|
+
store: SqliteLocalRunStore;
|
|
4
|
+
recovered: boolean;
|
|
5
|
+
quarantinedTo: string | null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Open the store; on open failure or integrity failure, quarantine and
|
|
9
|
+
* rebuild fresh. Schema-downgrade refusals are NOT recovered — a newer
|
|
10
|
+
* schema is an operator decision, not corruption.
|
|
11
|
+
*/
|
|
12
|
+
export declare function openSqliteLocalRunStoreWithRecovery(options: OpenSqliteLocalRunStoreOptions & {
|
|
13
|
+
quarantineStamp?: string;
|
|
14
|
+
}): OpenWithRecoveryResult;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node 24 migration N4.3 — explicit versioned schema for the SQLite
|
|
3
|
+
* local-run-store qualification. Upgrades run forward only; a database
|
|
4
|
+
* written by a NEWER schema is refused (downgrade is not assumed safe).
|
|
5
|
+
*/
|
|
6
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
7
|
+
export declare const LOCAL_SCHEMA_VERSION = 1;
|
|
8
|
+
export declare class SchemaDowngradeRefusedError extends Error {
|
|
9
|
+
constructor(found: number, supported: number);
|
|
10
|
+
}
|
|
11
|
+
export declare function readStoredSchemaVersion(db: DatabaseSync): number | null;
|
|
12
|
+
/** Apply/upgrade the schema; refuses downgrade. Caller must already hold the connection. */
|
|
13
|
+
export declare function applyLocalRunStoreSchema(db: DatabaseSync, nowIso: string): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { LocalRunStore } from "./local-run-store.js";
|
|
2
|
+
import type { ClassifyOptions } from "./sqlite/sqlite-filesystem-policy.js";
|
|
3
|
+
interface LifecycleTestOverrides {
|
|
4
|
+
classifyOptions?: ClassifyOptions;
|
|
5
|
+
}
|
|
6
|
+
/** Test-only: inject filesystem-policy classification (cross-platform temp dirs). */
|
|
7
|
+
export declare function __setLocalRunStoreLifecycleTestOverrides(overrides: LifecycleTestOverrides): void;
|
|
8
|
+
export interface LocalRunStoreLifecycleStatus {
|
|
9
|
+
/** `open` — handle live; `unavailable` — open failed (reason set); `unopened` — not yet requested. */
|
|
10
|
+
state: "open" | "unavailable" | "unopened";
|
|
11
|
+
harnessRoot: string | null;
|
|
12
|
+
databasePath: string | null;
|
|
13
|
+
schemaVersion: number | null;
|
|
14
|
+
/** True when open had to quarantine a corrupt db and rebuild fresh. */
|
|
15
|
+
recovered: boolean;
|
|
16
|
+
quarantinedTo: string | null;
|
|
17
|
+
/** Failure classification when unavailable (message, no payloads). */
|
|
18
|
+
reason: string | null;
|
|
19
|
+
/** Total open attempts this process (retry churn indicator). */
|
|
20
|
+
openAttempts: number;
|
|
21
|
+
}
|
|
22
|
+
/** Backoff window before an unavailable store is re-probed (D1a precondition). */
|
|
23
|
+
export declare const LOCAL_RUN_STORE_OPEN_RETRY_MS: number;
|
|
24
|
+
/**
|
|
25
|
+
* The process-wide store handle, or `null` when the store cannot open.
|
|
26
|
+
* Never throws. A changed harness root (tests, reconfiguration) re-opens; a
|
|
27
|
+
* transient open failure is retried after the backoff window.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getLocalRunStore(nowMs?: number): LocalRunStore | null;
|
|
30
|
+
/** Lifecycle status without forcing an open (state `unopened` before first use). */
|
|
31
|
+
export declare function getLocalRunStoreLifecycleStatus(): LocalRunStoreLifecycleStatus;
|
|
32
|
+
/** Open (if needed) and report status in one call — the doctor/tick probe. */
|
|
33
|
+
export declare function probeLocalRunStore(): LocalRunStoreLifecycleStatus;
|
|
34
|
+
/** Close and forget the handle (tests, daemon shutdown). Never throws. */
|
|
35
|
+
export declare function closeLocalRunStore(): void;
|
|
36
|
+
/** Test-only: full reset including the process open-attempt counter. */
|
|
37
|
+
export declare function __resetLocalRunStoreLifecycleForTests(): void;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable-harness D0 — per-surface migration flags for the local run store.
|
|
3
|
+
*
|
|
4
|
+
* Every surface migrates independently through `off` (default — today's
|
|
5
|
+
* behavior, byte-identical) → `shadow` (store written alongside the existing
|
|
6
|
+
* path; existing path authoritative; divergences counted) → `enforce` (store
|
|
7
|
+
* authoritative for the LOCAL decision). Unknown/empty values resolve to
|
|
8
|
+
* `off` with a recorded warning — a typo must never silently enable a mode.
|
|
9
|
+
*
|
|
10
|
+
* D0 lands the parser only; no surface consults its flag yet.
|
|
11
|
+
*/
|
|
12
|
+
export type LocalRunStoreSurfaceMode = "off" | "shadow" | "enforce";
|
|
13
|
+
export type LocalRunStoreSurfaceKey = "dispatchReceipts" | "admission" | "planOutbox" | "keeper" | "meshLease" | "quota";
|
|
14
|
+
export interface LocalRunStoreSurfaceDefinition {
|
|
15
|
+
key: LocalRunStoreSurfaceKey;
|
|
16
|
+
env: string;
|
|
17
|
+
/** Plan milestone that wires the surface (operator orientation). */
|
|
18
|
+
milestone: string;
|
|
19
|
+
}
|
|
20
|
+
export declare const LOCAL_RUN_STORE_SURFACES: readonly LocalRunStoreSurfaceDefinition[];
|
|
21
|
+
export type LocalRunStoreSurfaceFlags = Record<LocalRunStoreSurfaceKey, LocalRunStoreSurfaceMode>;
|
|
22
|
+
/** Where each surface's effective mode came from (confirmation loop). */
|
|
23
|
+
export type LocalRunStoreSurfaceFlagSource = "env" | "server" | "default";
|
|
24
|
+
export interface ResolvedLocalRunStoreSurfaceFlags {
|
|
25
|
+
flags: LocalRunStoreSurfaceFlags;
|
|
26
|
+
sources: Record<LocalRunStoreSurfaceKey, LocalRunStoreSurfaceFlagSource>;
|
|
27
|
+
/** One entry per env var whose value was set but not a recognized mode. */
|
|
28
|
+
warnings: string[];
|
|
29
|
+
}
|
|
30
|
+
export declare function parseLocalRunStoreSurfaceMode(raw: string | undefined | null): {
|
|
31
|
+
mode: LocalRunStoreSurfaceMode;
|
|
32
|
+
unknown: boolean;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Effective per-surface modes with precedence
|
|
36
|
+
* env (explicitly set) > server-delivered policy > default `off`.
|
|
37
|
+
*
|
|
38
|
+
* The env var is the host-level override and local emergency brake; on the
|
|
39
|
+
* normal box (env unset everywhere) the server-delivered policy governs —
|
|
40
|
+
* boxes are administered from the backend, never by shell access
|
|
41
|
+
* (`remote-surface-flags.ts`). An UNKNOWN env value still wins the
|
|
42
|
+
* precedence as `off` (a typo must fail safe, not silently fall through to
|
|
43
|
+
* a server-delivered `enforce`).
|
|
44
|
+
*/
|
|
45
|
+
export declare function resolveLocalRunStoreSurfaceFlags(env?: NodeJS.ProcessEnv): ResolvedLocalRunStoreSurfaceFlags;
|
|
46
|
+
/** True when any surface is past `off` (store activity expected on this host). */
|
|
47
|
+
export declare function anyLocalRunStoreSurfaceActive(flags: LocalRunStoreSurfaceFlags): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type LocalRunStoreSurfaceFlags } from "./surface-flags.js";
|
|
2
|
+
export interface LocalRunStoreTickPosture {
|
|
3
|
+
contractVersion: 1;
|
|
4
|
+
observedAt: string;
|
|
5
|
+
/** `open` | `unavailable` (a tick posture always probes, so never `unopened`). */
|
|
6
|
+
state: "open" | "unavailable" | "unopened";
|
|
7
|
+
schemaVersion: number | null;
|
|
8
|
+
/** True when this open had to quarantine a corrupt db and rebuild. */
|
|
9
|
+
recovered: boolean;
|
|
10
|
+
/** Failure class when unavailable (error name only — no message payload). */
|
|
11
|
+
unavailableClass: string | null;
|
|
12
|
+
flags: LocalRunStoreSurfaceFlags;
|
|
13
|
+
/** Where each effective mode came from — env override, server policy, or default. */
|
|
14
|
+
flagSources: Record<string, "env" | "server" | "default">;
|
|
15
|
+
flagWarningCount: number;
|
|
16
|
+
/** Per-surface shadow divergence counters (D1+; empty in D0). */
|
|
17
|
+
divergenceCounters: Record<string, number>;
|
|
18
|
+
}
|
|
19
|
+
export declare function buildLocalRunStoreTickPosture(options?: {
|
|
20
|
+
env?: NodeJS.ProcessEnv;
|
|
21
|
+
now?: () => Date;
|
|
22
|
+
/** Liveness probe for the stranded-claim scan (injected to avoid run-store cycles). */
|
|
23
|
+
hasLiveWorker?: (intent: {
|
|
24
|
+
runId: string;
|
|
25
|
+
workerName: string;
|
|
26
|
+
}) => boolean;
|
|
27
|
+
}): LocalRunStoreTickPosture;
|
|
@@ -2,7 +2,7 @@ export type { PlanPersistUserStatus, PlanPersistOperation, PlanOutboxItem, Persi
|
|
|
2
2
|
export { hashPlanBody, hashSummary } from "./body-hash.js";
|
|
3
3
|
export { persistPlan } from "./persist.js";
|
|
4
4
|
export { drainPlanOutbox, loadOutboxById } from "./drain.js";
|
|
5
|
-
export { listOutboxItems, findOutboxByIdempotencyKey } from "./outbox-store.js";
|
|
5
|
+
export { listOutboxItems, findOutboxByIdempotencyKey, writeOutboxItem } from "./outbox-store.js";
|
|
6
6
|
export { formatPlanOutboxHandoffBlock, extractPlanOutboxFromTask, } from "./handoff.js";
|
|
7
7
|
export { isTmpOnlyPath, planOutboxDir } from "./paths.js";
|
|
8
8
|
export { PlanPersistError } from "./errors.js";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { LocalRunStoreSurfaceMode } from "../local-run-store/surface-flags.js";
|
|
2
|
+
export declare function planOutboxMode(env?: NodeJS.ProcessEnv): LocalRunStoreSurfaceMode;
|
|
3
|
+
/**
|
|
4
|
+
* A corrupt spool file was sighted. `shadow`: count only (caller keeps
|
|
5
|
+
* today's behavior). `enforce`: quarantine by rename (never delete) and tell
|
|
6
|
+
* the caller to continue. Returns true when the file was quarantined.
|
|
7
|
+
*/
|
|
8
|
+
export declare function handleCorruptOutboxFile(paths: string[], now?: () => Date): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Per-item drain serialization. `enforce`: only the reservation winner may
|
|
11
|
+
* drain the item (crash backstop = TTL). `shadow`: a lost race is counted,
|
|
12
|
+
* the caller proceeds as today. Never throws.
|
|
13
|
+
*/
|
|
14
|
+
export declare function tryAcquireDrainSlot(itemId: string, owner: string): {
|
|
15
|
+
proceed: boolean;
|
|
16
|
+
deduplicated: boolean;
|
|
17
|
+
};
|
|
18
|
+
export declare function releaseDrainSlot(itemId: string, owner: string): void;
|
|
19
|
+
/** Record the verified persist on the item's existing idempotency key. */
|
|
20
|
+
export declare function recordPlanPersistedReceipt(idempotencyKey: string, planId: string | null): void;
|
|
21
|
+
/** Archive intent receipt — recorded BEFORE the two renames (enforce). */
|
|
22
|
+
export declare function recordArchiveIntentReceipt(itemId: string): void;
|
|
23
|
+
/**
|
|
24
|
+
* ENFORCE-mode drain preamble: an item whose archive-intent receipt exists is
|
|
25
|
+
* a crash between the archive renames — complete the renames instead of
|
|
26
|
+
* re-persisting. Returns true when the item was (re)archived away.
|
|
27
|
+
*/
|
|
28
|
+
export declare function completeHalfArchiveIfIntended(itemId: string, renameRemaining: () => void): boolean;
|
|
29
|
+
/** Counters snapshot for the operator-tick posture (surface active only). */
|
|
30
|
+
export declare function planOutboxCounters(): Record<string, number>;
|
|
31
|
+
export declare function __resetPlanOutboxCountersForTests(): void;
|