@abloatai/transaction 0.50.0 → 0.52.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 (170) hide show
  1. package/dist/ablo.d.ts +1 -1
  2. package/dist/ablo.js +1 -1
  3. package/dist/ai-sdk/updateTool.d.ts +1 -1
  4. package/dist/ai-sdk/updateTool.js +1 -1
  5. package/dist/auth/capability.d.ts +2 -2
  6. package/dist/auth/capability.js +1 -1
  7. package/dist/auth/index.d.ts +9 -3
  8. package/dist/auth/index.d.ts.map +1 -1
  9. package/dist/auth/index.js +25 -20
  10. package/dist/auth/index.js.map +1 -1
  11. package/dist/auth/schemas.d.ts +24 -1
  12. package/dist/auth/schemas.d.ts.map +1 -1
  13. package/dist/auth/schemas.js +8 -2
  14. package/dist/auth/schemas.js.map +1 -1
  15. package/dist/auth/sessionIssuanceProtocol.d.ts +41 -0
  16. package/dist/auth/sessionIssuanceProtocol.d.ts.map +1 -0
  17. package/dist/auth/sessionIssuanceProtocol.js +48 -0
  18. package/dist/auth/sessionIssuanceProtocol.js.map +1 -0
  19. package/dist/auth/sessionMint.d.ts.map +1 -1
  20. package/dist/auth/sessionMint.js +3 -2
  21. package/dist/auth/sessionMint.js.map +1 -1
  22. package/dist/branches.d.ts +4 -4
  23. package/dist/coordination/schema.d.ts +3 -2
  24. package/dist/coordination/schema.d.ts.map +1 -1
  25. package/dist/coordination/schema.js +7 -2
  26. package/dist/coordination/schema.js.map +1 -1
  27. package/dist/errorCodes.d.ts +3 -2
  28. package/dist/errorCodes.d.ts.map +1 -1
  29. package/dist/errorCodes.js +4 -3
  30. package/dist/errorCodes.js.map +1 -1
  31. package/dist/errors.d.ts +1 -1
  32. package/dist/errors.js +1 -1
  33. package/dist/resources/httpResources.d.ts +11 -2
  34. package/dist/resources/httpResources.d.ts.map +1 -1
  35. package/dist/resources/modelOperations.d.ts +6 -6
  36. package/dist/schema/ddl.d.ts.map +1 -1
  37. package/dist/schema/ddl.js +9 -12
  38. package/dist/schema/ddl.js.map +1 -1
  39. package/dist/schema/field.d.ts +3 -3
  40. package/dist/schema/field.js +2 -2
  41. package/dist/schema/fieldRef.d.ts +1 -1
  42. package/dist/schema/generate.d.ts +2 -3
  43. package/dist/schema/generate.d.ts.map +1 -1
  44. package/dist/schema/generate.js +2 -8
  45. package/dist/schema/generate.js.map +1 -1
  46. package/dist/schema/index.d.ts +2 -2
  47. package/dist/schema/index.js +3 -3
  48. package/dist/schema/model.d.ts +7 -7
  49. package/dist/schema/model.js +2 -2
  50. package/dist/schema/openapi.d.ts.map +1 -1
  51. package/dist/schema/openapi.js +4 -7
  52. package/dist/schema/openapi.js.map +1 -1
  53. package/dist/schema/queries.d.ts +1 -1
  54. package/dist/schema/queries.js +1 -1
  55. package/dist/schema/relation.d.ts +8 -8
  56. package/dist/schema/relation.js +5 -5
  57. package/dist/schema/schema.d.ts +24 -37
  58. package/dist/schema/schema.d.ts.map +1 -1
  59. package/dist/schema/schema.js +15 -28
  60. package/dist/schema/schema.js.map +1 -1
  61. package/dist/schema/sugar.d.ts +7 -7
  62. package/dist/schema/sugar.js +5 -5
  63. package/dist/server/commit.d.ts +5 -1
  64. package/dist/server/commit.d.ts.map +1 -1
  65. package/dist/source/adapters/drizzle.d.ts.map +1 -1
  66. package/dist/source/adapters/drizzle.js +5 -0
  67. package/dist/source/adapters/drizzle.js.map +1 -1
  68. package/dist/source/adapters/kysely.d.ts.map +1 -1
  69. package/dist/source/adapters/kysely.js +33 -8
  70. package/dist/source/adapters/kysely.js.map +1 -1
  71. package/dist/source/adapters/kyselyMutationCore.d.ts +1 -1
  72. package/dist/source/adapters/kyselyMutationCore.d.ts.map +1 -1
  73. package/dist/source/adapters/kyselyMutationCore.js +58 -20
  74. package/dist/source/adapters/kyselyMutationCore.js.map +1 -1
  75. package/dist/source/adapters/memory.d.ts.map +1 -1
  76. package/dist/source/adapters/memory.js +5 -0
  77. package/dist/source/adapters/memory.js.map +1 -1
  78. package/dist/source/adapters/prisma.d.ts.map +1 -1
  79. package/dist/source/adapters/prisma.js +6 -1
  80. package/dist/source/adapters/prisma.js.map +1 -1
  81. package/dist/source/conformance.js +21 -21
  82. package/dist/source/contract.d.ts +17 -0
  83. package/dist/source/contract.d.ts.map +1 -1
  84. package/dist/source/contract.js +31 -0
  85. package/dist/source/contract.js.map +1 -1
  86. package/dist/source/index.d.ts +1 -1
  87. package/dist/source/index.d.ts.map +1 -1
  88. package/dist/source/index.js +1 -1
  89. package/dist/source/index.js.map +1 -1
  90. package/dist/source/types.d.ts +1 -0
  91. package/dist/source/types.d.ts.map +1 -1
  92. package/dist/source/types.js.map +1 -1
  93. package/dist/transactionLayer.d.ts +3 -3
  94. package/dist/transactionLayer.js +3 -3
  95. package/dist/transport/httpClient.d.ts +6 -2
  96. package/dist/transport/httpClient.d.ts.map +1 -1
  97. package/dist/transport/httpClient.js.map +1 -1
  98. package/dist/transport/httpTransport.d.ts +1 -0
  99. package/dist/transport/httpTransport.d.ts.map +1 -1
  100. package/dist/transport/httpTransport.js +50 -4
  101. package/dist/transport/httpTransport.js.map +1 -1
  102. package/dist/transport/wsFrameHandlers.d.ts.map +1 -1
  103. package/dist/transport/wsFrameHandlers.js +3 -0
  104. package/dist/transport/wsFrameHandlers.js.map +1 -1
  105. package/dist/types/streams.d.ts +2 -2
  106. package/dist/wire/accountResponses.js +2 -2
  107. package/dist/wire/accountResponses.js.map +1 -1
  108. package/dist/wire/auth.d.ts.map +1 -1
  109. package/dist/wire/auth.js +4 -3
  110. package/dist/wire/auth.js.map +1 -1
  111. package/dist/wire/commit.d.ts +144 -0
  112. package/dist/wire/commit.d.ts.map +1 -1
  113. package/dist/wire/commit.js +22 -1
  114. package/dist/wire/commit.js.map +1 -1
  115. package/dist/wire/frames.d.ts +4 -0
  116. package/dist/wire/frames.d.ts.map +1 -1
  117. package/dist/wire/inboundFrames.d.ts +22 -0
  118. package/dist/wire/inboundFrames.d.ts.map +1 -1
  119. package/dist/wire/index.d.ts +2 -2
  120. package/dist/wire/index.d.ts.map +1 -1
  121. package/dist/wire/index.js +1 -1
  122. package/dist/wire/index.js.map +1 -1
  123. package/dist/wire/protocol.d.ts +1 -1
  124. package/dist/wire/protocol.js +1 -1
  125. package/package.json +1 -1
  126. package/src/ablo.ts +1 -1
  127. package/src/ai-sdk/updateTool.ts +1 -1
  128. package/src/auth/capability.ts +3 -3
  129. package/src/auth/index.ts +54 -22
  130. package/src/auth/schemas.ts +16 -2
  131. package/src/auth/sessionIssuanceProtocol.ts +57 -0
  132. package/src/auth/sessionMint.ts +3 -2
  133. package/src/coordination/schema.ts +7 -2
  134. package/src/errorCodes.ts +10 -4
  135. package/src/errors.ts +1 -1
  136. package/src/resources/httpResources.ts +11 -2
  137. package/src/resources/modelOperations.ts +6 -6
  138. package/src/schema/ddl.ts +9 -13
  139. package/src/schema/field.ts +3 -3
  140. package/src/schema/fieldRef.ts +1 -1
  141. package/src/schema/generate.ts +2 -8
  142. package/src/schema/index.ts +3 -3
  143. package/src/schema/model.ts +7 -7
  144. package/src/schema/openapi.ts +4 -7
  145. package/src/schema/queries.ts +1 -1
  146. package/src/schema/relation.ts +8 -8
  147. package/src/schema/residency.ts +0 -1
  148. package/src/schema/schema.ts +33 -58
  149. package/src/schema/sugar.ts +7 -7
  150. package/src/server/commit.ts +5 -1
  151. package/src/source/adapters/drizzle.ts +5 -0
  152. package/src/source/adapters/kysely.ts +48 -10
  153. package/src/source/adapters/kyselyMutationCore.ts +69 -22
  154. package/src/source/adapters/memory.ts +5 -0
  155. package/src/source/adapters/prisma.ts +6 -1
  156. package/src/source/conformance.ts +21 -21
  157. package/src/source/contract.ts +34 -0
  158. package/src/source/index.ts +2 -0
  159. package/src/source/types.ts +1 -0
  160. package/src/transactionLayer.ts +3 -3
  161. package/src/transport/httpClient.ts +6 -2
  162. package/src/transport/httpTransport.ts +59 -4
  163. package/src/transport/wsFrameHandlers.ts +3 -0
  164. package/src/types/global.ts +1 -1
  165. package/src/types/streams.ts +2 -2
  166. package/src/wire/accountResponses.ts +2 -2
  167. package/src/wire/auth.ts +4 -3
  168. package/src/wire/commit.ts +31 -1
  169. package/src/wire/index.ts +3 -0
  170. package/src/wire/protocol.ts +1 -1
