@vincemakes/kiso-runtime 0.1.0
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/LICENSE +21 -0
- package/README.md +8 -0
- package/dist/agent.d.ts +64 -0
- package/dist/agent.js +119 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/session.d.ts +155 -0
- package/dist/session.js +920 -0
- package/dist/store.d.ts +111 -0
- package/dist/store.js +586 -0
- package/package.json +53 -0
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionStore — append-only JSONL durability, identity-safe (A 组).
|
|
3
|
+
*
|
|
4
|
+
* One file per session: `<root>/<id>.jsonl`, lines of
|
|
5
|
+
* `{"runId": string, "ts": number, "event": Event}`. The single-writer
|
|
6
|
+
* lock (第四轮) is an EXCLUSIVE KERNEL flock on `<id>.lock`, held by a
|
|
7
|
+
* dedicated helper process:
|
|
8
|
+
*
|
|
9
|
+
* - the kernel arbitrates every race — a contender can never remove or
|
|
10
|
+
* overwrite a live holder's lock, because there is nothing to remove;
|
|
11
|
+
* the lock simply exists while the helper lives and vanishes with it;
|
|
12
|
+
* - the lock file ALSO carries `{"pid": number, "token": string}` written
|
|
13
|
+
* by the holder, as a best-effort guard for OLD-format writers (whose
|
|
14
|
+
* O_EXCL pidfile scheme does not honor flock). 第五轮(P1-4): this guard
|
|
15
|
+
* is NOT a seamless rolling upgrade — an old writer that created an
|
|
16
|
+
* empty lock file before writing its pid creates a split-brain window
|
|
17
|
+
* that a pidfile read cannot close. The documented upgrade contract is
|
|
18
|
+
* QUARANTINE: stop every old-format process, THEN start the new
|
|
19
|
+
* version. A dead/empty/unreadable legacy lock is otherwise harmless —
|
|
20
|
+
* flock ignores content, and the kernel lock is what matters;
|
|
21
|
+
* - `close()` releases only THIS instance's helper; `closeAll()` every
|
|
22
|
+
* held helper — a foreign close can never release another writer's
|
|
23
|
+
* kernel lock (flock is tied to the helper's open file description).
|
|
24
|
+
*
|
|
25
|
+
* Consistency contract (A 组):
|
|
26
|
+
* - every id is validated BEFORE any file side effect (append, close,
|
|
27
|
+
* load, lock paths);
|
|
28
|
+
* - append runs an expected-last-seq CAS against the file's REAL last
|
|
29
|
+
* committed seq: a stale preloaded handle writing a duplicate seq is
|
|
30
|
+
* refused with StaleWriterError — and the run that fed it terminates,
|
|
31
|
+
* so the in-memory EventLog never continues past a rejected write;
|
|
32
|
+
* - the torn tail is repaired before EVERY append, and committed records
|
|
33
|
+
* (newline-terminated) are never truncated;
|
|
34
|
+
* - load is strict (A 组 round 1): a partial final line is the only
|
|
35
|
+
* tolerated damage; everything else throws StoreCorruptionError.
|
|
36
|
+
*/
|
|
37
|
+
import { type Event } from "@vincemakes/kiso-core";
|
|
38
|
+
/** History that does not parse as a contiguous kiso trajectory. */
|
|
39
|
+
export declare class StoreCorruptionError extends Error {
|
|
40
|
+
constructor(message: string);
|
|
41
|
+
}
|
|
42
|
+
/** A write that would duplicate or skip a seq — the handle is stale. */
|
|
43
|
+
export declare class StaleWriterError extends Error {
|
|
44
|
+
constructor(expected: number, got: number);
|
|
45
|
+
}
|
|
46
|
+
/** A durable record: the run that produced it, and the event itself. */
|
|
47
|
+
export interface StoreRecord {
|
|
48
|
+
readonly runId: string;
|
|
49
|
+
readonly ts: number;
|
|
50
|
+
readonly event: Event;
|
|
51
|
+
}
|
|
52
|
+
export interface SessionMeta {
|
|
53
|
+
readonly id: string;
|
|
54
|
+
readonly title: string;
|
|
55
|
+
readonly events: number;
|
|
56
|
+
readonly runs: number;
|
|
57
|
+
readonly createdAt: number;
|
|
58
|
+
readonly updatedAt: number;
|
|
59
|
+
}
|
|
60
|
+
export declare class SessionStore {
|
|
61
|
+
#private;
|
|
62
|
+
readonly root: string;
|
|
63
|
+
constructor(root: string);
|
|
64
|
+
private pathFor;
|
|
65
|
+
private lockPathFor;
|
|
66
|
+
/**
|
|
67
|
+
* Take the single-writer lock (第四轮): an EXCLUSIVE kernel flock held
|
|
68
|
+
* by a dedicated helper process. The KERNEL arbitrates every race —
|
|
69
|
+
* there is no stale lock to delete and no takeover to race: a
|
|
70
|
+
* contender either gets the flock (the previous holder is gone) or it
|
|
71
|
+
* fails. The lock file also carries the holder's identity so an OLD-format
|
|
72
|
+
* writer (which does not honor flock) still sees a live owner and
|
|
73
|
+
* refuses to take over — a best-effort guard, NOT a seamless rolling
|
|
74
|
+
* upgrade (第五轮 P1-4): the documented upgrade contract is quarantine —
|
|
75
|
+
* stop every old-format process, then start the new version.
|
|
76
|
+
* No recursion, no deletion, no window between NEW-format writers.
|
|
77
|
+
*/
|
|
78
|
+
private acquireLock;
|
|
79
|
+
/** 第五轮(P1-2): true only while the helper process is alive. */
|
|
80
|
+
private lockHeld;
|
|
81
|
+
/**
|
|
82
|
+
* Release OUR lock only: kill OUR helper. The kernel releases the
|
|
83
|
+
* flock with the helper's death; the identity file is CLEARED so a
|
|
84
|
+
* same-process successor is never mistaken for a live legacy owner —
|
|
85
|
+
* the flock is the authority, the file is advisory (第四轮).
|
|
86
|
+
*/
|
|
87
|
+
private releaseLock;
|
|
88
|
+
/** Write-ahead: durable (written + fsynced) before returning. */
|
|
89
|
+
append(sessionId: string, runId: string, event: Event): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Open (creating if needed) the session file. The torn tail is repaired
|
|
92
|
+
* here AND before every append — an in-process append failure cannot
|
|
93
|
+
* poison the next one. The parent directory is fsynced so the file's
|
|
94
|
+
* existence survives a crash.
|
|
95
|
+
*/
|
|
96
|
+
private fd;
|
|
97
|
+
/**
|
|
98
|
+
* Replay a session's log. The ONLY tolerated damage is a partial final
|
|
99
|
+
* line (a crash mid-write): it is dropped and the contiguous prefix is
|
|
100
|
+
* returned. Anything else — mid-file garbage, valid JSON that is not a
|
|
101
|
+
* kiso record, a seq that is not 0..N — throws StoreCorruptionError.
|
|
102
|
+
*/
|
|
103
|
+
load(sessionId: string): StoreRecord[];
|
|
104
|
+
has(sessionId: string): boolean;
|
|
105
|
+
list(): SessionMeta[];
|
|
106
|
+
/** Release a session's fd and OUR writer lock. Idempotent. */
|
|
107
|
+
close(sessionId: string): void;
|
|
108
|
+
/** Release every held fd and lock, including locks whose JSONL open failed. */
|
|
109
|
+
closeAll(): void;
|
|
110
|
+
}
|
|
111
|
+
export type { Event };
|