@abloatai/transaction 0.47.0 → 0.49.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.
- package/CONVENTIONS.md +7 -7
- package/README.md +1 -1
- package/dist/ai-sdk/modelTools.d.ts +1 -1
- package/dist/ai-sdk/modelTools.js +1 -1
- package/dist/ai-sdk/updateTool.d.ts +2 -2
- package/dist/ai-sdk/updateTool.d.ts.map +1 -1
- package/dist/auth/bootstrapScope.d.ts +1 -1
- package/dist/auth/capability.d.ts +32 -10
- package/dist/auth/capability.d.ts.map +1 -1
- package/dist/auth/capability.js +9 -3
- package/dist/auth/capability.js.map +1 -1
- package/dist/auth/capabilityLifecycle.d.ts +2 -2
- package/dist/auth/capabilityLifecycle.d.ts.map +1 -1
- package/dist/auth/capabilityLifecycle.js +3 -2
- package/dist/auth/capabilityLifecycle.js.map +1 -1
- package/dist/auth/identity.d.ts +3 -0
- package/dist/auth/identity.d.ts.map +1 -1
- package/dist/auth/identity.js +13 -1
- package/dist/auth/identity.js.map +1 -1
- package/dist/auth/index.d.ts +2 -2
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/schemas.d.ts +18 -1
- package/dist/auth/schemas.d.ts.map +1 -1
- package/dist/auth/schemas.js +3 -1
- package/dist/auth/schemas.js.map +1 -1
- package/dist/auth/sessionMint.d.ts.map +1 -1
- package/dist/auth/sessionMint.js +0 -1
- package/dist/auth/sessionMint.js.map +1 -1
- package/dist/branches.d.ts +61 -12
- package/dist/branches.d.ts.map +1 -1
- package/dist/branches.js +25 -3
- package/dist/branches.js.map +1 -1
- package/dist/commitRecordRuntime.d.ts +30 -0
- package/dist/commitRecordRuntime.d.ts.map +1 -0
- package/dist/commitRecordRuntime.js +188 -0
- package/dist/commitRecordRuntime.js.map +1 -0
- package/dist/coordination/index.d.ts +3 -3
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +2 -2
- package/dist/coordination/index.js.map +1 -1
- package/dist/coordination/locator.d.ts +5 -0
- package/dist/coordination/locator.d.ts.map +1 -1
- package/dist/coordination/locator.js +12 -0
- package/dist/coordination/locator.js.map +1 -1
- package/dist/coordination/schema.d.ts +205 -15
- package/dist/coordination/schema.d.ts.map +1 -1
- package/dist/coordination/schema.js +88 -47
- package/dist/coordination/schema.js.map +1 -1
- package/dist/coordination/targetConflict.js +1 -1
- package/dist/durableWrites.d.ts +3 -3
- package/dist/durableWrites.d.ts.map +1 -1
- package/dist/durableWrites.js +1 -1
- package/dist/errorCodes.d.ts +35 -2
- package/dist/errorCodes.d.ts.map +1 -1
- package/dist/errorCodes.js +67 -19
- package/dist/errorCodes.js.map +1 -1
- package/dist/errorObservation.d.ts +82 -0
- package/dist/errorObservation.d.ts.map +1 -0
- package/dist/errorObservation.js +101 -0
- package/dist/errorObservation.js.map +1 -0
- package/dist/errors.d.ts +2 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +6 -1
- package/dist/errors.js.map +1 -1
- package/dist/footprint.d.ts +10 -22
- package/dist/footprint.d.ts.map +1 -1
- package/dist/footprint.js +12 -24
- package/dist/footprint.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/read-set.d.ts +8 -0
- package/dist/internal/read-set.d.ts.map +1 -0
- package/dist/internal/read-set.js +7 -0
- package/dist/internal/read-set.js.map +1 -0
- package/dist/logger.d.ts +1 -1
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +1 -1
- package/dist/readSetContext.d.ts +54 -0
- package/dist/readSetContext.d.ts.map +1 -0
- package/dist/readSetContext.js +98 -0
- package/dist/readSetContext.js.map +1 -0
- package/dist/resources/functionalUpdate.d.ts +13 -3
- package/dist/resources/functionalUpdate.d.ts.map +1 -1
- package/dist/resources/functionalUpdate.js.map +1 -1
- package/dist/resources/httpResources.d.ts +23 -7
- package/dist/resources/httpResources.d.ts.map +1 -1
- package/dist/resources/modelOperations.d.ts +2 -2
- package/dist/resources/modelOperations.js +1 -1
- package/dist/resources/mutationOptions.d.ts +1 -1
- package/dist/resources/mutationOptions.js +1 -1
- package/dist/resources/where.d.ts +1 -1
- package/dist/resources/where.js +1 -1
- package/dist/resources/writeOptionsSchema.d.ts +39 -4
- package/dist/resources/writeOptionsSchema.d.ts.map +1 -1
- package/dist/resources/writeOptionsSchema.js +12 -6
- package/dist/resources/writeOptionsSchema.js.map +1 -1
- package/dist/schema/index.js +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/openapi.d.ts.map +1 -1
- package/dist/schema/openapi.js +45 -11
- package/dist/schema/openapi.js.map +1 -1
- package/dist/server/commit.d.ts +7 -1
- package/dist/server/commit.d.ts.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/source/adapters/kysely.d.ts.map +1 -1
- package/dist/source/adapters/kysely.js +11 -4
- package/dist/source/adapters/kysely.js.map +1 -1
- package/dist/source/adapters/kyselyMutationCore.d.ts.map +1 -1
- package/dist/source/adapters/kyselyMutationCore.js +14 -2
- package/dist/source/adapters/kyselyMutationCore.js.map +1 -1
- package/dist/source/contract.d.ts +2 -0
- package/dist/source/contract.d.ts.map +1 -1
- package/dist/source/index.d.ts +1 -1
- package/dist/source/index.d.ts.map +1 -1
- package/dist/source/types.d.ts +1 -1
- package/dist/testing/fixtures/httpResponses.d.ts +30 -1
- package/dist/testing/fixtures/httpResponses.d.ts.map +1 -1
- package/dist/testing/fixtures/httpResponses.js +48 -1
- package/dist/testing/fixtures/httpResponses.js.map +1 -1
- package/dist/transactionLayer.d.ts +1 -1
- package/dist/transactions/{settlement → confirmation}/commitEnvelope.d.ts +4 -2
- package/dist/transactions/confirmation/commitEnvelope.d.ts.map +1 -0
- package/dist/transactions/{settlement → confirmation}/commitEnvelope.js +8 -3
- package/dist/transactions/confirmation/commitEnvelope.js.map +1 -0
- package/dist/transactions/confirmation/httpCommitEnvelope.d.ts.map +1 -0
- package/dist/transactions/confirmation/httpCommitEnvelope.js.map +1 -0
- package/dist/transactions/confirmation/idempotencyKey.d.ts.map +1 -0
- package/dist/transactions/confirmation/idempotencyKey.js.map +1 -0
- package/dist/transactions/{settlement → confirmation}/pendingWrite.d.ts +3 -2
- package/dist/transactions/confirmation/pendingWrite.d.ts.map +1 -0
- package/dist/transactions/confirmation/pendingWrite.js.map +1 -0
- package/dist/transport/commitFrames.d.ts +1 -0
- package/dist/transport/commitFrames.d.ts.map +1 -1
- package/dist/transport/commitFrames.js +8 -7
- package/dist/transport/commitFrames.js.map +1 -1
- package/dist/transport/connectionManager.d.ts +1 -1
- package/dist/transport/connectionManager.js +1 -1
- package/dist/transport/httpClient.d.ts +23 -9
- package/dist/transport/httpClient.d.ts.map +1 -1
- package/dist/transport/httpClient.js +134 -15
- package/dist/transport/httpClient.js.map +1 -1
- package/dist/transport/httpOptions.d.ts +9 -1
- package/dist/transport/httpOptions.d.ts.map +1 -1
- package/dist/transport/httpTransport.d.ts +3 -0
- package/dist/transport/httpTransport.d.ts.map +1 -1
- package/dist/transport/httpTransport.js +109 -22
- package/dist/transport/httpTransport.js.map +1 -1
- package/dist/transport/wsFrameHandlers.d.ts +3 -1
- package/dist/transport/wsFrameHandlers.d.ts.map +1 -1
- package/dist/transport/wsFrameHandlers.js +38 -17
- package/dist/transport/wsFrameHandlers.js.map +1 -1
- package/dist/transport/wsTransport.d.ts +3 -0
- package/dist/transport/wsTransport.d.ts.map +1 -1
- package/dist/transport/wsTransport.js +32 -1
- package/dist/transport/wsTransport.js.map +1 -1
- package/dist/utils/json.d.ts +1 -1
- package/dist/utils/json.js +1 -1
- package/dist/wire/accountResponses.d.ts +1 -1
- package/dist/wire/auth.d.ts +2 -2
- package/dist/wire/commit.d.ts +1652 -213
- package/dist/wire/commit.d.ts.map +1 -1
- package/dist/wire/commit.js +196 -132
- package/dist/wire/commit.js.map +1 -1
- package/dist/wire/delta.d.ts +1 -1
- package/dist/wire/errorEnvelope.d.ts +1 -0
- package/dist/wire/errorEnvelope.d.ts.map +1 -1
- package/dist/wire/errorEnvelope.js +1 -0
- package/dist/wire/errorEnvelope.js.map +1 -1
- package/dist/wire/feedCursor.d.ts +1 -1
- package/dist/wire/feedCursor.js +1 -1
- package/dist/wire/feedEvent.d.ts +3 -3
- package/dist/wire/frames.d.ts +8 -4
- package/dist/wire/frames.d.ts.map +1 -1
- package/dist/wire/frames.js +6 -3
- package/dist/wire/frames.js.map +1 -1
- package/dist/wire/inboundFrames.d.ts +44 -4
- package/dist/wire/inboundFrames.d.ts.map +1 -1
- package/dist/wire/index.d.ts +6 -4
- package/dist/wire/index.d.ts.map +1 -1
- package/dist/wire/index.js +6 -5
- package/dist/wire/index.js.map +1 -1
- package/dist/wire/modelMutations.d.ts +35 -0
- package/dist/wire/modelMutations.d.ts.map +1 -1
- package/dist/wire/modelMutations.js +9 -2
- package/dist/wire/modelMutations.js.map +1 -1
- package/dist/wire/modelResponses.d.ts +12 -1
- package/dist/wire/modelResponses.d.ts.map +1 -1
- package/dist/wire/modelResponses.js +12 -1
- package/dist/wire/modelResponses.js.map +1 -1
- package/package.json +12 -2
- package/src/ai-sdk/modelTools.ts +1 -1
- package/src/ai-sdk/updateTool.ts +2 -2
- package/src/auth/bootstrapScope.ts +1 -1
- package/src/auth/capability.ts +13 -4
- package/src/auth/capabilityLifecycle.ts +3 -2
- package/src/auth/identity.ts +16 -1
- package/src/auth/index.ts +2 -0
- package/src/auth/schemas.ts +3 -0
- package/src/auth/sessionMint.ts +0 -1
- package/src/branches.ts +27 -3
- package/src/commitRecordRuntime.ts +245 -0
- package/src/coordination/index.ts +24 -1
- package/src/coordination/locator.ts +17 -0
- package/src/coordination/schema.ts +114 -49
- package/src/coordination/targetConflict.ts +1 -1
- package/src/durableWrites.ts +3 -3
- package/src/errorCodes.ts +205 -25
- package/src/errorObservation.ts +121 -0
- package/src/errors.ts +5 -1
- package/src/footprint.ts +16 -27
- package/src/index.ts +8 -1
- package/src/internal/read-set.ts +18 -0
- package/src/logger.ts +1 -1
- package/src/observability.ts +1 -1
- package/src/readSetContext.ts +164 -0
- package/src/resources/functionalUpdate.ts +16 -4
- package/src/resources/httpResources.ts +27 -8
- package/src/resources/modelOperations.ts +2 -2
- package/src/resources/mutationOptions.ts +1 -1
- package/src/resources/where.ts +1 -1
- package/src/resources/writeOptionsSchema.ts +30 -12
- package/src/schema/index.ts +1 -1
- package/src/schema/openapi.ts +52 -11
- package/src/server/commit.ts +7 -1
- package/src/server/index.ts +5 -1
- package/src/source/adapters/kysely.ts +12 -4
- package/src/source/adapters/kyselyMutationCore.ts +14 -2
- package/src/source/index.ts +1 -1
- package/src/source/types.ts +1 -1
- package/src/testing/fixtures/httpResponses.ts +60 -1
- package/src/transactionLayer.ts +1 -1
- package/src/transactions/{settlement → confirmation}/commitEnvelope.ts +13 -3
- package/src/transport/commitFrames.ts +7 -5
- package/src/transport/connectionManager.ts +1 -1
- package/src/transport/httpClient.ts +256 -29
- package/src/transport/httpOptions.ts +9 -1
- package/src/transport/httpTransport.ts +139 -27
- package/src/transport/wsFrameHandlers.ts +46 -8
- package/src/transport/wsTransport.ts +42 -1
- package/src/utils/json.ts +1 -1
- package/src/wire/commit.ts +248 -147
- package/src/wire/errorEnvelope.ts +1 -0
- package/src/wire/feedCursor.ts +1 -1
- package/src/wire/frames.ts +9 -4
- package/src/wire/index.ts +38 -5
- package/src/wire/modelMutations.ts +16 -2
- package/src/wire/modelResponses.ts +14 -1
- package/dist/transactions/settlement/commitEnvelope.d.ts.map +0 -1
- package/dist/transactions/settlement/commitEnvelope.js.map +0 -1
- package/dist/transactions/settlement/httpCommitEnvelope.d.ts.map +0 -1
- package/dist/transactions/settlement/httpCommitEnvelope.js.map +0 -1
- package/dist/transactions/settlement/idempotencyKey.d.ts.map +0 -1
- package/dist/transactions/settlement/idempotencyKey.js.map +0 -1
- package/dist/transactions/settlement/pendingWrite.d.ts.map +0 -1
- package/dist/transactions/settlement/pendingWrite.js.map +0 -1
- /package/dist/transactions/{settlement → confirmation}/httpCommitEnvelope.d.ts +0 -0
- /package/dist/transactions/{settlement → confirmation}/httpCommitEnvelope.js +0 -0
- /package/dist/transactions/{settlement → confirmation}/idempotencyKey.d.ts +0 -0
- /package/dist/transactions/{settlement → confirmation}/idempotencyKey.js +0 -0
- /package/dist/transactions/{settlement → confirmation}/pendingWrite.js +0 -0
- /package/src/transactions/{settlement → confirmation}/httpCommitEnvelope.ts +0 -0
- /package/src/transactions/{settlement → confirmation}/idempotencyKey.ts +0 -0
- /package/src/transactions/{settlement → confirmation}/pendingWrite.ts +0 -0
|
@@ -20,7 +20,15 @@ import { z } from 'zod';
|
|
|
20
20
|
import type { MutationOptions } from '../resources/mutationOptions.js';
|
|
21
21
|
import { AbloValidationError } from '../errors.js';
|
|
22
22
|
import { commitWaitSchema } from '../wire/commit.js';
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
onStaleModeSchema,
|
|
25
|
+
MAX_READ_SET_ENTRIES,
|
|
26
|
+
readDependencyListSchema,
|
|
27
|
+
readSetWatermarkSchema,
|
|
28
|
+
readSetProjectionEntryCount,
|
|
29
|
+
trackDependencyListSchema,
|
|
30
|
+
} from '../coordination/schema.js';
|
|
31
|
+
import type { AssertExact } from '../types/assertExact.js';
|
|
24
32
|
|
|
25
33
|
// Re-exported, not redeclared. `coordination/schema.ts` owns this enum — it is
|
|
26
34
|
// what the wire schemas and the server validate against — while the published
|
|
@@ -38,7 +46,7 @@ export const writeOptionsSchema = z.object({
|
|
|
38
46
|
/** Resolve when queued locally (default) or once the server confirms. */
|
|
39
47
|
wait: commitWaitSchema.optional(),
|
|
40
48
|
/** Stale guard: the sync watermark the caller's reasoning was based on. */
|
|
41
|
-
readAt:
|
|
49
|
+
readAt: readSetWatermarkSchema.nullish(),
|
|
42
50
|
/** What the server does when the target moved past `readAt`. */
|
|
43
51
|
onStale: onStaleModeSchema.nullish(),
|
|
44
52
|
/** The held claim's fencing token (Option B), sourced from the claim handle
|
|
@@ -49,6 +57,15 @@ export const writeOptionsSchema = z.object({
|
|
|
49
57
|
/** The claim this write belongs to — either a claim id, or a live claim
|
|
50
58
|
* handle whose `release`/`revoke` functions are preserved untouched. */
|
|
51
59
|
claim: z.union([z.string(), z.looseObject({ id: z.string() })]).nullish(),
|
|
60
|
+
/** Low-level claim identity carried after a live handle is normalized. */
|
|
61
|
+
claimRef: z.union([z.string(), z.object({ id: z.string() })]).nullish(),
|
|
62
|
+
/** Commit-lifetime entries in the write's ReadSet. */
|
|
63
|
+
reads: readDependencyListSchema.nullish(),
|
|
64
|
+
/** Persisted entries projected from the write's ReadSet. */
|
|
65
|
+
track: trackDependencyListSchema.nullish(),
|
|
66
|
+
}).refine((value) => readSetProjectionEntryCount(value) <= MAX_READ_SET_ENTRIES, {
|
|
67
|
+
path: ['reads'],
|
|
68
|
+
message: `reads and track may contain at most ${MAX_READ_SET_ENTRIES} entries combined`,
|
|
52
69
|
});
|
|
53
70
|
|
|
54
71
|
export type WriteOptionsInput = z.infer<typeof writeOptionsSchema>;
|
|
@@ -78,14 +95,15 @@ export function assertWriteOptions(value: unknown, context?: string): void {
|
|
|
78
95
|
}
|
|
79
96
|
|
|
80
97
|
// ── Drift guard ──────────────────────────────────────────────────────────────
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
type
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
// `claim` is the one high-level handle normalized before MutationOptions. Every
|
|
99
|
+
// other key is the canonical interface itself. Compare keys exactly: mutual
|
|
100
|
+
// assignment between all-optional objects is vacuous and failed to catch three
|
|
101
|
+
// missing members in this schema.
|
|
102
|
+
type WriteOptionsContract = MutationOptions & {
|
|
103
|
+
readonly claim?: string | { readonly id: string } | null;
|
|
104
|
+
};
|
|
105
|
+
const _writeOptionsContractInSync: AssertExact<
|
|
106
|
+
keyof WriteOptionsInput,
|
|
107
|
+
keyof WriteOptionsContract
|
|
108
|
+
> = true;
|
|
91
109
|
void _writeOptionsContractInSync;
|
package/src/schema/index.ts
CHANGED
|
@@ -275,6 +275,6 @@ export {
|
|
|
275
275
|
|
|
276
276
|
// The OpenAPI generator describes the language-agnostic `/v1` surface — the
|
|
277
277
|
// primitive itself, which a Python or Go caller consumes with no TypeScript
|
|
278
|
-
// anywhere — so it lives in the
|
|
278
|
+
// anywhere — so it lives in the confirmation core (ADR 0016). Re-exported here so
|
|
279
279
|
// the `@abloatai/ablo/schema` import path keeps resolving.
|
|
280
280
|
export { abloOpenApi, schemaToOpenApi, type SchemaToOpenApiOptions } from './openapi.js';
|
package/src/schema/openapi.ts
CHANGED
|
@@ -29,7 +29,13 @@ import { ABLO_HOSTED_HTTP_BASE_URL } from '../auth/hostedEndpoints.js';
|
|
|
29
29
|
// one definition site, so the documented surface cannot drift from the
|
|
30
30
|
// enforced one.
|
|
31
31
|
import { z } from 'zod';
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
commitRequestSchema,
|
|
34
|
+
commitReceiptSchema,
|
|
35
|
+
commitRecordSchema,
|
|
36
|
+
commitRecordListSchema,
|
|
37
|
+
commitRecordWhereSchema,
|
|
38
|
+
} from '../wire/commit.js';
|
|
33
39
|
import {
|
|
34
40
|
claimRequestSchema,
|
|
35
41
|
claimHeartbeatRequestSchema,
|
|
@@ -154,7 +160,9 @@ const ABLO_OPERATION_IDS: Readonly<Record<string, string>> = {
|
|
|
154
160
|
'POST /v1/capabilities/{id}/rotate': 'rotateCapability',
|
|
155
161
|
'GET /v1/schema': 'getSchema',
|
|
156
162
|
'GET /v1/logs': 'listLogEntries',
|
|
163
|
+
'GET /v1/commits': 'listCommits',
|
|
157
164
|
'POST /v1/commits': 'commit',
|
|
165
|
+
'GET /v1/commits/{id}': 'getCommit',
|
|
158
166
|
};
|
|
159
167
|
|
|
160
168
|
function fieldSchema(f: FieldMeta): Json {
|
|
@@ -317,6 +325,11 @@ function queryParams(schema: z.ZodType): Json[] {
|
|
|
317
325
|
}));
|
|
318
326
|
}
|
|
319
327
|
|
|
328
|
+
const commitRecordListQuerySchema = commitRecordWhereSchema.safeExtend({
|
|
329
|
+
cursor: z.string().min(1).optional(),
|
|
330
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
331
|
+
});
|
|
332
|
+
|
|
320
333
|
/**
|
|
321
334
|
* Both release routes answer in one shape. `released` distinguishes "this call
|
|
322
335
|
* ended your lease" from "there was nothing of yours to end" — both success,
|
|
@@ -644,7 +657,7 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
|
|
|
644
657
|
'/v1/branches/{id}/status': {
|
|
645
658
|
get: {
|
|
646
659
|
tags: ['branches'],
|
|
647
|
-
summary: 'Diagnose one branch
|
|
660
|
+
summary: 'Diagnose one branch',
|
|
648
661
|
description:
|
|
649
662
|
'Returns branch lifecycle, active schema, compatibility with the parent schema, safe datasource coordinates, and readiness blockers.',
|
|
650
663
|
parameters: [idParam()],
|
|
@@ -699,7 +712,7 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
|
|
|
699
712
|
description:
|
|
700
713
|
'One round trip per cadence for a worker holding many rows, instead of ' +
|
|
701
714
|
'one per row. Takes only `ttl`; the leases are whichever ones your ' +
|
|
702
|
-
'credential holds on this
|
|
715
|
+
'credential holds on this branch.',
|
|
703
716
|
requestBody: optionalJsonBody(derive(claimHeartbeatRequestSchema, 'input')),
|
|
704
717
|
responses: {
|
|
705
718
|
'200': namedResp(
|
|
@@ -736,11 +749,9 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
|
|
|
736
749
|
tags: ['claims'],
|
|
737
750
|
summary: 'Heartbeat a claim by id — held or queued',
|
|
738
751
|
description:
|
|
739
|
-
'
|
|
740
|
-
'`
|
|
741
|
-
'
|
|
742
|
-
'`queued` means still in line, `held` means the grant landed, at which ' +
|
|
743
|
-
'point `GET /v1/claims/{claimId}` carries the fence token.',
|
|
752
|
+
'Keep a held or queued claim active. Branch on the returned status: ' +
|
|
753
|
+
'`queued` is still waiting and `held` has been granted. Retrieve the ' +
|
|
754
|
+
'claim after a grant before writing.',
|
|
744
755
|
parameters: [claimIdParam()],
|
|
745
756
|
requestBody: optionalJsonBody(derive(claimHeartbeatRequestSchema, 'input')),
|
|
746
757
|
responses: {
|
|
@@ -812,7 +823,7 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
|
|
|
812
823
|
tags: ['schema'],
|
|
813
824
|
summary: 'What the models look like',
|
|
814
825
|
description:
|
|
815
|
-
"The schema deployed on your credential's
|
|
826
|
+
"The schema deployed on your credential's branch: every model with its " +
|
|
816
827
|
'fields, their types, and its relations. Read this when you have no ' +
|
|
817
828
|
'local schema declaration to read types from — it is what makes a ' +
|
|
818
829
|
'field typo a local check rather than a rejected write. Each model ' +
|
|
@@ -835,7 +846,7 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
|
|
|
835
846
|
'How a caller without a socket learns what its peers did. Omit ' +
|
|
836
847
|
'`after` for the most recent entries, then copy each page\'s ' +
|
|
837
848
|
'`next_cursor` back as `after` to walk forward. Scope is taken from ' +
|
|
838
|
-
'your key — organization,
|
|
849
|
+
'your key — organization, branch and sync groups — so a caller cannot ' +
|
|
839
850
|
'widen what it sees by asking.',
|
|
840
851
|
parameters: queryParams(logQuerySchema),
|
|
841
852
|
responses: {
|
|
@@ -849,6 +860,12 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
|
|
|
849
860
|
},
|
|
850
861
|
},
|
|
851
862
|
'/v1/commits': {
|
|
863
|
+
get: {
|
|
864
|
+
tags: ['commits'],
|
|
865
|
+
summary: 'List commit records',
|
|
866
|
+
parameters: queryParams(commitRecordListQuerySchema),
|
|
867
|
+
responses: { '200': jsonResp('Tenant-scoped commit records', derive(commitRecordListSchema, 'output')) },
|
|
868
|
+
},
|
|
852
869
|
post: {
|
|
853
870
|
tags: ['commits'],
|
|
854
871
|
summary: 'Commit a batch of operations atomically, and/or register durable premises',
|
|
@@ -857,6 +874,14 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
|
|
|
857
874
|
responses: { '200': commitReceipt() },
|
|
858
875
|
},
|
|
859
876
|
},
|
|
877
|
+
'/v1/commits/{id}': {
|
|
878
|
+
get: {
|
|
879
|
+
tags: ['commits'],
|
|
880
|
+
summary: 'Retrieve a commit record',
|
|
881
|
+
parameters: [idParam()],
|
|
882
|
+
responses: { '200': jsonResp('The commit record, or null when absent', derive(commitRecordSchema.nullable(), 'output')) },
|
|
883
|
+
},
|
|
884
|
+
},
|
|
860
885
|
};
|
|
861
886
|
|
|
862
887
|
applyOperationIds(paths, ABLO_OPERATION_IDS);
|
|
@@ -864,7 +889,7 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
|
|
|
864
889
|
|
|
865
890
|
return envelope(
|
|
866
891
|
options,
|
|
867
|
-
'
|
|
892
|
+
'Ablo collaboration infrastructure: commit, read, and claim. `{model}` is any model ' +
|
|
868
893
|
'from your pushed schema — the routes are the same whichever it is. ' +
|
|
869
894
|
'Authenticate every request with your API key as a Bearer token.',
|
|
870
895
|
paths,
|
|
@@ -962,6 +987,12 @@ export function schemaToOpenApi<S extends SchemaRecord>(
|
|
|
962
987
|
}
|
|
963
988
|
|
|
964
989
|
paths['/v1/commits'] = {
|
|
990
|
+
get: {
|
|
991
|
+
tags: ['commits'],
|
|
992
|
+
summary: 'List commit records',
|
|
993
|
+
parameters: queryParams(commitRecordListQuerySchema),
|
|
994
|
+
responses: { '200': jsonResp('Tenant-scoped commit records', derive(commitRecordListSchema, 'output')) },
|
|
995
|
+
},
|
|
965
996
|
post: {
|
|
966
997
|
tags: ['commits'],
|
|
967
998
|
summary: 'Commit a batch of operations atomically, and/or register durable premises',
|
|
@@ -970,7 +1001,17 @@ export function schemaToOpenApi<S extends SchemaRecord>(
|
|
|
970
1001
|
responses: { '200': commitReceipt() },
|
|
971
1002
|
},
|
|
972
1003
|
};
|
|
1004
|
+
paths['/v1/commits/{id}'] = {
|
|
1005
|
+
get: {
|
|
1006
|
+
tags: ['commits'],
|
|
1007
|
+
summary: 'Retrieve a commit record',
|
|
1008
|
+
parameters: [idParam()],
|
|
1009
|
+
responses: { '200': jsonResp('The commit record, or null when absent', derive(commitRecordSchema.nullable(), 'output')) },
|
|
1010
|
+
},
|
|
1011
|
+
};
|
|
1012
|
+
operationIds['GET /v1/commits'] = 'listCommits';
|
|
973
1013
|
operationIds['POST /v1/commits'] = 'commit';
|
|
1014
|
+
operationIds['GET /v1/commits/{id}'] = 'getCommit';
|
|
974
1015
|
applyOperationIds(paths, operationIds);
|
|
975
1016
|
attachCanonicalErrors(paths);
|
|
976
1017
|
|
package/src/server/commit.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type { ParticipantKind, ConfirmationState } from '../log/syncDeltaRow.js'
|
|
|
17
17
|
import type { ParticipantRef } from '../wire/delta.js';
|
|
18
18
|
import type { CommitExecutionResultInput } from '../wire/commit.js';
|
|
19
19
|
import type { ReadDependency, TrackDependency } from '../coordination/schema.js';
|
|
20
|
+
import type { EffectiveAuthority } from '../auth/capability.js';
|
|
20
21
|
|
|
21
22
|
export interface CommitContext {
|
|
22
23
|
participantId: string;
|
|
@@ -25,6 +26,11 @@ export interface CommitContext {
|
|
|
25
26
|
* structured attribution rather than a string-prefix convention.
|
|
26
27
|
*/
|
|
27
28
|
participantKind: ParticipantKind;
|
|
29
|
+
/** Effective data grant resolved from the authenticated identity at commit time. */
|
|
30
|
+
authority?: EffectiveAuthority;
|
|
31
|
+
/** Server request identity and ingress used for physical-attempt evidence. */
|
|
32
|
+
requestId?: string;
|
|
33
|
+
transport?: 'http' | 'websocket' | 'internal';
|
|
28
34
|
organizationId: string;
|
|
29
35
|
/** Immutable branch selected by the authenticated credential. */
|
|
30
36
|
branchId: string;
|
|
@@ -90,7 +96,7 @@ export interface CommitContext {
|
|
|
90
96
|
|
|
91
97
|
/**
|
|
92
98
|
* The server execution receipt persisted in `mutation_log`. Its runtime schema
|
|
93
|
-
* lives with the HTTP/WS
|
|
99
|
+
* lives with the HTTP/WS confirmation contract so queued correlation cannot drift
|
|
94
100
|
* between cache, transport, and client.
|
|
95
101
|
*/
|
|
96
102
|
export type CommitExecutionResult = CommitExecutionResultInput;
|
package/src/server/index.ts
CHANGED
|
@@ -21,6 +21,10 @@ export type {
|
|
|
21
21
|
AdapterResolver,
|
|
22
22
|
} from './adapter.js';
|
|
23
23
|
export type { CommitContext, CommitExecutionResult } from './commit.js';
|
|
24
|
-
export {
|
|
24
|
+
export {
|
|
25
|
+
commitExecutionResultSchema,
|
|
26
|
+
commitRecordSchema,
|
|
27
|
+
type CommitRecord,
|
|
28
|
+
} from '../wire/commit.js';
|
|
25
29
|
export { storageModeSchema, type StorageMode } from './storageMode.js';
|
|
26
30
|
export type { ColumnOverride, BootstrapModel } from './readConfig.js';
|
|
@@ -338,15 +338,23 @@ export function kyselyDirectMutation<S extends SchemaRecord>(
|
|
|
338
338
|
db: KyselyLike,
|
|
339
339
|
schema: Schema<S>,
|
|
340
340
|
): MutationAdapter {
|
|
341
|
+
const markerModels = new Map<string, string>();
|
|
342
|
+
for (const [key, definition] of Object.entries(schema.models)) {
|
|
343
|
+
const typename = definition.typename || key;
|
|
344
|
+
markerModels.set(key, typename);
|
|
345
|
+
markerModels.set(key.toLowerCase(), typename);
|
|
346
|
+
markerModels.set(typename, typename);
|
|
347
|
+
markerModels.set(typename.toLowerCase(), typename);
|
|
348
|
+
}
|
|
341
349
|
return createKyselyMutationAdapter(
|
|
342
350
|
db,
|
|
343
351
|
createKyselyMutationCore(db, schema),
|
|
344
352
|
'direct',
|
|
345
353
|
{
|
|
346
|
-
markerModelFor: (operationModel) =>
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
354
|
+
markerModelFor: (operationModel) =>
|
|
355
|
+
markerModels.get(operationModel) ??
|
|
356
|
+
markerModels.get(operationModel.toLowerCase()) ??
|
|
357
|
+
operationModel,
|
|
350
358
|
},
|
|
351
359
|
);
|
|
352
360
|
}
|
|
@@ -100,11 +100,23 @@ function buildColumnMaps(schema: Schema): ReadonlyMap<string, ModelColumns> {
|
|
|
100
100
|
}
|
|
101
101
|
const orgColumn = tenancyColumn(model.tenancy);
|
|
102
102
|
if (orgColumn) register('organizationId', orgColumn);
|
|
103
|
-
|
|
103
|
+
const columns = {
|
|
104
104
|
table: model.tableName ?? key,
|
|
105
105
|
fieldToColumn,
|
|
106
106
|
columnToField,
|
|
107
|
-
}
|
|
107
|
+
};
|
|
108
|
+
// The public clients normalize operation model names to lowercase before
|
|
109
|
+
// they cross the wire. Preserve the authored key for direct adapter users,
|
|
110
|
+
// but resolve the same lowercase key/typename aliases as the server's
|
|
111
|
+
// schema-derived ModelMap. Without these aliases, a camelCase schema key
|
|
112
|
+
// such as `collaborationWorkItems` validates at the server boundary and
|
|
113
|
+
// then fails here as an unknown customer model.
|
|
114
|
+
out.set(key, columns);
|
|
115
|
+
out.set(key.toLowerCase(), columns);
|
|
116
|
+
if (model.typename) {
|
|
117
|
+
out.set(model.typename, columns);
|
|
118
|
+
out.set(model.typename.toLowerCase(), columns);
|
|
119
|
+
}
|
|
108
120
|
}
|
|
109
121
|
return out;
|
|
110
122
|
}
|
package/src/source/index.ts
CHANGED
package/src/source/types.ts
CHANGED
|
@@ -152,7 +152,7 @@ export interface SourceEvent {
|
|
|
152
152
|
*/
|
|
153
153
|
readonly organizationId?: string;
|
|
154
154
|
/**
|
|
155
|
-
* @deprecated Legacy echo identity. It is not trusted for queued
|
|
155
|
+
* @deprecated Legacy echo identity. It is not trusted for queued confirmation;
|
|
156
156
|
* use `correlationId` and `transactionId` for mediated endpoint writes.
|
|
157
157
|
*/
|
|
158
158
|
readonly clientTxId?: string;
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
import { listEnvelope } from '@abloatai/transaction/wire';
|
|
19
19
|
import type {
|
|
20
|
+
CommitAck,
|
|
21
|
+
CommitReceiptWire,
|
|
20
22
|
ModelReadResponse,
|
|
21
23
|
ModelListResponse,
|
|
22
24
|
ClaimAcquiredResponse,
|
|
@@ -25,6 +27,55 @@ import type {
|
|
|
25
27
|
ClaimHeartbeatReply,
|
|
26
28
|
} from '@abloatai/transaction/wire';
|
|
27
29
|
import type { ModelClaim } from '@abloatai/transaction/coordination';
|
|
30
|
+
import type { EffectiveAuthority } from '@abloatai/transaction/auth';
|
|
31
|
+
|
|
32
|
+
/** Stable server-authored times shared by commit boundary fixtures. */
|
|
33
|
+
export const COMMIT_FIXTURE_TIMES = {
|
|
34
|
+
createdAt: '2026-08-05T10:00:00.000Z',
|
|
35
|
+
statusAt: '2026-08-05T10:00:00.058Z',
|
|
36
|
+
} as const;
|
|
37
|
+
|
|
38
|
+
/** Canonical authenticated authority for transport-boundary test responses. */
|
|
39
|
+
export const EFFECTIVE_AUTHORITY_FIXTURE = {
|
|
40
|
+
organizationId: 'org-1',
|
|
41
|
+
projectId: 'project-1',
|
|
42
|
+
branchId: 'branch-1',
|
|
43
|
+
syncGroups: ['org:org-1'],
|
|
44
|
+
operations: [],
|
|
45
|
+
participantKind: 'agent',
|
|
46
|
+
participantId: 'agent-1',
|
|
47
|
+
deliveryPartition: null,
|
|
48
|
+
} satisfies EffectiveAuthority;
|
|
49
|
+
|
|
50
|
+
/** A canonical hosted confirmation returned by HTTP or WebSocket. */
|
|
51
|
+
export function confirmedCommitReceiptResponse(args: {
|
|
52
|
+
clientTxId: string;
|
|
53
|
+
lastSyncId?: number;
|
|
54
|
+
serverTxId?: string;
|
|
55
|
+
ops?: number;
|
|
56
|
+
}): CommitReceiptWire {
|
|
57
|
+
const lastSyncId = args.lastSyncId ?? 0;
|
|
58
|
+
return {
|
|
59
|
+
object: 'commit_receipt',
|
|
60
|
+
clientTxId: args.clientTxId,
|
|
61
|
+
serverTxId: args.serverTxId ?? String(lastSyncId),
|
|
62
|
+
...COMMIT_FIXTURE_TIMES,
|
|
63
|
+
success: true,
|
|
64
|
+
authority: EFFECTIVE_AUTHORITY_FIXTURE,
|
|
65
|
+
status: 'confirmed',
|
|
66
|
+
lastSyncId,
|
|
67
|
+
ops: args.ops ?? 1,
|
|
68
|
+
} satisfies CommitReceiptWire;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Canonical normalized acknowledgement for mutation-executor test doubles. */
|
|
72
|
+
export function confirmedCommitAck(lastSyncId = 0): CommitAck {
|
|
73
|
+
return {
|
|
74
|
+
status: 'confirmed',
|
|
75
|
+
statusAt: COMMIT_FIXTURE_TIMES.statusAt,
|
|
76
|
+
lastSyncId,
|
|
77
|
+
} satisfies CommitAck;
|
|
78
|
+
}
|
|
28
79
|
|
|
29
80
|
/** `GET /v1/models/{model}/{id}`. Pass `data: null` for a miss. */
|
|
30
81
|
export function modelReadResponse(args: {
|
|
@@ -51,14 +102,22 @@ export function modelListResponse(args: {
|
|
|
51
102
|
hasMore?: boolean;
|
|
52
103
|
nextCursor?: string | null;
|
|
53
104
|
stamp?: number;
|
|
105
|
+
evidence?: readonly { id: string; stamp: number }[];
|
|
54
106
|
}): ModelListResponse {
|
|
107
|
+
const stamp = args.stamp ?? 0;
|
|
108
|
+
const inferredEvidence = args.data.flatMap((row): { id: string; stamp: number }[] => {
|
|
109
|
+
if (typeof row !== 'object' || row === null) return [];
|
|
110
|
+
const id = (row as { id?: unknown }).id;
|
|
111
|
+
return typeof id === 'string' ? [{ id, stamp }] : [];
|
|
112
|
+
});
|
|
55
113
|
return {
|
|
56
114
|
object: 'list',
|
|
57
115
|
model: args.model,
|
|
58
116
|
data: [...args.data],
|
|
59
117
|
has_more: args.hasMore ?? false,
|
|
60
118
|
next_cursor: args.nextCursor ?? null,
|
|
61
|
-
stamp
|
|
119
|
+
stamp,
|
|
120
|
+
evidence: [...(args.evidence ?? inferredEvidence)],
|
|
62
121
|
} satisfies ModelListResponse;
|
|
63
122
|
}
|
|
64
123
|
|
package/src/transactionLayer.ts
CHANGED
|
@@ -30,7 +30,7 @@ import type { ModelScope } from './types/index.js';
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* A commit's durable acceptance — `{ status: 'queued' }` with the correlation
|
|
33
|
-
* the
|
|
33
|
+
* the confirmation feed later confirms. The wire layer owns the receipt fields;
|
|
34
34
|
* the seam adds the client idempotency identity needed for a safe wait/retry.
|
|
35
35
|
*/
|
|
36
36
|
export type CommitReceipt = ClientCommitReceipt & {
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { z } from 'zod';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
MAX_READ_SET_ENTRIES,
|
|
13
|
+
readDependencyListSchema,
|
|
14
|
+
readSetProjectionEntryCount,
|
|
15
|
+
trackDependencyListSchema,
|
|
16
|
+
} from '../../coordination/schema.js';
|
|
12
17
|
import { wireCommitOperationSchema } from '../../wire/frames.js';
|
|
13
18
|
import { correlationIdSchema } from '../../wire/commit.js';
|
|
14
19
|
import { idempotencyKeySchema } from './idempotencyKey.js';
|
|
@@ -41,6 +46,7 @@ export const durableCommitOperationSchema = wireCommitOperationSchema
|
|
|
41
46
|
id: true,
|
|
42
47
|
input: true,
|
|
43
48
|
transactionId: true,
|
|
49
|
+
claimId: true,
|
|
44
50
|
readAt: true,
|
|
45
51
|
onStale: true,
|
|
46
52
|
fenceToken: true,
|
|
@@ -50,6 +56,7 @@ export const durableCommitOperationSchema = wireCommitOperationSchema
|
|
|
50
56
|
id: z.string().min(1),
|
|
51
57
|
input: z.record(z.string(), z.unknown()).optional(),
|
|
52
58
|
transactionId: z.string().min(1).optional(),
|
|
59
|
+
claimId: z.string().min(1).optional(),
|
|
53
60
|
options: z
|
|
54
61
|
.strictObject({
|
|
55
62
|
idempotencyKey: z.string().min(1).max(255).nullable().optional(),
|
|
@@ -62,8 +69,11 @@ export type DurableCommitOperation = z.infer<typeof durableCommitOperationSchema
|
|
|
62
69
|
export type DurableCommitOperationInput = z.input<typeof durableCommitOperationSchema>;
|
|
63
70
|
|
|
64
71
|
const durableCommitOptionsSchema = z.strictObject({
|
|
65
|
-
reads:
|
|
66
|
-
track:
|
|
72
|
+
reads: readDependencyListSchema.nullable().optional(),
|
|
73
|
+
track: trackDependencyListSchema.nullable().optional(),
|
|
74
|
+
}).refine((value) => readSetProjectionEntryCount(value) <= MAX_READ_SET_ENTRIES, {
|
|
75
|
+
path: ['reads'],
|
|
76
|
+
message: `reads and track may contain at most ${MAX_READ_SET_ENTRIES} entries combined`,
|
|
67
77
|
});
|
|
68
78
|
|
|
69
79
|
export const commitOutboxScopeSchema = z.strictObject({
|
|
@@ -53,6 +53,7 @@ export interface CommitFrameOperation {
|
|
|
53
53
|
readonly id: string;
|
|
54
54
|
readonly input?: Record<string, unknown>;
|
|
55
55
|
readonly transactionId?: string;
|
|
56
|
+
readonly claimId?: string | null;
|
|
56
57
|
readonly readAt?: number | null;
|
|
57
58
|
readonly onStale?: OnStaleMode | null;
|
|
58
59
|
readonly fenceToken?: number | null;
|
|
@@ -80,17 +81,18 @@ export function buildCommitFrame(
|
|
|
80
81
|
id: op.id,
|
|
81
82
|
input: op.input,
|
|
82
83
|
transactionId: op.transactionId,
|
|
84
|
+
claimId: op.claimId,
|
|
83
85
|
readAt: op.readAt,
|
|
84
86
|
onStale: op.onStale,
|
|
85
87
|
fenceToken: op.fenceToken,
|
|
86
88
|
})),
|
|
87
89
|
clientTxId,
|
|
88
90
|
};
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
if (track
|
|
91
|
+
// Preserve declaration at the wire boundary. `null`, an explicit empty list,
|
|
92
|
+
// and omission are distinct inputs even when today's server treats each as
|
|
93
|
+
// "none"; the WebSocket path must not silently erase a field the caller set.
|
|
94
|
+
if (reads !== undefined) payload.reads = reads === null ? null : [...reads];
|
|
95
|
+
if (track !== undefined) payload.track = track === null ? null : [...track];
|
|
94
96
|
return { type: 'commit', payload };
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* first successful connect and disposed on teardown.
|
|
16
16
|
*
|
|
17
17
|
* This is a plain state machine, not a reactive object: it lives in the
|
|
18
|
-
*
|
|
18
|
+
* confirmation core, which carries no reactivity runtime (ADR 0016). A consumer
|
|
19
19
|
* that wants to render recovery progress mirrors the transitions through
|
|
20
20
|
* {@link ConnectionCallbacks.onStateChange} into its own observable state —
|
|
21
21
|
* which is what the reactive engine's store does.
|