@@ -203,6 +203,7 @@ const DEFAULT_CLAIM_TTL_MS = 60_000;
203
203
  export interface HttpTransport {
204
204
  ready(): Promise<void>;
205
205
  waitForFlush(): Promise<void>;
206
+ /** Drains scheduled commits and active requests. */
206
207
  dispose(): Promise<void>;
207
208
  purge(): Promise<void>;
208
209
  readonly commits: CommitResource;
@@ -361,6 +362,26 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
361
362
  let readyPromise: Promise<void> | null = null;
362
363
  let effectiveAuthority: EffectiveAuthority | null = null;
363
364
  let httpCommitLane: Promise<void> = Promise.resolve();
365
+ let activeRequests = 0;
366
+ let requestIdleWaiters: Array<() => void> = [];
367
+ let disposePromise: Promise<void> | null = null;
368
+
369
+ function requestStarted(): void {
370
+ activeRequests += 1;
371
+ }
372
+
373
+ function requestFinished(): void {
374
+ activeRequests -= 1;
375
+ if (activeRequests !== 0) return;
376
+ const waiters = requestIdleWaiters;
377
+ requestIdleWaiters = [];
378
+ for (const resolve of waiters) resolve();
379
+ }
380
+
381
+ function waitForRequestIdle(): Promise<void> {
382
+ if (activeRequests === 0) return Promise.resolve();
383
+ return new Promise((resolve) => requestIdleWaiters.push(resolve));
384
+ }
364
385
 
365
386
  function runInHttpCommitLane<T>(work: () => Promise<T>): Promise<T> {
366
387
  const result = httpCommitLane.then(work);
@@ -475,7 +496,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
475
496
  * the commit paths do with their receipt schema. Everywhere else, go through
476
497
  * {@link requestJson}, which will not let a response past unvalidated.
477
498
  */
478
- async function requestRaw(
499
+ async function performRequest(
479
500
  path: string,
480
501
  init: RequestInit & {
481
502
  readonly idempotencyKey?: string | null;
@@ -557,6 +578,22 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
557
578
  return body;
558
579
  }
559
580
 
581
+ async function requestRaw(
582
+ path: string,
583
+ init: RequestInit & {
584
+ readonly idempotencyKey?: string | null;
585
+ readonly sealedProtocolVersion?: number;
586
+ },
587
+ skipReady = false
588
+ ): Promise<unknown> {
589
+ requestStarted();
590
+ try {
591
+ return await performRequest(path, init, skipReady);
592
+ } finally {
593
+ requestFinished();
594
+ }
595
+ }
596
+
560
597
  /**
561
598
  * Issues one request and validates its body against the route's schema.
562
599
  *
@@ -729,6 +766,11 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
729
766
  initial: CommitResponse
730
767
  ): Promise<CommitResponse> {
731
768
  let current = initial;
769
+ const operationResults = initial.operationResults;
770
+ const withOperationResults = (receipt: CommitResponse): CommitResponse =>
771
+ operationResults?.length
772
+ ? { ...receipt, operationResults }
773
+ : receipt;
732
774
  const correlationId = initial.correlationId;
733
775
  const deadlineAt = requestTimeoutMs > 0 ? Date.now() + requestTimeoutMs : null;
734
776
 
@@ -780,7 +822,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
780
822
  if (confirmationDeadline) clearTimeout(confirmationDeadline);
781
823
  }
782
824
 
783
- if (current.status === 'confirmed') return current;
825
+ if (current.status === 'confirmed') return withOperationResults(current);
784
826
  const delayMs =
785
827
  deadlineAt === null
786
828
  ? HTTP_CONFIRMATION_POLL_INTERVAL_MS
@@ -791,7 +833,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
791
833
  });
792
834
  }
793
835
  }
794
- return current;
836
+ return withOperationResults(current);
795
837
  }
796
838
 
797
839
  async function replayHttpCommitOutbox(): Promise<Map<string, ReplayedHttpCommit>> {
@@ -1111,6 +1153,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
1111
1153
  model: op.model,
1112
1154
  id: op.id ?? null,
1113
1155
  data: op.data ?? null,
1156
+ where: op.where ?? null,
1114
1157
  transactionId: op.transactionId ?? null,
1115
1158
  claimId:
1116
1159
  op.claimId ?? claimIdFor(claim?.target, claim?.id, op.model, op.id ?? null),
@@ -1392,6 +1435,9 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
1392
1435
  ? { notifications: body.notifications }
1393
1436
  : {}),
1394
1437
  ...(body.missingIds && body.missingIds.length > 0 ? { missingIds: body.missingIds } : {}),
1438
+ ...(body.operationResults && body.operationResults.length > 0
1439
+ ? { operationResults: body.operationResults }
1440
+ : {}),
1395
1441
  };
1396
1442
  },
1397
1443
  get({ id }) {
@@ -2053,7 +2099,16 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
2053
2099
  const replayed = await replayHttpCommitOutbox();
2054
2100
  await confirmReplayedHttpCommits(replayed);
2055
2101
  }),
2056
- async dispose() {},
2102
+ dispose() {
2103
+ if (!disposePromise) {
2104
+ const scheduledCommits = httpCommitLane;
2105
+ disposePromise = (async () => {
2106
+ await scheduledCommits;
2107
+ await waitForRequestIdle();
2108
+ })();
2109
+ }
2110
+ return disposePromise;
2111
+ },
2057
2112
  async purge() {},
2058
2113
  commits,
2059
2114
  claims,
@@ -222,6 +222,9 @@ const handleMutationResult: WsFrameHandler = (session, message) => {
222
222
  ...(receipt.missingIds && receipt.missingIds.length > 0
223
223
  ? { missingIds: receipt.missingIds }
224
224
  : {}),
225
+ ...(receipt.operationResults && receipt.operationResults.length > 0
226
+ ? { operationResults: receipt.operationResults }
227
+ : {}),
225
228
  }),
226
229
  );
227
230
  } else {
@@ -51,7 +51,7 @@ export interface DefaultSyncShape {
51
51
  // `models` is a `SchemaRecord` (not `Record<string, unknown>`) so that
52
52
  // `ResolveSchema['models']` still satisfies the `R extends SchemaRecord`
53
53
  // bound on the SDK hooks when no `Register` is present — e.g. a shared
54
- // package (`@ablo/documents`, `@ablo/teams`) typechecked standalone, with no
54
+ // package (`feature packages`) typechecked standalone, with no
55
55
  // app registration in scope. Without this the fallback wouldn't type-check
56
56
  // against `useAblo<R>()`/`AbloProvider<R>` and every such package would need
57
57
  // its own ambient registration.
@@ -629,12 +629,12 @@ export interface Claim<T = Record<string, unknown>, M = ResolveClaimMeta> {
629
629
  revoke?: () => void;
630
630
  /**
631
631
  * Extend the lease past the liveness window — the "still working" signal
632
- * for long-running tasks. Resolving means the lease is still yours, now
632
+ * for long-running items. Resolving means the lease is still yours, now
633
633
  * good until the returned `expiresAt`; a lease that lapsed (and may have
634
634
  * been granted to the next in line) rejects with an
635
635
  * {@link ../errors.js AbloClaimedError} carrying code `claim_lost`, so the
636
636
  * failed beat doubles as the loss notification. Each beat's extension is
637
- * clamped server-side — hold a long task by beating on a cadence (pass
637
+ * clamped server-side — hold a long item by beating on a cadence (pass
638
638
  * `heartbeat` when claiming to have the SDK do this for you), not by
639
639
  * asking once for a huge window. Present only on a claim you hold.
640
640
  */
@@ -101,7 +101,7 @@ export const conflictAxisWireSchema = z.object({
101
101
 
102
102
  /** One model in the deployed schema, as the schema read reports it. */
103
103
  export const schemaModelResponseSchema = z.object({
104
- /** The key local code addresses (`ablo.documents`). */
104
+ /** The key local code addresses (`ablo.records`). */
105
105
  key: z.string(),
106
106
  /** The wire typename the engine routes and gates on. */
107
107
  typename: z.string(),
@@ -122,7 +122,7 @@ export const schemaModelResponseSchema = z.object({
122
122
  *
123
123
  * The schema artifact has always carried these and this response dropped
124
124
  * them, so a caller with no local schema declaration — which is every caller
125
- * that is not TypeScript — had no way to learn that `task.title` is a
125
+ * that is not TypeScript — had no way to learn that `item.title` is a
126
126
  * required string. It found out from a 400 at the end of a round trip.
127
127
  *
128
128
  * For an agent that is not merely slow, it is a reasoning detour: a typo
package/src/wire/auth.ts CHANGED
@@ -40,9 +40,10 @@ export const ephemeralKeyRequestSchema = z.object({
40
40
  */
41
41
  organizationId: z.string().min(1).optional(),
42
42
  /**
43
- * Resolve this session's schema from a shared project rather than from the
44
- * target organization, while its data stays scoped to that organization.
45
- * Both fields are set together, and both require `ephemeral:mint-any-org`.
43
+ * Override the shared project used to resolve this session's schema while
44
+ * its data stays scoped to the target organization. A cross-organization
45
+ * mint defaults both coordinates to the caller key's own project. Both
46
+ * fields are set together, and both require `ephemeral:mint-any-org`.
46
47
  */
47
48
  schemaProjectId: z.string().min(1).optional(),
48
49
  schemaOwnerOrgId: z.string().min(1).optional(),
@@ -102,6 +102,31 @@ export type CommitWait = z.infer<typeof commitWaitSchema>;
102
102
  const missingIdsSchema = z.array(z.string().min(1));
103
103
  const notificationsSchema = z.array(staleNotificationSchema);
104
104
 
105
+ export const commitOperationOutcomeSchema = z.enum([
106
+ 'created',
107
+ 'updated',
108
+ 'deleted',
109
+ 'archived',
110
+ 'unarchived',
111
+ ]);
112
+
113
+ export const commitOperationResultSchema = z.strictObject({
114
+ transactionId: z.string().min(1),
115
+ outcome: commitOperationOutcomeSchema,
116
+ row: z.record(z.string(), z.unknown()),
117
+ });
118
+ export type CommitOperationResult<
119
+ Row extends Record<string, unknown> = Record<string, unknown>,
120
+ > = Omit<z.infer<typeof commitOperationResultSchema>, 'row'> & { readonly row: Row };
121
+
122
+ const operationResultsSchema = z
123
+ .array(commitOperationResultSchema)
124
+ .min(1)
125
+ .refine(
126
+ (results) => new Set(results.map((result) => result.transactionId)).size === results.length,
127
+ 'operation result transactionIds must be unique',
128
+ );
129
+
105
130
  export const commitActorSchema = z.strictObject({
106
131
  kind: participantKindSchema,
107
132
  id: z.string().min(1),
@@ -155,6 +180,8 @@ const successfulReceiptCommonShape = {
155
180
  authority: effectiveAuthoritySchema,
156
181
  notifications: notificationsSchema.optional(),
157
182
  missingIds: missingIdsSchema.optional(),
183
+ /** Exact rows returned by the writing transaction; absent on durable replay. */
184
+ operationResults: operationResultsSchema.optional(),
158
185
  } as const;
159
186
 
160
187
  const queuedCommitReceiptSchema = queuedCommitStatusSchema.safeExtend(
@@ -316,6 +343,7 @@ export type CommitExecutionResult = z.infer<typeof commitExecutionResultSchema>;
316
343
  const ackCommonShape = {
317
344
  notifications: notificationsSchema.optional(),
318
345
  missingIds: missingIdsSchema.optional(),
346
+ operationResults: operationResultsSchema.optional(),
319
347
  } as const;
320
348
 
321
349
  /** Normalized acknowledgement handed from a mutation transport to the queue. */
@@ -350,6 +378,7 @@ export const clientCommitReceiptSchema = z.strictObject({
350
378
  lastSyncId: readSetWatermarkSchema.optional(),
351
379
  notifications: notificationsSchema.optional(),
352
380
  missingIds: missingIdsSchema.optional(),
381
+ operationResults: operationResultsSchema.optional(),
353
382
  });
354
383
  export type ClientCommitReceipt = z.infer<typeof clientCommitReceiptSchema>;
355
384
 
@@ -382,13 +411,14 @@ export const commitOperationBodySchema = z.object({
382
411
  action: z.string(),
383
412
  model: z.string(),
384
413
  data: z.record(z.string(), z.unknown()).nullish(),
414
+ where: z.record(z.string().min(1), z.unknown()).nullish(),
385
415
  });
386
416
  export type CommitOperationBody = z.infer<typeof commitOperationBodySchema>;
387
417
 
388
418
  /** Commit records retain intent metadata but never copy customer mutation data. */
389
419
  export const COMMIT_OPERATION_DATA_RETENTION = 'redacted' as const;
390
420
  export const commitRecordOperationSchema = commitOperationBodySchema
391
- .omit({ data: true })
421
+ .omit({ data: true, where: true })
392
422
  .safeExtend({ data: z.strictObject({ retention: z.literal(COMMIT_OPERATION_DATA_RETENTION) }) });
393
423
  export type CommitRecordOperation = z.infer<typeof commitRecordOperationSchema>;
394
424
 
package/src/wire/index.ts CHANGED
@@ -99,6 +99,8 @@ export {
99
99
  commitStatusSchema,
100
100
  commitWaitSchema,
101
101
  commitReceiptSchema,
102
+ commitOperationOutcomeSchema,
103
+ commitOperationResultSchema,
102
104
  rejectedCommitReceiptSchema,
103
105
  mutationResultPayloadSchema,
104
106
  mutationResultMessageSchema,
@@ -121,6 +123,7 @@ export {
121
123
  } from './commit.js';
122
124
  export type {
123
125
  CommitOperationBody,
126
+ CommitOperationResult,
124
127
  CommitActor,
125
128
  CommitAttempt,
126
129
  CommitClaimReference,
@@ -16,7 +16,7 @@
16
16
  * The lease is three ping intervals long and is renewed on every ping, so a
17
17
  * live holder always has at least two intervals of runway and a silent one
18
18
  * lapses about two missed pings after it stops renewing. The window measures
19
- * liveness, not how long a task may run: widening the lease without also
19
+ * liveness, not how long a item may run: widening the lease without also
20
20
  * widening the ping makes holders flap, and neither value should be redefined
21
21
  * anywhere else.
22
22
  */