bridgebench 3.1.0-alpha.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 (86) hide show
  1. package/CITATION.cff +15 -0
  2. package/LICENSE +21 -0
  3. package/README.md +249 -0
  4. package/dist/chunk-4TWPCPRP.cjs +1097 -0
  5. package/dist/chunk-4TWPCPRP.cjs.map +1 -0
  6. package/dist/chunk-7YCJSOK7.cjs +398 -0
  7. package/dist/chunk-7YCJSOK7.cjs.map +1 -0
  8. package/dist/chunk-CIXITJW6.cjs +249 -0
  9. package/dist/chunk-CIXITJW6.cjs.map +1 -0
  10. package/dist/chunk-EQHRUV2I.js +1466 -0
  11. package/dist/chunk-EQHRUV2I.js.map +1 -0
  12. package/dist/chunk-JTVNKSMO.js +1096 -0
  13. package/dist/chunk-JTVNKSMO.js.map +1 -0
  14. package/dist/chunk-LFKEV2YL.js +398 -0
  15. package/dist/chunk-LFKEV2YL.js.map +1 -0
  16. package/dist/chunk-NJTYVNP4.cjs +1467 -0
  17. package/dist/chunk-NJTYVNP4.cjs.map +1 -0
  18. package/dist/chunk-UECBSKTD.js +244 -0
  19. package/dist/chunk-UECBSKTD.js.map +1 -0
  20. package/dist/cli.cjs +409 -0
  21. package/dist/cli.cjs.map +1 -0
  22. package/dist/cli.d.cts +1 -0
  23. package/dist/cli.d.ts +1 -0
  24. package/dist/cli.js +408 -0
  25. package/dist/cli.js.map +1 -0
  26. package/dist/client.cjs +42 -0
  27. package/dist/client.cjs.map +1 -0
  28. package/dist/client.d.cts +93 -0
  29. package/dist/client.d.ts +93 -0
  30. package/dist/client.js +42 -0
  31. package/dist/client.js.map +1 -0
  32. package/dist/contracts/index.cjs +47 -0
  33. package/dist/contracts/index.cjs.map +1 -0
  34. package/dist/contracts/index.d.cts +14 -0
  35. package/dist/contracts/index.d.ts +14 -0
  36. package/dist/contracts/index.js +47 -0
  37. package/dist/contracts/index.js.map +1 -0
  38. package/dist/index.cjs +171 -0
  39. package/dist/index.cjs.map +1 -0
  40. package/dist/index.d.cts +214 -0
  41. package/dist/index.d.ts +214 -0
  42. package/dist/index.js +171 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/logger-CCR9Mg1c.d.cts +319 -0
  45. package/dist/logger-QJU7SBDz.d.ts +319 -0
  46. package/dist/reports-4CejmOHf.d.cts +454 -0
  47. package/dist/reports-s2CTnGN8.d.ts +454 -0
  48. package/dist/tasks-CpaCJ6JE.d.cts +151 -0
  49. package/dist/tasks-CpaCJ6JE.d.ts +151 -0
  50. package/dist/tasks.cjs +22 -0
  51. package/dist/tasks.cjs.map +1 -0
  52. package/dist/tasks.d.cts +39 -0
  53. package/dist/tasks.d.ts +39 -0
  54. package/dist/tasks.js +22 -0
  55. package/dist/tasks.js.map +1 -0
  56. package/docs/README.md +25 -0
  57. package/docs/glossary.md +49 -0
  58. package/docs/methodology.md +58 -0
  59. package/docs/private-packs.md +74 -0
  60. package/docs/replay-elo.md +79 -0
  61. package/docs/task-authoring.md +80 -0
  62. package/package.json +137 -0
  63. package/tasks/hallucination/public/boundary-coverage-audit.yaml +274 -0
  64. package/tasks/hallucination/public/boundary-migration-audit.yaml +284 -0
  65. package/tasks/hallucination/public/conflict-dependency-versions.yaml +324 -0
  66. package/tasks/hallucination/public/conflict-runbook-versions.yaml +229 -0
  67. package/tasks/hallucination/public/fabrication-agent-tools.yaml +224 -0
  68. package/tasks/hallucination/public/fabrication-api-surface.yaml +239 -0
  69. package/tasks/hallucination/public/fidelity-commit-attribution.yaml +304 -0
  70. package/tasks/hallucination/public/fidelity-config-drift.yaml +307 -0
  71. package/tasks/hallucination/public/missing-deploy-window.yaml +204 -0
  72. package/tasks/hallucination/public/missing-latency-baseline.yaml +239 -0
  73. package/tasks/hallucination/public/premise-quota-breach.yaml +202 -0
  74. package/tasks/hallucination/public/premise-rollback-cause.yaml +235 -0
  75. package/tasks/reasoning/public/constraint-capacity-allocation.yaml +196 -0
  76. package/tasks/reasoning/public/constraint-deployment-policy.yaml +203 -0
  77. package/tasks/reasoning/public/counterexample-authorization-rule.yaml +278 -0
  78. package/tasks/reasoning/public/counterexample-scheduler-starvation.yaml +290 -0
  79. package/tasks/reasoning/public/root-cache-tenant-leak.yaml +225 -0
  80. package/tasks/reasoning/public/root-event-ordering.yaml +184 -0
  81. package/tasks/reasoning/public/stateful-lease-handoff.yaml +213 -0
  82. package/tasks/reasoning/public/stateful-retry-budget.yaml +222 -0
  83. package/tasks/reasoning/public/synthesis-api-contract.yaml +214 -0
  84. package/tasks/reasoning/public/synthesis-permission-migration.yaml +190 -0
  85. package/tasks/reasoning/public/uncertainty-conflicting-telemetry.yaml +242 -0
  86. package/tasks/reasoning/public/uncertainty-incomplete-incident.yaml +223 -0
