@namzu/sdk 20.2.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 (82) hide show
  1. package/CHANGELOG.md +185 -0
  2. package/dist/manager/run/persistence.d.ts +2 -2
  3. package/dist/manager/run/persistence.d.ts.map +1 -1
  4. package/dist/manager/run/persistence.js +14 -5
  5. package/dist/manager/run/persistence.js.map +1 -1
  6. package/dist/public-runtime.d.ts +2 -1
  7. package/dist/public-runtime.d.ts.map +1 -1
  8. package/dist/public-runtime.js +6 -1
  9. package/dist/public-runtime.js.map +1 -1
  10. package/dist/runtime/query/checkpoint.d.ts +27 -1
  11. package/dist/runtime/query/checkpoint.d.ts.map +1 -1
  12. package/dist/runtime/query/checkpoint.js +34 -4
  13. package/dist/runtime/query/checkpoint.js.map +1 -1
  14. package/dist/runtime/query/context.d.ts +2 -0
  15. package/dist/runtime/query/context.d.ts.map +1 -1
  16. package/dist/runtime/query/context.js +1 -0
  17. package/dist/runtime/query/context.js.map +1 -1
  18. package/dist/runtime/query/index.d.ts +32 -1
  19. package/dist/runtime/query/index.d.ts.map +1 -1
  20. package/dist/runtime/query/index.js +5 -0
  21. package/dist/runtime/query/index.js.map +1 -1
  22. package/dist/runtime/query/resume-run.d.ts +9 -1
  23. package/dist/runtime/query/resume-run.d.ts.map +1 -1
  24. package/dist/runtime/query/resume-run.js +2 -1
  25. package/dist/runtime/query/resume-run.js.map +1 -1
  26. package/dist/store/index.d.ts +2 -1
  27. package/dist/store/index.d.ts.map +1 -1
  28. package/dist/store/index.js +2 -1
  29. package/dist/store/index.js.map +1 -1
  30. package/dist/store/run/checkpoint-disk.d.ts +18 -2
  31. package/dist/store/run/checkpoint-disk.d.ts.map +1 -1
  32. package/dist/store/run/checkpoint-disk.js +50 -5
  33. package/dist/store/run/checkpoint-disk.js.map +1 -1
  34. package/dist/store/run/checkpoint-memory.d.ts +22 -2
  35. package/dist/store/run/checkpoint-memory.d.ts.map +1 -1
  36. package/dist/store/run/checkpoint-memory.js +99 -4
  37. package/dist/store/run/checkpoint-memory.js.map +1 -1
  38. package/dist/store/run/claim-disk.d.ts +130 -0
  39. package/dist/store/run/claim-disk.d.ts.map +1 -0
  40. package/dist/store/run/claim-disk.js +550 -0
  41. package/dist/store/run/claim-disk.js.map +1 -0
  42. package/dist/store/run/disk.d.ts +10 -8
  43. package/dist/store/run/disk.d.ts.map +1 -1
  44. package/dist/store/run/disk.js.map +1 -1
  45. package/dist/store/run/listing.d.ts +44 -1
  46. package/dist/store/run/listing.d.ts.map +1 -1
  47. package/dist/store/run/listing.js +92 -1
  48. package/dist/store/run/listing.js.map +1 -1
  49. package/dist/store/run/memory.d.ts +46 -0
  50. package/dist/store/run/memory.d.ts.map +1 -0
  51. package/dist/store/run/memory.js +104 -0
  52. package/dist/store/run/memory.js.map +1 -0
  53. package/dist/types/run/checkpoint-store.d.ts +178 -2
  54. package/dist/types/run/checkpoint-store.d.ts.map +1 -1
  55. package/dist/types/run/config.d.ts +12 -0
  56. package/dist/types/run/config.d.ts.map +1 -1
  57. package/dist/types/run/index.d.ts +1 -0
  58. package/dist/types/run/index.d.ts.map +1 -1
  59. package/dist/types/run/index.js +1 -0
  60. package/dist/types/run/index.js.map +1 -1
  61. package/dist/types/run/store.d.ts +103 -0
  62. package/dist/types/run/store.d.ts.map +1 -0
  63. package/dist/types/run/store.js +30 -0
  64. package/dist/types/run/store.js.map +1 -0
  65. package/package.json +1 -1
  66. package/src/manager/run/persistence.ts +17 -7
  67. package/src/public-runtime.ts +6 -0
  68. package/src/runtime/query/checkpoint.ts +42 -5
  69. package/src/runtime/query/context.ts +3 -0
  70. package/src/runtime/query/index.ts +39 -1
  71. package/src/runtime/query/resume-run.ts +12 -2
  72. package/src/store/index.ts +5 -0
  73. package/src/store/run/checkpoint-disk.ts +70 -5
  74. package/src/store/run/checkpoint-memory.ts +118 -3
  75. package/src/store/run/claim-disk.ts +593 -0
  76. package/src/store/run/disk.ts +10 -8
  77. package/src/store/run/listing.ts +116 -1
  78. package/src/store/run/memory.ts +121 -0
  79. package/src/types/run/checkpoint-store.ts +189 -2
  80. package/src/types/run/config.ts +13 -0
  81. package/src/types/run/index.ts +1 -0
  82. package/src/types/run/store.ts +112 -0
