@kynver-app/runtime 0.2.0 → 0.2.4
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 +58 -58
- package/dist/context-preview/chat-memory-preview.d.ts +33 -0
- package/dist/daemon-supervision/index.d.ts +1 -0
- package/dist/daemon-supervision/supervision-health.d.ts +20 -0
- package/dist/daemon-supervision/verify.d.ts +1 -0
- package/dist/doctor/github-readiness.d.ts +16 -0
- package/dist/doctor/runtime-takeover-deploy-evidence.d.ts +9 -0
- package/dist/doctor/runtime-takeover.probes.d.ts +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +85 -80
- package/dist/instruction-bundle/contract.js +1 -1
- package/dist/local-run-store/index.d.ts +5 -0
- package/dist/local-run-store/index.js +46 -0
- package/dist/local-run-store/local-run-store.d.ts +80 -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 +55 -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/runner-config-backfill.d.ts +18 -0
- package/dist/server/cleanup.js +14 -14
- package/dist/server/default-repo.js +1 -1
- package/dist/server/monitor.js +7 -7
- package/dist/server/worker-policy.js +1 -1
- 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,5 @@
|
|
|
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";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import{existsSync as T,realpathSync as E,readFileSync as f}from"node:fs";import m from"node:path";var _=new Set(["ext4","ext3","ext2","btrfs","xfs","f2fs","zfs","tmpfs","overlay","overlayfs"]),S=new Set(["9p","drvfs","nfs","nfs4","cifs","smb","smbfs","smb3","sshfs","fuse.sshfs","davfs","davfs2","glusterfs","ceph","lustre","afs","vboxsf","prl_fs","virtiofs"]);function g(){try{return f("/proc/self/mounts","utf8")}catch{return null}}function h(n){let e=m.resolve(n);for(let t=0;t<64;t+=1){if(T(e))try{return E.native(e)}catch{return e}let r=m.dirname(e);if(r===e)return e;e=r}return e}function R(n){return n.replace(/\\(\d{3})/g,(e,t)=>String.fromCharCode(Number.parseInt(t,8)))}function O(n){let e=[];for(let t of n.split(`
|
|
2
|
+
`)){let r=t.trim().split(/\s+/);r.length<3||e.push({mountPoint:R(r[1]),fsType:r[2]})}return e}function L(n,e){let t=null;for(let r of e){let s=r.mountPoint.endsWith("/")?r.mountPoint:`${r.mountPoint}/`;(n===r.mountPoint||`${n}/`.startsWith(s))&&(!t||r.mountPoint.length>t.mountPoint.length)&&(t=r)}return t}function d(n,e){let r=(e?.resolveRealPath??h)(n),s=(e?.mountsSource??g)();if(s===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:r};let o=L(r,O(s));if(!o)return{verdict:"rejected",reason:"No mount entry covers the resolved database path \u2014 refusing WAL mode (fail closed).",filesystemType:null,mountPoint:null,resolvedPath:r};let i=o.fsType.toLowerCase();return S.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:o.mountPoint,resolvedPath:r}:_.has(i)?{verdict:"accepted",reason:`Local filesystem '${i}' accepted for WAL mode.`,filesystemType:i,mountPoint:o.mountPoint,resolvedPath:r}:{verdict:"rejected",reason:`Filesystem '${i}' is unknown to the qualification policy; WAL mode is refused rather than guessed (fail closed).`,filesystemType:i,mountPoint:o.mountPoint,resolvedPath:r}}var p=1,u=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"}},v=[`CREATE TABLE IF NOT EXISTS local_meta (
|
|
3
|
+
key TEXT PRIMARY KEY,
|
|
4
|
+
value TEXT NOT NULL
|
|
5
|
+
) STRICT`,`CREATE TABLE IF NOT EXISTS process_identity (
|
|
6
|
+
process_key TEXT PRIMARY KEY,
|
|
7
|
+
pid INTEGER NOT NULL,
|
|
8
|
+
runtime_version TEXT NOT NULL,
|
|
9
|
+
node_version TEXT NOT NULL,
|
|
10
|
+
started_at TEXT NOT NULL,
|
|
11
|
+
registered_at TEXT NOT NULL
|
|
12
|
+
) STRICT`,`CREATE TABLE IF NOT EXISTS admission_reservations (
|
|
13
|
+
reservation_key TEXT PRIMARY KEY,
|
|
14
|
+
owner TEXT NOT NULL,
|
|
15
|
+
expires_at TEXT NOT NULL,
|
|
16
|
+
created_at TEXT NOT NULL
|
|
17
|
+
) STRICT`,`CREATE TABLE IF NOT EXISTS operation_receipts (
|
|
18
|
+
idempotency_key TEXT PRIMARY KEY,
|
|
19
|
+
operation TEXT NOT NULL,
|
|
20
|
+
result_json TEXT,
|
|
21
|
+
recorded_at TEXT NOT NULL
|
|
22
|
+
) STRICT`,`CREATE TABLE IF NOT EXISTS recovery_checkpoints (
|
|
23
|
+
scope TEXT NOT NULL,
|
|
24
|
+
seq INTEGER NOT NULL,
|
|
25
|
+
payload_json TEXT NOT NULL,
|
|
26
|
+
created_at TEXT NOT NULL,
|
|
27
|
+
PRIMARY KEY (scope, seq)
|
|
28
|
+
) STRICT`];function A(n){if(!n.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'local_meta'").get())return null;let t=n.prepare("SELECT value FROM local_meta WHERE key = 'schema_version'").get();if(!t?.value)return null;let r=Number.parseInt(t.value,10);return Number.isFinite(r)?r:null}function y(n,e){let t=A(n);if(t!==null&&t>1)throw new u(t,1);n.exec("BEGIN IMMEDIATE");try{for(let r of v)n.exec(r);n.prepare("INSERT INTO local_meta (key, value) VALUES ('schema_version', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(String(1)),n.prepare("INSERT INTO local_meta (key, value) VALUES ('schema_applied_at', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(e),n.exec("COMMIT")}catch(r){throw n.exec("ROLLBACK"),r}}import{mkdirSync as I}from"node:fs";import N from"node:path";import{DatabaseSync as C}from"node:sqlite";var l=class extends Error{constructor(e){super(`local-run-store refused: ${e}`),this.name="FilesystemPolicyError"}},b="local-run-store.sqlite",x=5e3,a=class n{db;now;databasePath;constructor(e,t,r){this.db=e,this.databasePath=t,this.now=r}static open(e){let t=d(e.directory,e.classifyOptions);if(t.verdict!=="accepted")throw new l(t.reason);I(e.directory,{recursive:!0,mode:448});let r=N.join(e.directory,e.filename??b),s=new C(r,{timeout:e.busyTimeoutMs??x}),o=e.now??(()=>new Date);try{s.exec("PRAGMA journal_mode = WAL");let i=s.prepare("PRAGMA journal_mode").get();if(i?.journal_mode?.toLowerCase()!=="wal")throw new l(`journal_mode did not switch to WAL (got '${i?.journal_mode}') \u2014 filesystem unsuitable.`);s.exec("PRAGMA synchronous = NORMAL"),y(s,o().toISOString())}catch(i){throw s.close(),i}return new n(s,r,o)}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
|
|
29
|
+
(process_key, pid, runtime_version, node_version, started_at, registered_at)
|
|
30
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
31
|
+
ON CONFLICT(process_key) DO UPDATE SET
|
|
32
|
+
pid = excluded.pid,
|
|
33
|
+
runtime_version = excluded.runtime_version,
|
|
34
|
+
node_version = excluded.node_version,
|
|
35
|
+
started_at = excluded.started_at,
|
|
36
|
+
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
|
|
37
|
+
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,r,s){let o=s??this.now(),i=new Date(o.getTime()+r).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>o.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)
|
|
38
|
+
VALUES (?, ?, ?, ?)
|
|
39
|
+
ON CONFLICT(reservation_key) DO UPDATE SET
|
|
40
|
+
owner = excluded.owner,
|
|
41
|
+
expires_at = excluded.expires_at`).run(e,t,i,o.toISOString()),{acquired:!0,current:{key:e,owner:t,expiresAt:i}})})}releaseAdmission(e,t){return this.inImmediateTransaction(()=>{let r=this.db.prepare("DELETE FROM admission_reservations WHERE reservation_key = ? AND owner = ?").run(e,t);return Number(r.changes)>0})}recordOperationReceipt(e){return this.inImmediateTransaction(()=>{let t=this.db.prepare(`INSERT INTO operation_receipts (idempotency_key, operation, result_json, recorded_at)
|
|
42
|
+
VALUES (?, ?, ?, ?)
|
|
43
|
+
ON CONFLICT(idempotency_key) DO NOTHING`).run(e.idempotencyKey,e.operation,e.resultJson??null,this.now().toISOString());return{deduplicated:Number(t.changes)===0}})}getOperationReceipt(e){let t=this.db.prepare(`SELECT idempotency_key, operation, result_json, recorded_at
|
|
44
|
+
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),r=Number(t.max_seq)+1;return this.db.prepare(`INSERT INTO recovery_checkpoints (scope, seq, payload_json, created_at)
|
|
45
|
+
VALUES (?, ?, ?, ?)`).run(e.scope,r,e.payloadJson,this.now().toISOString()),{seq:r}})}latestRecoveryCheckpoint(e){let t=this.db.prepare(`SELECT scope, seq, payload_json, created_at FROM recovery_checkpoints
|
|
46
|
+
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 w,renameSync as M}from"node:fs";function P(n,e){let t=`${n}.quarantine-${e}`;for(let r of["","-wal","-shm"]){let s=`${n}${r}`;w(s)&&M(s,`${t}${r}`)}return t}function k(n){let e=n.quarantineStamp??(n.now??(()=>new Date))().toISOString().replace(/[:.]/g,"-"),t=null;try{let o=a.open(n);if(o.integrityCheck())return{store:o,recovered:!1,quarantinedTo:null};o.close()}catch(o){if(o.name==="SchemaDowngradeRefusedError"||o.name==="FilesystemPolicyError")throw o;t=o}let r=`${n.directory}/${n.filename??"local-run-store.sqlite"}`,s=P(r,e);try{return{store:a.open(n),recovered:!0,quarantinedTo:s}}catch(o){throw new AggregateError([t,o].filter(Boolean),"local-run-store rebuild after quarantine failed")}}export{l as FilesystemPolicyError,p as LOCAL_SCHEMA_VERSION,u as SchemaDowngradeRefusedError,a as SqliteLocalRunStore,d as classifyDatabaseDirectory,k as openSqliteLocalRunStoreWithRecovery};
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
saveRecoveryCheckpoint(input: {
|
|
72
|
+
scope: string;
|
|
73
|
+
payloadJson: string;
|
|
74
|
+
}): {
|
|
75
|
+
seq: number;
|
|
76
|
+
};
|
|
77
|
+
latestRecoveryCheckpoint(scope: string): RecoveryCheckpointRecord | null;
|
|
78
|
+
localSchemaVersion(): number;
|
|
79
|
+
close(): void;
|
|
80
|
+
}
|
|
@@ -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,55 @@
|
|
|
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
|
+
getOperationReceipt(idempotencyKey: string): OperationReceiptRecord | null;
|
|
41
|
+
saveRecoveryCheckpoint(input: {
|
|
42
|
+
scope: string;
|
|
43
|
+
payloadJson: string;
|
|
44
|
+
}): {
|
|
45
|
+
seq: number;
|
|
46
|
+
};
|
|
47
|
+
latestRecoveryCheckpoint(scope: string): RecoveryCheckpointRecord | null;
|
|
48
|
+
localSchemaVersion(): number;
|
|
49
|
+
/** Explicit checkpoint policy: truncate the WAL back into the main db. */
|
|
50
|
+
checkpointTruncate(): void;
|
|
51
|
+
integrityCheck(): boolean;
|
|
52
|
+
/** Read-only config readback used by qualification assertions. */
|
|
53
|
+
pragma(name: "journal_mode" | "synchronous"): string;
|
|
54
|
+
close(): void;
|
|
55
|
+
}
|
|
@@ -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,18 @@
|
|
|
1
|
+
import { type KynverUserConfig } from "./config.js";
|
|
2
|
+
export interface RunnerConfigBackfillResult {
|
|
3
|
+
updated: boolean;
|
|
4
|
+
fields: string[];
|
|
5
|
+
skippedAmbiguous: string[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Safe migration/backfill of ~/.kynver/config.json from env and scoped credentials.
|
|
9
|
+
* Never adopts ambiguous workspace identity or writes raw tokens.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildRunnerConfigBackfillUpdates(config: KynverUserConfig, env?: NodeJS.ProcessEnv): {
|
|
12
|
+
updates: Partial<KynverUserConfig>;
|
|
13
|
+
fields: string[];
|
|
14
|
+
skippedAmbiguous: string[];
|
|
15
|
+
};
|
|
16
|
+
export declare function backfillRunnerConfigFromLegacySources(opts?: {
|
|
17
|
+
dryRun?: boolean;
|
|
18
|
+
}): RunnerConfigBackfillResult;
|