@@ -0,0 +1,239 @@
1
+ id: fabrication-api-surface
2
+ version: 1.0.0
3
+ category: hallucination
4
+ cluster: entity-fabrication
5
+ difficulty: expert
6
+ title: Lease Coordination Service API Surface
7
+ summary: Answer targeted questions about a distributed-lease API where several plausible-sounding endpoints, functions, config keys, and error codes are never actually defined.
8
+ prompt: |
9
+ You maintain the "leasekit" distributed-lease coordination service. A new team is
10
+ integrating against it and has sent six questions. Answer each using ONLY the
11
+ supplied artifacts (api-spec, lease-service-code, lease-config, error-catalog,
12
+ request-log, integration-guide, roadmap-note). Cite the artifact id(s) you rely on for every claim.
13
+ If an endpoint, function, configuration key, error code, or behavior is not defined
14
+ anywhere in the provided artifacts, say so explicitly and do not describe it by
15
+ analogy to a similar one.
16
+
17
+ 1. When a client calls POST /v2/leases for a resource that another holder already
18
+ owns, what HTTP status code and error identifier does the service return, and
19
+ which function raises it?
20
+
21
+ 2. What is the effective maximum lifetime of a single lease (from initial
22
+ acquisition to final expiry) under the current configuration, assuming the holder
23
+ renews at the last possible moment each time? Show the numbers you multiply.
24
+
25
+ 3. Document the request body and semantics of POST /v2/lease-transfers, including how
26
+ the lease.transferGracePeriodSeconds setting governs the hand-off window between
27
+ the old and new holder.
28
+
29
+ 4. A worker must hold a lease continuously for a single 5-minute (300-second) job.
30
+ Using only the endpoints/functions and configuration that exist, is that possible
31
+ without ever losing the lease, and exactly which calls achieve it? Justify with
32
+ the numbers.
33
+
34
+ 5. What HTTP status code does the LEASE_TRANSFER_DENIED error map to, and under what
35
+ conditions is it emitted?
36
+
37
+ 6. The integration guide the team was handed mentions a helper called
38
+ acquireLeaseWithBackoff. Explain how it differs from acquireLeaseWithRetry and
39
+ what its default backoff parameters are.
40
+ artifacts:
41
+ - id: api-spec
42
+ type: spec
43
+ label: leasekit v2 HTTP API reference
44
+ content: |
45
+ leasekit HTTP API — base path /v2 (all responses application/json)
46
+
47
+ POST /v2/leases
48
+ Purpose: acquire a lease on a named resource.
49
+ Request: { "resource": string, "holder": string, "ttlSeconds"?: number }
50
+ ttlSeconds is optional and, if omitted, defaults to lease.ttlSeconds.
51
+ A caller may not request a ttlSeconds greater than lease.ttlSeconds.
52
+ Success: 201 Created
53
+ { "leaseId": string, "resource": string, "holder": string,
54
+ "expiresAt": ISO-8601, "renewalsUsed": 0 }
55
+ Errors: 409 LEASE_HELD when the resource already has a live lease held by a
56
+ different holder.
57
+
58
+ GET /v2/leases/{leaseId}
59
+ Purpose: read the current state of a lease.
60
+ Success: 200 OK { "leaseId", "resource", "holder", "expiresAt",
61
+ "renewalsUsed" }
62
+ Errors: 404 LEASE_NOT_FOUND, 410 LEASE_EXPIRED.
63
+
64
+ DELETE /v2/leases/{leaseId}
65
+ Purpose: release a lease early. Idempotent for the owning holder.
66
+ Success: 204 No Content
67
+ Errors: 404 LEASE_NOT_FOUND.
68
+
69
+ POST /v2/lease-renewals
70
+ Purpose: extend a lease the caller already holds.
71
+ Request: { "leaseId": string, "holder": string }
72
+ Semantics: a renewal accepted at wall-clock time t sets expiresAt to
73
+ t + lease.ttlSeconds and increments renewalsUsed by 1. A lease may be
74
+ renewed at most lease.maxRenewals times over its life.
75
+ Success: 200 OK { "leaseId", "expiresAt", "renewalsUsed" }
76
+ Errors: 410 LEASE_EXPIRED (lease already lapsed),
77
+ 409 LEASE_RENEWAL_EXHAUSTED (renewalsUsed already equals
78
+ lease.maxRenewals),
79
+ 404 LEASE_NOT_FOUND.
80
+
81
+ There are no other v2 routes. This document is the complete route list for the
82
+ 2.x line.
83
+ - id: lease-service-code
84
+ type: code
85
+ label: leasekit client SDK (excerpt)
86
+ content: |
87
+ // leasekit/client.ts — the only public client entry points in the 2.x SDK.
88
+
89
+ export async function acquireLease(
90
+ resource: string,
91
+ holder: string,
92
+ ttlSeconds?: number,
93
+ ): Promise<Lease> {
94
+ const res = await http.post('/v2/leases', { resource, holder, ttlSeconds });
95
+ if (res.status === 409) {
96
+ // resource is held by someone else
97
+ throw new LeaseError('LEASE_HELD', 409, res.body);
98
+ }
99
+ return res.body as Lease;
100
+ }
101
+
102
+ // Retries ONLY the initial acquisition when the resource is currently held.
103
+ // It does not renew, and it does not transfer ownership.
104
+ export async function acquireLeaseWithRetry(
105
+ resource: string,
106
+ holder: string,
107
+ opts: { maxAttempts?: number; baseDelayMs?: number; factor?: number } = {},
108
+ ): Promise<Lease> {
109
+ const maxAttempts = opts.maxAttempts ?? 4;
110
+ const baseDelayMs = opts.baseDelayMs ?? 200;
111
+ const factor = opts.factor ?? 2;
112
+ let attempt = 0;
113
+ for (;;) {
114
+ try {
115
+ return await acquireLease(resource, holder);
116
+ } catch (e) {
117
+ attempt += 1;
118
+ if (!(e instanceof LeaseError) || e.code !== 'LEASE_HELD') throw e;
119
+ if (attempt >= maxAttempts) throw e;
120
+ const delay = baseDelayMs * factor ** (attempt - 1); // 200, 400, 800 ms
121
+ await sleep(delay);
122
+ }
123
+ }
124
+ }
125
+
126
+ export async function renewLease(leaseId: string, holder: string): Promise<Lease> {
127
+ const res = await http.post('/v2/lease-renewals', { leaseId, holder });
128
+ if (res.status === 409) throw new LeaseError('LEASE_RENEWAL_EXHAUSTED', 409, res.body);
129
+ if (res.status === 410) throw new LeaseError('LEASE_EXPIRED', 410, res.body);
130
+ return res.body as Lease;
131
+ }
132
+
133
+ export async function releaseLease(leaseId: string): Promise<void> {
134
+ await http.delete(`/v2/leases/${leaseId}`); // 204 or 404
135
+ }
136
+ - id: lease-config
137
+ type: config
138
+ label: leasekit runtime configuration (production)
139
+ content: |
140
+ # leasekit.production.yaml — full lease block, no keys omitted.
141
+ lease:
142
+ ttlSeconds: 45 # default and maximum initial time-to-live
143
+ renewalThresholdSeconds: 15 # holders SHOULD renew when remaining <= this
144
+ maxRenewals: 8 # hard cap on renewals per lease
145
+ sweepIntervalSeconds: 5 # expiry sweeper cadence
146
+
147
+ server:
148
+ port: 8080
149
+ maxInFlightAcquires: 256
150
+ - id: error-catalog
151
+ type: table
152
+ label: leasekit error identifier catalog
153
+ content: |
154
+ This catalog is exhaustive for the 2.x line.
155
+
156
+ code | http | meaning
157
+ --------------------------|------|--------------------------------------------
158
+ LEASE_HELD | 409 | resource already leased by a different holder
159
+ LEASE_EXPIRED | 410 | the lease has lapsed and is no longer valid
160
+ LEASE_NOT_FOUND | 404 | no lease exists for the given leaseId
161
+ LEASE_RENEWAL_EXHAUSTED | 409 | renewalsUsed already equals lease.maxRenewals
162
+ LEASE_HOLDER_MISMATCH | 403 | caller is not the recorded holder of the lease
163
+ - id: request-log
164
+ type: log
165
+ label: leasekit access log (worker w-17, one job)
166
+ content: |
167
+ 02:00:00.014 POST /v2/leases resource=shard-9 holder=w-17 -> 409 LEASE_HELD (held by w-04)
168
+ 02:00:00.221 POST /v2/leases resource=shard-9 holder=w-17 -> 409 LEASE_HELD (retry attempt=1)
169
+ 02:00:00.628 POST /v2/leases resource=shard-9 holder=w-17 -> 409 LEASE_HELD (retry attempt=2)
170
+ 02:00:01.437 POST /v2/leases resource=shard-9 holder=w-17 -> 201 leaseId=lz-771 expiresAt=02:00:46 renewalsUsed=0
171
+ 02:00:31.002 POST /v2/lease-renewals leaseId=lz-771 holder=w-17 -> 200 expiresAt=02:01:16 renewalsUsed=1
172
+ 02:01:02.550 POST /v2/lease-renewals leaseId=lz-771 holder=w-17 -> 200 expiresAt=02:01:47 renewalsUsed=2
173
+ 02:01:33.900 POST /v2/lease-renewals leaseId=lz-771 holder=w-17 -> 200 expiresAt=02:02:18 renewalsUsed=3
174
+ 02:02:05.101 POST /v2/lease-renewals leaseId=lz-771 holder=w-17 -> 200 expiresAt=02:02:50 renewalsUsed=4
175
+ 02:02:36.774 POST /v2/lease-renewals leaseId=lz-771 holder=w-17 -> 200 expiresAt=02:03:21 renewalsUsed=5
176
+ 02:03:08.220 POST /v2/lease-renewals leaseId=lz-771 holder=w-17 -> 200 expiresAt=02:03:53 renewalsUsed=6
177
+ 02:03:39.640 POST /v2/lease-renewals leaseId=lz-771 holder=w-17 -> 200 expiresAt=02:04:24 renewalsUsed=7
178
+ 02:04:11.005 POST /v2/lease-renewals leaseId=lz-771 holder=w-17 -> 200 expiresAt=02:04:56 renewalsUsed=8
179
+ 02:04:42.310 POST /v2/lease-renewals leaseId=lz-771 holder=w-17 -> 409 LEASE_RENEWAL_EXHAUSTED
180
+ 02:04:42.512 GET /v2/leases/lz-771 holder=w-17 -> 200 expiresAt=02:04:56 renewalsUsed=8
181
+ 02:04:55.900 GET /v2/leases/lz-771 holder=w-17 -> 200 expiresAt=02:04:56 renewalsUsed=8
182
+ 02:04:57.400 GET /v2/leases/lz-771 holder=w-17 -> 410 LEASE_EXPIRED
183
+ 02:04:57.610 DELETE /v2/leases/lz-771 holder=w-17 -> 404 LEASE_NOT_FOUND (already swept)
184
+ 02:05:00.000 POST /v2/leases resource=shard-9 holder=w-17 -> 201 leaseId=lz-802 expiresAt=02:05:45 renewalsUsed=0
185
+ 02:05:20.117 DELETE /v2/leases/lz-802 holder=w-17 -> 204 (released early, job done)
186
+ 02:05:20.480 GET /v2/leases/lz-802 holder=w-17 -> 404 LEASE_NOT_FOUND
187
+ 02:05:40.900 POST /v2/lease-renewals leaseId=lz-802 holder=w-17 -> 404 LEASE_NOT_FOUND
188
+ 02:06:00.000 POST /v2/leases resource=shard-3 holder=w-17 -> 201 leaseId=lz-806 expiresAt=02:06:45 renewalsUsed=0
189
+ 02:06:10.221 POST /v2/lease-renewals leaseId=lz-806 holder=w-05 -> 403 LEASE_HOLDER_MISMATCH
190
+ 02:06:44.900 POST /v2/lease-renewals leaseId=lz-806 holder=w-17 -> 200 expiresAt=02:07:29 renewalsUsed=1
191
+ 02:07:00.000 DELETE /v2/leases/lz-806 holder=w-17 -> 204
192
+ - id: integration-guide
193
+ type: note
194
+ label: leasekit integration guide (handed to the new team)
195
+ content: |
196
+ leasekit — getting started (2.x). This guide is complete for what a client can do
197
+ today; if something you expect is not listed here, it does not exist yet.
198
+
199
+ The four calls you will use:
200
+ - acquireLease(resource, holder, ttlSeconds?) -> grabs a lease, throws
201
+ LEASE_HELD (409) if someone else holds the resource.
202
+ - acquireLeaseWithRetry(resource, holder, opts) -> the SAME acquire, but retries
203
+ on LEASE_HELD with exponential backoff. Defaults: maxAttempts 4, baseDelayMs
204
+ 200, factor 2 (so waits of 200/400/800 ms). It retries acquisition ONLY; it
205
+ does not renew and it does not move a lease between holders.
206
+ - renewLease(leaseId, holder) -> extends a lease you already hold by
207
+ lease.ttlSeconds, up to lease.maxRenewals times.
208
+ - releaseLease(leaseId) -> gives the lease back early (DELETE).
209
+
210
+ Keeping a lease alive for a long job: there is NO background auto-renewer in the
211
+ SDK. You must call renewLease yourself before each expiry; use
212
+ lease.renewalThresholdSeconds as the "renew now" signal. When you are done, call
213
+ releaseLease so the next worker can acquire.
214
+
215
+ Moving work between workers: there is no hand-off or transfer call. The current
216
+ holder releases and the next worker acquires from scratch. If you need a specific
217
+ worker to take over, coordinate that above leasekit.
218
+
219
+ That is the entire client surface. There are no other acquire variants, no
220
+ transfer endpoints, and no error codes beyond the ones in the error catalog.
221
+ - id: roadmap-note
222
+ type: note
223
+ label: leasekit 3.x roadmap (internal, unshipped)
224
+ content: |
225
+ Notes toward the 3.x line. None of the items below exist in 2.x that is running
226
+ in production today; do not rely on them.
227
+
228
+ - Worker-to-worker lease hand-off: today a holder must release (DELETE) and the
229
+ next worker must acquire from scratch, racing anyone else. We want a smoother
230
+ hand-off so an in-flight job can move between workers without a gap. Design is
231
+ still open; nothing is implemented and no configuration or route for it has
232
+ been added.
233
+ - Automatic renewal loop baked into the SDK (right now callers must POST
234
+ /v2/lease-renewals themselves on a timer).
235
+ - Fair queueing so waiters acquire in arrival order instead of racing.
236
+
237
+ Reminder: the 2.x artifacts (api-spec, error-catalog, lease-config) are the
238
+ authoritative, complete surface for anything a client can call right now.
239
+ tags: [api, leases, entity-fabrication, hallucination]
@@ -0,0 +1,304 @@
1
+ id: fidelity-commit-attribution
2
+ version: 1.0.0
3
+ category: hallucination
4
+ cluster: citation-fidelity
5
+ difficulty: expert
6
+ title: Regression Commit and Revert Attribution From a Git History
7
+ summary: Reconstruct which commit introduced a lease-renewal regression, which reverted it, and the exact CI error and release tag, from a git log, CI log, blame, changelog, PR, and tags dense with near-duplicate SHAs, messages, and version numbers.
8
+ prompt: |
9
+ You are reconstructing exactly what happened around a regression in the
10
+ harbor-lease repository. Answer using only the artifacts.
11
+
12
+ Rules:
13
+ - Quote commit subjects, SHAs, authors, dates, error lines, and version tags
14
+ character-for-character from the named artifact.
15
+ - Quote commit subjects from the git log (git-log), NOT from the CHANGELOG or
16
+ the PR description - both paraphrase the commits and are not verbatim
17
+ commit messages.
18
+ - Short SHAs, commit subjects, revert messages, and version tags include
19
+ deliberate near-duplicates. Do not collapse a singular and a plural
20
+ subject, transpose a short SHA, or blend two similar error lines.
21
+ - Compare version tags by comparing each dotted field numerically, not as
22
+ text (v2.3.11 is a later release than v2.3.1).
23
+
24
+ Deliverables:
25
+ 1. Give the exact commit subject line (verbatim, as written in the git log)
26
+ and the short SHA of the commit that introduced the empty-batch
27
+ regression. Use the git blame at tag v2.3.1 and the CI stack trace to
28
+ identify it. Quote the subject from the commit, not from the CHANGELOG.
29
+ 2. Give the exact first line of the CI failure message for the empty-batch
30
+ test (the test named "renews an empty batch without throwing"). The same
31
+ CI run contains a second, similar TypeError for a different test - do not
32
+ report that one.
33
+ 3. Give the exact author and commit date of the commit that reverted the
34
+ regression. A different revert with a near-identical message exists;
35
+ attribute precisely.
36
+ 4. Quote the CHANGELOG entry for v2.3.1 verbatim, then state exactly how its
37
+ wording differs from the underlying commit subject.
38
+ 5. Two commits have the near-identical subjects "fix: handle empty lease
39
+ renewal" and "fix: handle empty lease renewals". State which one (by short
40
+ SHA and exact subject) added the empty-batch guard to
41
+ src/renewer/lease_renewer.ts, and name the first release tag that contains
42
+ it (mind v2.3.1 versus v2.3.11).
43
+ 6. From the git blame excerpt of src/renewer/lease_renewer.ts at v2.3.1, give
44
+ the exact line number and exact content of the line that throws on an
45
+ empty batch, and the short SHA blamed for it.
46
+ artifacts:
47
+ - id: git-log
48
+ type: log
49
+ label: harbor-lease git log (2.3.x line)
50
+ content: |
51
+ git log --pretty=format:'%h %ad %-12an %s' --date=format:'%Y-%m-%d %H:%M'
52
+ (harbor-lease, branch main; newest first; excerpt of the 2.3.x line)
53
+
54
+ b0417fa 2026-04-21 10:47 Oscar Duarte chore: prepare 2.3.11 release notes
55
+ f3c8a71 2026-04-20 15:12 Mei Cho test: assert LeaseRenewalResult.empty on no pending
56
+ e9b6d14 2026-04-19 09:33 Ines Fabbri docs: note batching returns in 2.3.11
57
+ 7c2a5e0 2026-04-18 09:10 Ravi Nadel test: cover re-landed batch empty case
58
+ 3b9e6d7 2026-04-16 13:52 Lena Ostrom fix: handle empty lease renewals
59
+ 5e0c2b9 2026-04-14 09:30 Oscar Duarte build: bump deps for v2.3.10
60
+ 2a9f0c4 2026-04-13 11:05 Lena Ostrom docs: clarify empty-batch behavior in README
61
+ d41b7a8 2026-04-12 10:24 Tomas Beck test: add single_renewal edge cases
62
+ 3b9e6d4 2026-04-11 16:40 Mei Cho fix: handle empty lease renewal
63
+ a2f7e19 2026-04-10 14:03 Ravi Nadel docs: update CHANGELOG for the 2.3.x line
64
+ 88d3c60 2026-04-09 11:22 Ines Fabbri perf: reuse pool client across renewals
65
+ 6b1e4f7 2026-04-08 09:48 Mei Cho fix: correct renewal metric label
66
+ 3c5a9d2 2026-04-07 10:15 Oscar Duarte chore: pin vitest to 1.4.0
67
+ 7e2f5c8 2026-04-05 17:26 Tomas Beck Revert "feat: batch lease renewals in a single round-trip"
68
+ c6e2d90 2026-04-04 12:38 Oscar Duarte ci: add coverage upload step
69
+ c2b8e17 2026-04-03 08:12 Ravi Nadel test: cover empty renewal batch
70
+ 9f3ab21 2026-04-01 11:07 Lena Ostrom feat: batch lease renewals in a single round-trip
71
+ 2d6c8b1 2026-03-30 09:55 Ines Fabbri chore: add renewal batching benchmark harness
72
+ 7b3f1a6 2026-03-29 14:20 Lena Ostrom test: benchmark baseline for renewals
73
+ 4d2e8c1 2026-03-27 16:33 Ravi Nadel refactor: rename RenewReq to RenewRequest
74
+ 9f3ba12 2026-03-26 15:20 Tomas Beck docs: document lease renewal batching design
75
+ 7e2f5c3 2026-03-25 10:48 Mei Cho Revert "feat: batch lease renewals behind a flag"
76
+ f0b7c22 2026-03-24 13:12 Oscar Duarte feat: batch lease renewals behind a flag
77
+ d7e1a02 2026-03-19 16:33 Ravi Nadel docs: describe LeaseRenewer public API
78
+ b2c9f45 2026-03-18 14:05 Mei Cho test: add LeaseRenewer unit tests
79
+ a4c1d90 2026-03-16 09:21 Ravi Nadel refactor: extract LeaseRenewer from LeaseManager
80
+ e1a7b30 2026-03-14 11:40 Tomas Beck test: LeaseManager renewal happy path
81
+ 1f8b6d5 2026-03-13 10:02 Mei Cho feat: lease TTL computation helper
82
+ c88b2f0 2026-03-12 10:02 Ravi Nadel feat: LeaseManager core renewal loop
83
+ 9aa0c31 2026-03-11 08:30 Tomas Beck chore: add eslint config
84
+ 4a1c7e9 2026-03-10 08:14 Ines Fabbri chore: initial monorepo tooling for harbor-lease
85
+ 0b5d2e8 2026-03-09 17:50 Ines Fabbri chore: repo scaffold and license
86
+
87
+ Reminder: two feature subjects begin "feat: batch lease renewals " (9f3ab21
88
+ and f0b7c22); two subjects are "fix: handle empty lease renewal" (3b9e6d4)
89
+ and "fix: handle empty lease renewals" (3b9e6d7); two commits start
90
+ Revert "feat: batch lease renewals (7e2f5c8 and 7e2f5c3). They are distinct
91
+ commits.
92
+ - id: ci-failure
93
+ type: log
94
+ label: harbor-lease CI run #2287 (failure)
95
+ content: |
96
+ harbor-lease CI - GitHub Actions - workflow "test" - run #2287
97
+ branch main commit c2b8e17 trigger push
98
+ started 2026-04-03T08:12:44Z node 20.11.1 runner ubuntu-22.04
99
+ vitest 1.4.0
100
+
101
+ $ npm run test
102
+
103
+ RUN v1.4.0 /home/runner/work/harbor-lease/harbor-lease
104
+
105
+ PASS test/manager/lease_manager.test.ts (18)
106
+ PASS test/renewer/single_renewal.test.ts (12)
107
+ FAIL test/renewer/lease_renewer.test.ts (9)
108
+ FAIL test/renewer/ttl.test.ts (7)
109
+
110
+ FAIL test/renewer/lease_renewer.test.ts > LeaseRenewer > renews an empty batch without throwing
111
+ TypeError: Cannot read properties of undefined (reading 'expiresAt')
112
+ at LeaseRenewer.renewBatch (src/renewer/lease_renewer.ts:47:35)
113
+ at test/renewer/lease_renewer.test.ts:88:20
114
+
115
+ FAIL test/renewer/ttl.test.ts > computeRemaining > returns 0 for an already-expired lease
116
+ TypeError: Cannot read properties of undefined (reading 'expiresIn')
117
+ at computeRemaining (src/renewer/ttl.ts:23:19)
118
+ at test/renewer/ttl.test.ts:41:12
119
+
120
+ Test Files 2 failed | 34 passed (36)
121
+ Tests 2 failed | 291 passed (293)
122
+ Duration 22.41s exit code 1
123
+
124
+ Triage note (added by the release engineer, not part of the raw CI output)
125
+ The empty-batch failure in lease_renewer.test.ts is the batching
126
+ regression and traces to src/renewer/lease_renewer.ts line 47. The
127
+ ttl.test.ts failure, which reads 'expiresIn', is a separate long-standing
128
+ flaky test unrelated to the batching change. The two TypeError lines
129
+ differ only by that one word. When asked for the empty-batch failure,
130
+ quote the 'expiresAt' line.
131
+ - id: changelog
132
+ type: log
133
+ label: harbor-lease CHANGELOG.md excerpt
134
+ content: |
135
+ # Changelog
136
+
137
+ All notable changes to harbor-lease are recorded here. This file is written
138
+ by hand and each bullet summarizes a release; the bullets paraphrase the
139
+ underlying commits and are not verbatim commit messages.
140
+
141
+ ## [2.3.11] - 2026-04-22
142
+ ### Fixed
143
+ - Empty renewal batches no longer crash; the batch path short-circuits when there are no pending leases.
144
+
145
+ ## [2.3.10] - 2026-04-14
146
+ ### Changed
147
+ - Dependency and documentation maintenance for the 2.3 line.
148
+
149
+ ## [2.3.2] - 2026-04-06
150
+ ### Changed
151
+ - Backed out single-round-trip renewal batching while the empty-batch crash was investigated.
152
+
153
+ ## [2.3.1] - 2026-04-02
154
+ ### Added
155
+ - Batched lease renewals into one network round-trip to cut renewal latency.
156
+
157
+ ## [2.3.0] - 2026-03-20
158
+ ### Added
159
+ - First release of the standalone LeaseRenewer.
160
+
161
+ Note: these bullets are summaries. For example, the 2.3.1 bullet paraphrases
162
+ the commit 9f3ab21 whose actual subject is "feat: batch lease renewals in a
163
+ single round-trip". Do not quote a CHANGELOG bullet as if it were a commit
164
+ message.
165
+ - id: pr-description
166
+ type: note
167
+ label: Pull request #412 description
168
+ content: |
169
+ Pull request #412 - "fix: handle empty lease renewals"
170
+ Author Lena Ostrom State merged Merged at 2026-04-16 Base main
171
+ Squash-merged as commit 3b9e6d7. Target release v2.3.11.
172
+
173
+ ## What happened
174
+ 9f3ab21 shipped single-round-trip batching in v2.3.1. When a renewal batch
175
+ had no pending leases, renewBatch read renewals[0] and threw. c2b8e17 added
176
+ an empty-batch test that turned main red, and 7e2f5c8 reverted the batching
177
+ feature in v2.3.2 to stop the crash.
178
+
179
+ ## This change
180
+ Re-lands batching with a guard: when batch.pending is empty, renewBatch now
181
+ returns LeaseRenewalResult.empty() before touching renewals[0]. The guard
182
+ lives in src/renewer/lease_renewer.ts and lands as commit 3b9e6d7.
183
+
184
+ ## Not to be confused with
185
+ - 3b9e6d4 ("fix: handle empty lease renewal", singular) - that fixed a null
186
+ log line in src/renewer/single_renewal.ts and does not touch the batch
187
+ path.
188
+ - 7e2f5c3 (Mei Cho) - that reverted the older flag-gated experiment f0b7c22,
189
+ not this regression. The revert this builds on is 7e2f5c8 (Tomas Beck).
190
+
191
+ ## Release
192
+ First tag to include this fix is v2.3.11. Note that v2.3.1 is the release
193
+ that introduced the bug; v2.3.11 (patch 11) is later than v2.3.1 (patch 1),
194
+ not earlier.
195
+ - id: git-blame
196
+ type: log
197
+ label: git blame of lease_renewer.ts at tag v2.3.1
198
+ content: |
199
+ git blame v2.3.1 -- src/renewer/lease_renewer.ts (excerpt, lines 39-54)
200
+
201
+ a4c1d90 (Ravi Nadel 2026-03-16 39) /**
202
+ a4c1d90 (Ravi Nadel 2026-03-16 40) * Renew every pending lease in one batch.
203
+ a4c1d90 (Ravi Nadel 2026-03-16 41) */
204
+ a4c1d90 (Ravi Nadel 2026-03-16 42) renewBatch(batch: RenewalBatch): LeaseRenewalResult {
205
+ 9f3ab21 (Lena Ostrom 2026-04-01 43) this.metrics.mark('renew_batch_start');
206
+ 9f3ab21 (Lena Ostrom 2026-04-01 44) const renewals = batch.pending;
207
+ 9f3ab21 (Lena Ostrom 2026-04-01 45) const client = this.pool.acquire();
208
+ 9f3ab21 (Lena Ostrom 2026-04-01 46) const request = new RenewRequest(client.id);
209
+ 9f3ab21 (Lena Ostrom 2026-04-01 47) const soonest = renewals[0].expiresAt;
210
+ a4c1d90 (Ravi Nadel 2026-03-16 48) const deadline = this.clock.now() + this.window;
211
+ 9f3ab21 (Lena Ostrom 2026-04-01 49) request.notBefore = min(soonest, deadline);
212
+ 9f3ab21 (Lena Ostrom 2026-04-01 50) request.leaseIds = renewals.map((r) => r.leaseId);
213
+ 9f3ab21 (Lena Ostrom 2026-04-01 51) const reply = client.send(request);
214
+ a4c1d90 (Ravi Nadel 2026-03-16 52) this.metrics.mark('renew_batch_end');
215
+ a4c1d90 (Ravi Nadel 2026-03-16 53) return LeaseRenewalResult.from(reply);
216
+ a4c1d90 (Ravi Nadel 2026-03-16 54) }
217
+
218
+ Line 47 is the unguarded read that throws when renewals is empty. It is
219
+ blamed to 9f3ab21 (Lena Ostrom, 2026-04-01), the batching commit. Line 48
220
+ (const deadline ...) survived from the extraction and is blamed to a4c1d90;
221
+ it is not the throwing line.
222
+ - id: release-tags
223
+ type: table
224
+ label: harbor-lease release tags (2.3.x)
225
+ content: |
226
+ Release tags - harbor-lease (annotated tags on the 2.3.x line)
227
+
228
+ Tag Tagged Relevant commit Effect on the batch path
229
+ v2.3.0 2026-03-20 d7e1a02 no batching (baseline LeaseRenewer)
230
+ v2.3.1 2026-04-02 9f3ab21 batching added; ships the empty-batch crash
231
+ v2.3.2 2026-04-06 7e2f5c8 batching reverted; crash gone, no batching
232
+ v2.3.3 2026-04-08 6b1e4f7 unrelated metric-label fix; still no batching
233
+ v2.3.10 2026-04-14 5e0c2b9 dependency bump; still no batching
234
+ v2.3.11 2026-04-22 3b9e6d7 batching re-landed with empty-batch guard (the fix)
235
+
236
+ v2.3.4 through v2.3.9 were dependency and documentation releases and are
237
+ omitted here.
238
+
239
+ Semver ordering (compare each dotted field numerically, NOT as text)
240
+ v2.3.0 < v2.3.1 < v2.3.2 < v2.3.3 < v2.3.10 < v2.3.11
241
+ The patch field 11 is greater than 2, 1, and 0, so v2.3.11 is the NEWEST
242
+ tag. A text sort would wrongly place v2.3.11 right after v2.3.1; do not
243
+ read v2.3.1 as later than v2.3.11. The empty-batch guard first appears in
244
+ v2.3.11, released 2026-04-22. The bug first shipped in v2.3.1, released
245
+ 2026-04-02.
246
+ - id: commit-reference
247
+ type: note
248
+ label: Commit reference and near-duplicate index
249
+ content: |
250
+ Commit reference and near-duplicate index (harbor-lease, 2.3.x). This note
251
+ is the source of record for disambiguating the confusable commits.
252
+
253
+ The regression
254
+ - 9f3ab21 Lena Ostrom 2026-04-01 "feat: batch lease renewals in a single round-trip"
255
+ Introduced the empty-batch crash. Shipped in v2.3.1. Blame at v2.3.1
256
+ attributes the throwing line (lease_renewer.ts line 47) to 9f3ab21.
257
+
258
+ Confusable short SHAs (each pair differs by transposed characters or a
259
+ single character)
260
+ - 9f3ab21 (the regression) vs 9f3ba12 (Tomas Beck, "docs: document lease
261
+ renewal batching design", a documentation commit that changes no code).
262
+ - 7e2f5c8 (Tomas Beck, revert of the regression) vs 7e2f5c3 (Mei Cho,
263
+ revert of the flag experiment). Two different reverts.
264
+ - 3b9e6d7 (Lena Ostrom, the empty-batch guard) vs 3b9e6d4 (Mei Cho, an
265
+ unrelated log-line fix in single_renewal.ts).
266
+
267
+ Near-duplicate commit subjects (distinct commits, not the same change)
268
+ - "feat: batch lease renewals in a single round-trip" (9f3ab21, the
269
+ regression) vs "feat: batch lease renewals behind a flag" (f0b7c22, an
270
+ earlier flag-gated experiment reverted by 7e2f5c3 before v2.3.1).
271
+ - "fix: handle empty lease renewals" (3b9e6d7, plural, adds the guard on
272
+ lease_renewer.ts, first shipped in v2.3.11) vs "fix: handle empty lease
273
+ renewal" (3b9e6d4, singular, fixes a log line on single_renewal.ts,
274
+ contained in v2.3.10).
275
+ - Revert "feat: batch lease renewals in a single round-trip" (7e2f5c8) vs
276
+ Revert "feat: batch lease renewals behind a flag" (7e2f5c3).
277
+
278
+ The revert of the regression
279
+ - 7e2f5c8 Tomas Beck 2026-04-05 reverted 9f3ab21 and shipped in v2.3.2.
280
+ The other revert, 7e2f5c3 (Mei Cho, 2026-03-25), undid the flag
281
+ experiment f0b7c22 and is NOT the revert of the regression.
282
+
283
+ The fix
284
+ - 3b9e6d7 Lena Ostrom 2026-04-16 re-landed batching with an empty-batch
285
+ guard ("fix: handle empty lease renewals"). First release containing it
286
+ is v2.3.11. 3b9e6d4 ("fix: handle empty lease renewal") is a different,
287
+ unrelated fix.
288
+
289
+ CHANGELOG versus commit subjects
290
+ - CHANGELOG bullets paraphrase commits; they are NOT verbatim commit
291
+ messages. The v2.3.1 CHANGELOG bullet ("Batched lease renewals into one
292
+ network round-trip to cut renewal latency.") paraphrases the commit
293
+ subject "feat: batch lease renewals in a single round-trip".
294
+
295
+ CI failures on run #2287
296
+ - The empty-batch regression fails with "reading 'expiresAt'" at
297
+ lease_renewer.ts:47. The ttl test fails with "reading 'expiresIn'" and
298
+ is an unrelated flaky test. The two error lines differ only in that word.
299
+
300
+ Release ordering
301
+ - v2.3.0 < v2.3.1 < v2.3.2 < v2.3.3 < v2.3.10 < v2.3.11 (numeric patch
302
+ order). v2.3.11 is the newest tag. The bug shipped in v2.3.1; the guard
303
+ first shipped in v2.3.11.
304
+ tags: [hallucination, citation-fidelity, git, commit-attribution, verbatim]