@@ -3,11 +3,20 @@ import type {
3
3
  CheckpointListingScope,
4
4
  CheckpointRunScope,
5
5
  CheckpointStore,
6
+ ClaimFence,
7
+ ClaimRunOptions,
6
8
  DurableRunEntry,
7
9
  DurableRunPage,
8
10
  ListDurableRunsOptions,
11
+ RunClaim,
9
12
  } from '../../types/run/checkpoint-store.js'
10
- import { assertContiguousListingScope, paginateDurableRuns, toDurableRunEntry } from './listing.js'
13
+ import {
14
+ assertContiguousListingScope,
15
+ fencedOut,
16
+ paginateDurableRuns,
17
+ toClaimSummary,
18
+ toDurableRunEntry,
19
+ } from './listing.js'
11
20
 
12
21
  /**
13
22
  * Process-local {@link CheckpointStore}, keyed by the full five-layer scope.
@@ -35,7 +44,111 @@ export class InMemoryCheckpointStore implements CheckpointStore {
35
44
  return [scope.tenantId, scope.projectId, scope.sessionId, scope.runId].join('/')
36
45
  }
37
46
 
38
- async writeCheckpoint(scope: CheckpointRunScope, checkpoint: IterationCheckpoint): Promise<void> {
47
+ /** `tenant/project/session/run` → the run's current holding, if any. */
48
+ private readonly claims = new Map<string, RunClaim>()
49
+
50
+ /**
51
+ * The highest fence ever issued per run, kept separately from the claim.
52
+ *
53
+ * The claim is removed on release; this is not. That separation is the
54
+ * whole point: the first version deleted the claim and then computed the
55
+ * next fence from it, so releasing rewound the counter to 1 and a worker
56
+ * stalled at fence 1 could write beside a new holder also at fence 1 —
57
+ * and the documented `finally { releaseRun() }` did it on every pass.
58
+ *
59
+ * The disk store gets this property from file names that persist. In
60
+ * memory the equivalent is a high-water mark nothing clears, and the two
61
+ * must agree, because this class is what a host reads when writing a
62
+ * backend of its own.
63
+ */
64
+ private readonly highWater = new Map<string, ClaimFence>()
65
+
66
+ async claimRun(scope: CheckpointRunScope, options: ClaimRunOptions): Promise<RunClaim | null> {
67
+ const key = this.key(scope)
68
+ const now = options.now ?? Date.now()
69
+ const held = this.claims.get(key)
70
+
71
+ // Held by somebody else and still live. Not an error: two readers on
72
+ // one queue is the ordinary case.
73
+ if (held && now < held.expiresAt && held.holder !== options.holder) return null
74
+
75
+ // A reclaim of an expired holding and a renewal by the current holder
76
+ // are the same write. The fence advances either way, so a previous
77
+ // holder that wakes up is fenced out in both cases — a renewal that
78
+ // kept the fence would leave a stalled twin able to write.
79
+ // Counted from the high-water mark, never from the live claim. A
80
+ // released run has no claim, and computing from that absence is what
81
+ // rewound the counter to 1 on every release.
82
+ const fence = (this.highWater.get(key) ?? 0) + 1
83
+ const claim: RunClaim = { holder: options.holder, fence, expiresAt: now + options.ttlMs }
84
+ this.highWater.set(key, fence)
85
+ this.claims.set(key, claim)
86
+ return claim
87
+ }
88
+
89
+ async releaseRun(scope: CheckpointRunScope, fence: ClaimFence): Promise<void> {
90
+ const key = this.key(scope)
91
+ const held = this.claims.get(key)
92
+ // A stale fence releases nothing. A worker that stalled past its lease
93
+ // must not be able to hand away a run somebody else now holds.
94
+ //
95
+ // The high-water mark deliberately survives this. Dropping the claim
96
+ // returns the run to the queue; forgetting the number it reached would
97
+ // re-issue a fence a stalled worker still believes it holds.
98
+ if (!held || held.fence !== fence) return
99
+ this.claims.delete(key)
100
+ // And the counter steps past the released holding, because the disk
101
+ // store's release does: it appends a TOMBSTONE at `fence + 1`, which
102
+ // raises the maximum name and therefore the fence every later write is
103
+ // checked against. Without this step a holder that released could still
104
+ // write with the fence it just gave up — refused on disk, accepted here.
105
+ //
106
+ // The consequence is visible in the numbering: a released run's next
107
+ // claim is `fence + 2` in both stores, because the tombstone consumed
108
+ // one. That is parity, not an off-by-one.
109
+ this.highWater.set(key, fence + 1)
110
+ }
111
+
112
+ async writeCheckpoint(
113
+ scope: CheckpointRunScope,
114
+ checkpoint: IterationCheckpoint,
115
+ fence?: ClaimFence,
116
+ ): Promise<void> {
117
+ const key = this.key(scope)
118
+ // An unfenced write is allowed even on a claimed run: a host adopting
119
+ // claims on one worker must not break the workers that have not
120
+ // adopted them. A fenced write is checked, and that check is what
121
+ // makes the lease real.
122
+ if (fence !== undefined) {
123
+ // Against the HIGH-WATER MARK, not the live claim, and the difference
124
+ // is a silent loss rather than a duplicate.
125
+ //
126
+ // `releaseRun` deletes the claim. Reading `claims` here meant that
127
+ // between a release and the next take there was no holding to
128
+ // compare against, so `held` was `undefined` and every fence was
129
+ // accepted however stale. w1 stalls at fence 1; w2 reclaims at 2,
130
+ // finishes the work, releases cleanly; w1 wakes and writes with
131
+ // fence 1 — accepted — and its checkpoint carries a fresh
132
+ // `createdAt`, so it sorts newest and the next resume restores w1's
133
+ // stale history. w2's completed work is gone, with no error
134
+ // anywhere.
135
+ //
136
+ // The disk store refuses that write, because `currentFence` reads
137
+ // file names and the release tombstone raised the maximum. The
138
+ // minting side here already counted from the high-water mark; only
139
+ // the enforcement side was left reading the claim, so the two
140
+ // shipped stores disagreed at the one point that decides whether a
141
+ // lease is real.
142
+ const current = this.highWater.get(key) ?? 0
143
+ if (fence < current) throw fencedOut(scope, fence, current)
144
+ }
145
+ return this.writeUnchecked(scope, checkpoint)
146
+ }
147
+
148
+ private async writeUnchecked(
149
+ scope: CheckpointRunScope,
150
+ checkpoint: IterationCheckpoint,
151
+ ): Promise<void> {
39
152
  const key = this.key(scope)
40
153
  let run = this.runs.get(key)
41
154
  if (!run) {
@@ -93,7 +206,9 @@ export class InMemoryCheckpointStore implements CheckpointStore {
93
206
  if (scope.sessionId !== undefined && runScope.sessionId !== scope.sessionId) continue
94
207
 
95
208
  const entry = toDurableRunEntry(runScope, [...checkpoints.values()], now)
96
- if (entry) entries.push(entry)
209
+ if (!entry) continue
210
+ const claim = this.claims.get(key)
211
+ entries.push(claim ? { ...entry, claim: toClaimSummary(claim, now) } : entry)
97
212
  }
98
213
 
99
214
  return paginateDurableRuns(entries, options)