@namzu/sdk 20.3.0 → 20.4.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/dist/public-runtime.d.ts +1 -0
  3. package/dist/public-runtime.d.ts.map +1 -1
  4. package/dist/public-runtime.js +5 -0
  5. package/dist/public-runtime.js.map +1 -1
  6. package/dist/runtime/query/checkpoint.d.ts +27 -1
  7. package/dist/runtime/query/checkpoint.d.ts.map +1 -1
  8. package/dist/runtime/query/checkpoint.js +34 -4
  9. package/dist/runtime/query/checkpoint.js.map +1 -1
  10. package/dist/runtime/query/index.d.ts +21 -1
  11. package/dist/runtime/query/index.d.ts.map +1 -1
  12. package/dist/runtime/query/index.js +4 -0
  13. package/dist/runtime/query/index.js.map +1 -1
  14. package/dist/runtime/query/resume-run.d.ts +9 -1
  15. package/dist/runtime/query/resume-run.d.ts.map +1 -1
  16. package/dist/runtime/query/resume-run.js +2 -1
  17. package/dist/runtime/query/resume-run.js.map +1 -1
  18. package/dist/store/index.d.ts +1 -1
  19. package/dist/store/index.d.ts.map +1 -1
  20. package/dist/store/index.js +1 -1
  21. package/dist/store/index.js.map +1 -1
  22. package/dist/store/run/checkpoint-disk.d.ts +18 -2
  23. package/dist/store/run/checkpoint-disk.d.ts.map +1 -1
  24. package/dist/store/run/checkpoint-disk.js +50 -5
  25. package/dist/store/run/checkpoint-disk.js.map +1 -1
  26. package/dist/store/run/checkpoint-memory.d.ts +22 -2
  27. package/dist/store/run/checkpoint-memory.d.ts.map +1 -1
  28. package/dist/store/run/checkpoint-memory.js +99 -4
  29. package/dist/store/run/checkpoint-memory.js.map +1 -1
  30. package/dist/store/run/claim-disk.d.ts +130 -0
  31. package/dist/store/run/claim-disk.d.ts.map +1 -0
  32. package/dist/store/run/claim-disk.js +550 -0
  33. package/dist/store/run/claim-disk.js.map +1 -0
  34. package/dist/store/run/listing.d.ts +44 -1
  35. package/dist/store/run/listing.d.ts.map +1 -1
  36. package/dist/store/run/listing.js +92 -1
  37. package/dist/store/run/listing.js.map +1 -1
  38. package/dist/types/run/checkpoint-store.d.ts +178 -2
  39. package/dist/types/run/checkpoint-store.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/src/public-runtime.ts +5 -0
  42. package/src/runtime/query/checkpoint.ts +42 -5
  43. package/src/runtime/query/index.ts +26 -1
  44. package/src/runtime/query/resume-run.ts +12 -2
  45. package/src/store/index.ts +4 -0
  46. package/src/store/run/checkpoint-disk.ts +70 -5
  47. package/src/store/run/checkpoint-memory.ts +118 -3
  48. package/src/store/run/claim-disk.ts +593 -0
  49. package/src/store/run/listing.ts +116 -1
  50. package/src/types/run/checkpoint-store.ts +189 -2
