@sentropic/h2a 0.97.9 → 0.97.11
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/dist/runtime/local-files/succession-lock.d.ts +162 -0
- package/dist/runtime/local-files/succession-lock.d.ts.map +1 -0
- package/dist/runtime/local-files/succession-lock.js +777 -0
- package/dist/runtime/local-files/succession-lock.js.map +1 -0
- package/dist/runtime/upgrade/index.d.ts +4 -98
- package/dist/runtime/upgrade/index.d.ts.map +1 -1
- package/dist/runtime/upgrade/index.js +8 -760
- package/dist/runtime/upgrade/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Succession lock (v4) — single-machine succession protocol.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `runtime/upgrade/index.ts` (Lot 4, step 1: neutral
|
|
5
|
+
* intra-package move — zero behaviour change). This is the shared lock primitive
|
|
6
|
+
* used by the auto-upgrade prefix lock today, and (later lots) by the identity
|
|
7
|
+
* binding lock. It is a LEAF: it imports only `node:fs/os/crypto/child_process`
|
|
8
|
+
* and nothing from the store, so `upgrade/index.ts` and `local-files/locks.ts`
|
|
9
|
+
* import it directly by file, never via `local-files/index.js`.
|
|
10
|
+
*
|
|
11
|
+
* The full protocol proof-sketch (I1–I7, Lemmas A–C) lives with the code below,
|
|
12
|
+
* moved unchanged from its original location.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* R2 staleness-alert threshold. An upgrade held under the lock completes in seconds to
|
|
16
|
+
* a few minutes (bounded tarball fetch + stage + atomic swap), so a lock far older than
|
|
17
|
+
* that whose holder reads "live" ONLY for want of a comparable start time is worth
|
|
18
|
+
* surfacing (a reused PID may be masking a dead holder). Set well above any legitimate
|
|
19
|
+
* hold. It NEVER triggers a reclaim — purely diagnostic (I7: `at` informs, never decides).
|
|
20
|
+
*/
|
|
21
|
+
export declare const STALE_LOCK_ALERT_MS: number;
|
|
22
|
+
/** Acquire rounds before giving up (a retry means a rival won meanwhile). */
|
|
23
|
+
export declare const PREFIX_LOCK_MAX_ROUNDS = 3;
|
|
24
|
+
/** Succession chain depth before failing closed with a diagnostic. */
|
|
25
|
+
export declare const PREFIX_LOCK_MAX_CHAIN = 8;
|
|
26
|
+
/** Holder-only GC age for abandoned TMP files (debris, never a decision). */
|
|
27
|
+
export declare const PREFIX_LOCK_TMP_DEBRIS_MAX_AGE_MS: number;
|
|
28
|
+
/** Fallback age for the residue sweep when no owner can be identified. */
|
|
29
|
+
export declare const UPGRADE_RESIDUE_MAX_AGE_MS: number;
|
|
30
|
+
/** Why acquisition failed: live holder, undecidable owner, or an OS error. */
|
|
31
|
+
export type PrefixLockReason = "busy" | "dead-undecidable" | `error:${string}`;
|
|
32
|
+
/** Lease on the global prefix. `token` is present only when acquired. */
|
|
33
|
+
export interface PrefixLockLease {
|
|
34
|
+
readonly acquired: boolean;
|
|
35
|
+
release(): void;
|
|
36
|
+
readonly reason?: PrefixLockReason;
|
|
37
|
+
/** Winning token; present only when acquired. Never republished (I2). */
|
|
38
|
+
readonly token?: string;
|
|
39
|
+
}
|
|
40
|
+
/** Observation window for one deterministic-test hook invocation. */
|
|
41
|
+
export interface PrefixLockHookContext {
|
|
42
|
+
readonly prefix: string;
|
|
43
|
+
readonly lockPath: string;
|
|
44
|
+
/** File the window is about: LOCK for publish/unlink, SUCC(t) after election. */
|
|
45
|
+
readonly path: string;
|
|
46
|
+
/** Our fresh token being published (or held). */
|
|
47
|
+
readonly token?: string;
|
|
48
|
+
/** Succession target g (afterPublishSucc / retire windows). */
|
|
49
|
+
readonly target?: string;
|
|
50
|
+
readonly round: number;
|
|
51
|
+
readonly depth: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Critical-section hooks for deterministic tests ONLY. Passed as the 2nd
|
|
55
|
+
* argument of `acquirePrefixLock(prefix, hooks)` by the test; never enabled
|
|
56
|
+
* via environment or configuration (there is no env/config reader for them),
|
|
57
|
+
* and production calls without hooks so every hook is a no-op at zero cost.
|
|
58
|
+
*/
|
|
59
|
+
export interface PrefixLockHooks {
|
|
60
|
+
/** Just BEFORE the initial publish(LOCK) (round 0) — initial race window. */
|
|
61
|
+
readonly beforePublishLock?: (ctx: PrefixLockHookContext) => void;
|
|
62
|
+
/** Just AFTER a publish(SUCC(t)) success, BEFORE retire — unique successor elected. */
|
|
63
|
+
readonly afterPublishSucc?: (ctx: PrefixLockHookContext) => void;
|
|
64
|
+
/** In retire(), around the targeted unlink(LOCK) (removal-to-republish window). */
|
|
65
|
+
readonly beforeRetireUnlink?: (ctx: PrefixLockHookContext) => void;
|
|
66
|
+
readonly afterRetireUnlink?: (ctx: PrefixLockHookContext) => void;
|
|
67
|
+
}
|
|
68
|
+
interface LockIdent {
|
|
69
|
+
readonly host: string;
|
|
70
|
+
readonly boot: string | null;
|
|
71
|
+
readonly ns: string | null;
|
|
72
|
+
/** Reader's time namespace; gates proc-sourced start comparisons (B2). */
|
|
73
|
+
readonly timeNs: string | null;
|
|
74
|
+
readonly pid: number;
|
|
75
|
+
readonly start: string | null;
|
|
76
|
+
}
|
|
77
|
+
interface LockRec extends LockIdent {
|
|
78
|
+
readonly token: string;
|
|
79
|
+
readonly target?: string;
|
|
80
|
+
/** Diagnostic only, never decides (I7). */
|
|
81
|
+
readonly at: number;
|
|
82
|
+
}
|
|
83
|
+
export declare function lockPathFor(prefix: string): string;
|
|
84
|
+
export declare function errnoOf(e: unknown): string;
|
|
85
|
+
export declare function safeAtIso(at: number): string;
|
|
86
|
+
export declare function readPidNs(platform?: NodeJS.Platform, readLink?: (p: string) => string): string | null;
|
|
87
|
+
export declare function readTimeNs(platform?: NodeJS.Platform, readLink?: (p: string) => string): string | null;
|
|
88
|
+
/**
|
|
89
|
+
* Process start identity, prefixed by its source ("proc:" from Linux /proc
|
|
90
|
+
* field 22, "ps:" from `ps lstart`). The prefix is part of the stored value
|
|
91
|
+
* so a reader using a different source never concludes "dead" from a
|
|
92
|
+
* format/TZ-fragile comparison (C1).
|
|
93
|
+
*/
|
|
94
|
+
export declare function procStartInfo(pid: number, platform?: NodeJS.Platform, mapsToSelf?: () => boolean): {
|
|
95
|
+
state?: string;
|
|
96
|
+
start?: string;
|
|
97
|
+
} | undefined;
|
|
98
|
+
interface SelfIdent extends LockIdent {
|
|
99
|
+
readonly pid: number;
|
|
100
|
+
}
|
|
101
|
+
/** This process's identity, computed once and memoized. */
|
|
102
|
+
export declare function me(): SelfIdent;
|
|
103
|
+
export declare function makeLockRec(token: string, target?: string): LockRec;
|
|
104
|
+
export declare function parseLockRec(raw: unknown): LockRec;
|
|
105
|
+
/** opus `read`: ENOENT -> absent, anything else unreadable -> corrupt (I1). */
|
|
106
|
+
export declare function readLockRecord(path: string): LockRec | "absent" | "corrupt";
|
|
107
|
+
type PublishStatus = {
|
|
108
|
+
status: "ok";
|
|
109
|
+
} | {
|
|
110
|
+
status: "exists";
|
|
111
|
+
} | {
|
|
112
|
+
status: "retry";
|
|
113
|
+
} | {
|
|
114
|
+
status: "error";
|
|
115
|
+
code: string;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* opus `publish` (I1): a name appears only with complete, durable content.
|
|
119
|
+
* Write tmp (wx + byte-count-checked write + fsync + close), then `link(2)`;
|
|
120
|
+
* EEXIST -> "exists", ENOENT at link -> "retry" (tmp reaped before link),
|
|
121
|
+
* any other failure -> "error" with the errno (no hard links -> fail closed).
|
|
122
|
+
* The tmp never contains `.succ.` (C2).
|
|
123
|
+
*/
|
|
124
|
+
export declare function publishLockRecord(path: string, rec: LockRec): PublishStatus;
|
|
125
|
+
type Liveness = "dead" | "live" | "undecidable";
|
|
126
|
+
/** Injected only by tests (platform / start-probe sims); production passes none. */
|
|
127
|
+
interface LivenessDeps {
|
|
128
|
+
readonly platform?: NodeJS.Platform;
|
|
129
|
+
readonly probe?: (pid: number) => {
|
|
130
|
+
state?: string;
|
|
131
|
+
start?: string;
|
|
132
|
+
} | undefined;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* A "live" verdict is `datable` when it came from a CONFIRMED, comparable start
|
|
136
|
+
* (same source, known equal time namespace) — the holder is provably the recorded
|
|
137
|
+
* process. It is NOT datable when "live" was reached only because the start was
|
|
138
|
+
* undatable (source mismatch, unknown/differing time namespace, or no start) — there
|
|
139
|
+
* a reused PID could be masking a dead holder. This bit is used solely to target the
|
|
140
|
+
* staleness alert (R2); it must never influence a reclaim decision.
|
|
141
|
+
*/
|
|
142
|
+
interface LivenessInfo {
|
|
143
|
+
readonly verdict: Liveness;
|
|
144
|
+
readonly datable: boolean;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Single liveness classifier. `livenessOf` and `isCertainlyDead` are exactly its
|
|
148
|
+
* "verdict"/"dead" arm, and the R2 staleness alert reads its `datable` bit, so none
|
|
149
|
+
* of them can drift from this one decision. Never derives "dead" from a fragile
|
|
150
|
+
* comparison: a source mismatch or an unknown/differing time namespace both yield a
|
|
151
|
+
* safe "live" (undatable), and a corrupt "legacy" start yields "undecidable" (C1).
|
|
152
|
+
*/
|
|
153
|
+
export declare function classifyLiveness(r: LockRec, self: SelfIdent, deps?: LivenessDeps): LivenessInfo;
|
|
154
|
+
export declare function livenessOf(r: LockRec, self: SelfIdent, deps?: LivenessDeps): Liveness;
|
|
155
|
+
/** No false positive: true implies certainly dead; any doubt is alive (I3). */
|
|
156
|
+
export declare function isCertainlyDead(r: LockRec): boolean;
|
|
157
|
+
/** opus `acquirePrefixLock`, plus M5 reasons and test-only critical hooks. */
|
|
158
|
+
export declare function acquirePrefixLock(prefix: string, hooks?: PrefixLockHooks): PrefixLockLease;
|
|
159
|
+
/** True when the path is older than the threshold; false when unstated. */
|
|
160
|
+
export declare function isOlderThan(path: string, now: number, maxAgeMs: number): boolean;
|
|
161
|
+
export {};
|
|
162
|
+
//# sourceMappingURL=succession-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"succession-lock.d.ts","sourceRoot":"","sources":["../../../src/runtime/local-files/succession-lock.ts"],"names":[],"mappings":"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"}
|