@@ -0,0 +1,130 @@
1
+ /**
2
+ * A run claim on a filesystem, correct across PROCESSES.
3
+ *
4
+ * ## The fence is the filename
5
+ *
6
+ * A run's claims live at `{runDir}/claims/{fence}.json`. Taking the run means
7
+ * exclusively creating the next number; the kernel makes exactly one of any
8
+ * number of simultaneous callers the creator, and every other gets `EEXIST`.
9
+ * The current holding is the highest-numbered file.
10
+ *
11
+ * That single decision is the whole mechanism, and it is what the first
12
+ * version of this file got wrong. That version kept ONE mutable `claim.json`
13
+ * and protected it with a lock, which needed a stale-lock breaker, which was
14
+ * `unlink` followed by an exclusive create — two operations. An adversarial
15
+ * pass reproduced the consequence from separate processes: two workers both
16
+ * judge a stale guard breakable, the second unlinks the FIRST one's fresh
17
+ * guard, both end up inside the section believing they hold it, neither has
18
+ * written yet so neither can be the loser of a re-read, and both write the
19
+ * same fence. Twenty-six of three hundred runs went to two workers at an
20
+ * identical fence — and an identical fence fences nobody out, because the
21
+ * comparison is `<`.
22
+ *
23
+ * Numbering the files instead removes every one of those steps. There is no
24
+ * lock to go stale, so no breaker, so no window. Concurrency is decided by
25
+ * one `O_CREAT | O_EXCL`.
26
+ *
27
+ * ## Four properties this layout gives for free
28
+ *
29
+ * **Monotonic across release and deletion.** Fences are file names that stay,
30
+ * so releasing cannot rewind the counter. Deleting `claim.json` used to send
31
+ * the next caller down a fresh-claim path that minted fence 1 again — so a
32
+ * worker stalled at fence 1 could write alongside the new holder, and the
33
+ * documented `finally { releaseRun() }` did it on every pass. Releasing here
34
+ * appends a tombstone rather than removing anything.
35
+ *
36
+ * **Unreadable content cannot wedge the run.** The fence is in the name, so a
37
+ * damaged or half-written body never hides the ordering. A caller reads the
38
+ * highest number and takes the next one; the previous holder, alive or not,
39
+ * is fenced out by arithmetic. Refusing to take an unparseable claim was safe
40
+ * against double-writing and left the run permanently unclaimable, which is
41
+ * the failure a lease exists to prevent.
42
+ *
43
+ * **The write-time fence check needs no parsing at all.** It compares a
44
+ * number against a directory listing, so a corrupt body cannot make the check
45
+ * skip itself.
46
+ *
47
+ * **No `rename` and no `unlink` on the contended path.** Renaming over a path
48
+ * another process merely holds open for reading fails on non-POSIX
49
+ * filesystems — measured at 84% under two concurrent readers — and a listing
50
+ * sweep reads exactly these files. Creating a new name never collides with a
51
+ * reader.
52
+ *
53
+ * ## The name appears already complete, because `link` publishes it
54
+ *
55
+ * An exclusive create decides the winner, but `wx` is open-THEN-write: for an
56
+ * instant the winning name exists and is empty. A reader landing in it parses
57
+ * nothing, reports the holding expired, and a second worker takes the next
58
+ * fence. Their fences differ so the loser's first checkpoint is refused — and
59
+ * both have already restored the run and executed its tools by then, and tool
60
+ * side effects are fenced by nothing.
61
+ *
62
+ * So the body is written to a temporary name first and the fence name is
63
+ * created by `link`ing to it. `link` fails `EEXIST` when the destination
64
+ * exists, so it arbitrates exactly as `wx` did, and the destination it creates
65
+ * is a second name for a file that was already whole. There is no instant at
66
+ * which the fence name exists and its body does not.
67
+ *
68
+ * Measured from separate OS processes on both platform families:
69
+ *
70
+ * - `link` refused an existing destination 20,000/20,000 times. Six writers
71
+ * over 6,000 fences produced no fence with two winners and none with none.
72
+ * - Paired identical fixtures: `wx` showed an empty destination in 15,985 of
73
+ * 16,000 first observations, `link` in 0 of 16,000. All 156,000 frontier
74
+ * observations were `ENOENT` or a complete parseable body — none empty, none
75
+ * torn.
76
+ * - `rename` is disqualified, and not for the reason first assumed. It never
77
+ * reports `EEXIST`; it silently REPLACES, 20,000/20,000. It cannot arbitrate
78
+ * a race at all — two workers publishing one fence would both succeed and
79
+ * the second would erase the first. (Its `EPERM`-under-readers failure is
80
+ * real too, at 93.7% on the non-POSIX family, but the exclusivity failure
81
+ * disqualifies it first.)
82
+ *
83
+ * Cost: three syscalls rather than one, +1.0 ms per acquisition on the
84
+ * non-POSIX family and +0.04 ms on POSIX, for an operation that runs once per
85
+ * run plus renewals.
86
+ *
87
+ * ## What it still does not do
88
+ *
89
+ * It does not detect liveness. Nothing can from here: a stalled holder, a
90
+ * suspended container and a partitioned network are indistinguishable, and
91
+ * they are indistinguishable from the holder's own side too, which is why it
92
+ * keeps writing. The fence is the answer — the write is checked, not the
93
+ * writer.
94
+ */
95
+ import type { ClaimFence, ClaimRunOptions, RunClaim } from '../../types/run/checkpoint-store.js';
96
+ /**
97
+ * The highest fence ever issued for this run, or 0 when it has never been
98
+ * claimed.
99
+ *
100
+ * Reads names only. This is deliberately the one question the contended path
101
+ * asks, because a name cannot be half-written: a file either exists or does
102
+ * not, where a body can be observed mid-write.
103
+ */
104
+ export declare function currentFence(runDir: string): Promise<ClaimFence>;
105
+ /**
106
+ * The run's current holding, or `null` when it has never been claimed.
107
+ *
108
+ * Returns `null` for an unreadable body too, and that is safe HERE in a way
109
+ * it was not in the previous design: the fence is known from the name
110
+ * regardless, so an unreadable body means "somebody took this number and its
111
+ * details are unavailable", and the caller's response is to take the NEXT
112
+ * number rather than to give up. Nothing is inferred from the absence.
113
+ */
114
+ export declare function readClaim(runDir: string): Promise<RunClaim | null>;
115
+ /** Take or extend the run's claim. `null` when somebody else holds it. */
116
+ export declare function acquireClaim(runDir: string, options: ClaimRunOptions): Promise<RunClaim | null>;
117
+ /**
118
+ * Give up a holding early, so the run returns to the queue without waiting
119
+ * out its lease.
120
+ *
121
+ * Appends a tombstone at the next fence rather than deleting anything. The
122
+ * counter must never rewind: a worker stalled at an old fence has to stay
123
+ * fenced out forever, and removing the record would let a later claimer be
124
+ * issued a number that stalled worker already believes it holds.
125
+ *
126
+ * A stale fence releases nothing — a worker that stalled past its lease must
127
+ * not be able to hand away a run somebody else now holds.
128
+ */
129
+ export declare function releaseClaim(runDir: string, fence: ClaimFence): Promise<void>;
130
+ //# sourceMappingURL=claim-disk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim-disk.d.ts","sourceRoot":"","sources":["../../../src/store/run/claim-disk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6FG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,qCAAqC,CAAA;AAoRhG;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAGtE;AA8DD;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CA6DxE;AAED,0EAA0E;AAC1E,wBAAsB,YAAY,CACjC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,eAAe,GACtB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAoC1B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBnF"}