@cynodia/axiom-server 0.11.0-alpha.1 → 0.11.2-alpha.1
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/README.md +46 -19
- package/conformance/migrations/add-optional-field.json +1 -1
- package/conformance/migrations/add-required-field-with-default.json +1 -1
- package/conformance/migrations/crash-and-resume.json +1 -1
- package/conformance/migrations/destructive-removal-approved.json +1 -1
- package/conformance/migrations/destructive-removal-refused.json +1 -1
- package/conformance/migrations/idempotent-rerun.json +1 -1
- package/conformance/migrations/invalid-target-record.json +1 -1
- package/conformance/migrations/large-batched-transformation.json +1 -1
- package/conformance/migrations/manifest.json +1 -1
- package/conformance/migrations/metadata-only-change.json +1 -1
- package/conformance/migrations/migration-lock.json +1 -1
- package/conformance/migrations/missing-migration-path.json +1 -1
- package/conformance/migrations/record-transformation.json +1 -1
- package/conformance/migrations/relationship-addition.json +1 -1
- package/conformance/migrations/remove-empty-field.json +1 -1
- package/conformance/migrations/schema-fingerprint-mismatch.json +1 -1
- package/conformance/migrations/transform-field.json +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/migration-execute.d.ts +16 -4
- package/dist/migration-execute.js +64 -12
- package/dist/migration-executor.d.ts +24 -0
- package/dist/migration-executor.js +120 -32
- package/dist/migration-gate.d.ts +42 -9
- package/dist/migration-gate.js +107 -20
- package/dist/migration.d.ts +12 -0
- package/dist/migration.js +12 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +56 -27
- package/dist/sqlite-contention.d.ts +80 -0
- package/dist/sqlite-contention.js +125 -0
- package/dist/sqlite-migration.d.ts +14 -0
- package/dist/sqlite-migration.js +193 -116
- package/package.json +3 -3
- package/schema/protocol.v1.schema.json +1 -1
- package/schema/server-ir.v7.schema.json +1 -1
package/README.md
CHANGED
|
@@ -22,21 +22,39 @@ SQLite), `TransportAdapter` (in-process and HTTP), `ServerHost` for time, identi
|
|
|
22
22
|
scheduling and authentication, and `IntegrationAdapter` for external systems. Nothing in
|
|
23
23
|
an ApplicationGraph mentions HTTP, SQL, a route or an SDK.
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
webhook
|
|
30
|
-
[`docs/INTEGRATIONS.md`](https://github.com/cynodia/axiom/blob/main/docs/INTEGRATIONS.md),
|
|
31
|
-
[`docs/EFFECTS.md`](https://github.com/cynodia/axiom/blob/main/docs/EFFECTS.md)
|
|
32
|
-
[`docs/TRIGGERS.md`](https://github.com/cynodia/axiom/blob/main/docs/TRIGGERS.md).
|
|
25
|
+
This package also owns the parts of an application that only the authority can run:
|
|
26
|
+
|
|
27
|
+
- **Timed and event-driven execution** (0.8) — scheduling and dispatching `TriggerDef`s,
|
|
28
|
+
the transactional outbox for `integration-effect` intent, post-commit effect delivery
|
|
29
|
+
with retry, and verified-webhook → semantic-event translation. See
|
|
30
|
+
[`docs/INTEGRATIONS.md`](https://github.com/cynodia/axiom/blob/main/docs/INTEGRATIONS.md),
|
|
31
|
+
[`docs/EFFECTS.md`](https://github.com/cynodia/axiom/blob/main/docs/EFFECTS.md),
|
|
32
|
+
[`docs/TRIGGERS.md`](https://github.com/cynodia/axiom/blob/main/docs/TRIGGERS.md).
|
|
33
|
+
- **Inbound streams and binary storage** (0.9) — long-lived `SubscriptionDef` sources
|
|
34
|
+
feeding the event pipeline, and `StorageDef` object stores behind host blob transports.
|
|
35
|
+
See [`docs/SUBSCRIPTIONS.md`](https://github.com/cynodia/axiom/blob/main/docs/SUBSCRIPTIONS.md),
|
|
36
|
+
[`docs/STORAGE.md`](https://github.com/cynodia/axiom/blob/main/docs/STORAGE.md).
|
|
37
|
+
- **The query layer** (0.10) — demand-driven `QueryDef` reads over authoritative data too
|
|
38
|
+
large to materialize, `DataProvider`s (`createMemoryDataProvider` / `createSqliteDataProvider`),
|
|
39
|
+
fingerprinted keyset cursors, and a principal/policy-scoped result cache. See
|
|
40
|
+
[`docs/QUERIES.md`](https://github.com/cynodia/axiom/blob/main/docs/QUERIES.md).
|
|
41
|
+
- **Schema evolution** (0.11) — `planMigration` / `explainMigration`, the host-controlled
|
|
42
|
+
`executeMigration`, the durable `MigrationMetadataStore` and lease lock, keyset-batched
|
|
43
|
+
crash-resumable migration, memory and SQLite `MigrationRowStore`s, and the
|
|
44
|
+
`createAxiomServer` startup gate. Concurrent migrators — including two OS processes on one
|
|
45
|
+
SQLite file — resolve to `completed` / `MIGRATION_IN_PROGRESS` / `alreadyAtTarget`; the
|
|
46
|
+
SQLite provider absorbs physical `SQLITE_BUSY` contention internally (0.11.2). See
|
|
47
|
+
[`docs/MIGRATIONS.md`](https://github.com/cynodia/axiom/blob/main/docs/MIGRATIONS.md).
|
|
33
48
|
|
|
34
49
|
Main exports: `createAxiomServer`, `createMemoryPersistence`, `createSqlitePersistence`,
|
|
35
50
|
`createServerHost`, `createDeterministicServerHost`, `createDirectTransport`,
|
|
36
51
|
`createHttpTransport`, `createRemoteGateway`, `serveOverHttp`, `serveAxiomApplication`,
|
|
37
52
|
`createFakeIntegrationAdapter`, `createHttpIntegrationAdapter`, `createTriggerRuntime`,
|
|
38
|
-
`createEffectRunner`, `
|
|
39
|
-
`
|
|
53
|
+
`createEffectRunner`, `createMemoryDataProvider`, `createSqliteDataProvider`,
|
|
54
|
+
`createMemoryMigrationStore`, `createSqliteMigrationStore`, `createMemoryRowStore`,
|
|
55
|
+
`createSqliteRowStore`, `planMigration`, `explainMigration`, `executeMigration`,
|
|
56
|
+
`getMigrationStatus`, `migrationAuthority`, `runConformanceFixture`, `runConformanceSuite`,
|
|
57
|
+
`runQueryConformanceFixture`, `runMigrationConformanceFixture`, `SERVER_DIAGNOSTIC_CODES`.
|
|
40
58
|
|
|
41
59
|
`serveAxiomApplication` is the whole deployment story: it serves the generated page at `GET /`
|
|
42
60
|
and the semantic endpoint at `POST /axiom`, from one process, for any Axiom application. No
|
|
@@ -44,17 +62,26 @@ route, controller, handler or SQL statement is written by an application author.
|
|
|
44
62
|
|
|
45
63
|
### Portable artifacts
|
|
46
64
|
|
|
47
|
-
|
|
48
|
-
|
|
65
|
+
The Server IR contract is versioned. **`axiom.server.v1` is frozen** — its bytes and
|
|
66
|
+
semantics never change — but it is **not the current contract**: a document declares the
|
|
67
|
+
oldest contract that can carry its vocabulary, computed from the document. The current
|
|
68
|
+
contract is **`axiom.server.v7`** (0.11 schema-evolution vocabulary); `v1`–`v6` are frozen
|
|
69
|
+
and shipped for compatibility. This package ships what an implementation in another language
|
|
70
|
+
needs to conform:
|
|
49
71
|
|
|
50
72
|
```
|
|
51
|
-
@cynodia/axiom-server/conformance
|
|
52
|
-
@cynodia/axiom-server/conformance/<name>.json
|
|
53
|
-
@cynodia/axiom-server/
|
|
54
|
-
@cynodia/axiom-server/
|
|
55
|
-
@cynodia/axiom-server/schema/server-ir.
|
|
56
|
-
@cynodia/axiom-server/schema/server-ir.
|
|
57
|
-
@cynodia/axiom-server/schema/
|
|
73
|
+
@cynodia/axiom-server/conformance the base fixture manifest
|
|
74
|
+
@cynodia/axiom-server/conformance/<name>.json one base fixture: IR, state, invocations, expectations
|
|
75
|
+
@cynodia/axiom-server/conformance/queries/<name>.json one query conformance fixture (axiom.conformance.v4)
|
|
76
|
+
@cynodia/axiom-server/conformance/migrations/<name>.json one migration conformance fixture (axiom.conformance.v5)
|
|
77
|
+
@cynodia/axiom-server/schema/server-ir.v1.schema.json JSON Schema for the frozen v1 IR
|
|
78
|
+
@cynodia/axiom-server/schema/server-ir.v2.schema.json v2 (+ group, expression-ref)
|
|
79
|
+
@cynodia/axiom-server/schema/server-ir.v3.schema.json v3 (+ integrations, effects, triggers, events)
|
|
80
|
+
@cynodia/axiom-server/schema/server-ir.v4.schema.json v4 (+ invocation source, structured effect outcome)
|
|
81
|
+
@cynodia/axiom-server/schema/server-ir.v5.schema.json v5 (+ subscriptions, storage, blob operations)
|
|
82
|
+
@cynodia/axiom-server/schema/server-ir.v6.schema.json v6 (+ queries, relationships, read policies)
|
|
83
|
+
@cynodia/axiom-server/schema/server-ir.v7.schema.json v7 (+ migrations, schema version + fingerprint) — current
|
|
84
|
+
@cynodia/axiom-server/schema/protocol.v1.schema.json JSON Schema for the protocol
|
|
58
85
|
```
|
|
59
86
|
|
|
60
87
|
The fixtures are pure data. Running them requires no part of this implementation — which is
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"contract": "axiom.server.v7",
|
|
11
11
|
"id": "migration-conformance",
|
|
12
12
|
"name": "Migration Conformance",
|
|
13
|
-
"version": "0.11.
|
|
13
|
+
"version": "0.11.2-alpha.1",
|
|
14
14
|
"schemaVersion": 2,
|
|
15
15
|
"schemaFingerprint": "7e4d24d95c273ff084e7758508a02e35015d5db2ceda46ca61e7ee8a2c2a4929",
|
|
16
16
|
"migrations": [
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"contract": "axiom.server.v7",
|
|
11
11
|
"id": "migration-conformance",
|
|
12
12
|
"name": "Migration Conformance",
|
|
13
|
-
"version": "0.11.
|
|
13
|
+
"version": "0.11.2-alpha.1",
|
|
14
14
|
"schemaVersion": 2,
|
|
15
15
|
"schemaFingerprint": "2c478dc5301786b76c6990e38e1659d621d7712fdfa785bf115b1275b9804877",
|
|
16
16
|
"migrations": [
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"contract": "axiom.server.v7",
|
|
13
13
|
"id": "migration-conformance",
|
|
14
14
|
"name": "Migration Conformance",
|
|
15
|
-
"version": "0.11.
|
|
15
|
+
"version": "0.11.2-alpha.1",
|
|
16
16
|
"schemaVersion": 2,
|
|
17
17
|
"schemaFingerprint": "2c478dc5301786b76c6990e38e1659d621d7712fdfa785bf115b1275b9804877",
|
|
18
18
|
"migrations": [
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"contract": "axiom.server.v7",
|
|
11
11
|
"id": "migration-conformance",
|
|
12
12
|
"name": "Migration Conformance",
|
|
13
|
-
"version": "0.11.
|
|
13
|
+
"version": "0.11.2-alpha.1",
|
|
14
14
|
"schemaVersion": 2,
|
|
15
15
|
"schemaFingerprint": "f9f0f88b1d0cd25eb570c1401fe3f96ca899330d8c210e79ef50a10e91ad236d",
|
|
16
16
|
"migrations": [
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"contract": "axiom.server.v7",
|
|
13
13
|
"id": "migration-conformance",
|
|
14
14
|
"name": "Migration Conformance",
|
|
15
|
-
"version": "0.11.
|
|
15
|
+
"version": "0.11.2-alpha.1",
|
|
16
16
|
"schemaVersion": 2,
|
|
17
17
|
"schemaFingerprint": "f9f0f88b1d0cd25eb570c1401fe3f96ca899330d8c210e79ef50a10e91ad236d",
|
|
18
18
|
"migrations": [
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"contract": "axiom.server.v7",
|
|
11
11
|
"id": "migration-conformance",
|
|
12
12
|
"name": "Migration Conformance",
|
|
13
|
-
"version": "0.11.
|
|
13
|
+
"version": "0.11.2-alpha.1",
|
|
14
14
|
"schemaVersion": 2,
|
|
15
15
|
"schemaFingerprint": "2c478dc5301786b76c6990e38e1659d621d7712fdfa785bf115b1275b9804877",
|
|
16
16
|
"migrations": [
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"contract": "axiom.server.v7",
|
|
12
12
|
"id": "migration-conformance",
|
|
13
13
|
"name": "Migration Conformance",
|
|
14
|
-
"version": "0.11.
|
|
14
|
+
"version": "0.11.2-alpha.1",
|
|
15
15
|
"schemaVersion": 2,
|
|
16
16
|
"schemaFingerprint": "2c478dc5301786b76c6990e38e1659d621d7712fdfa785bf115b1275b9804877",
|
|
17
17
|
"migrations": [
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"contract": "axiom.server.v7",
|
|
12
12
|
"id": "migration-conformance",
|
|
13
13
|
"name": "Migration Conformance",
|
|
14
|
-
"version": "0.11.
|
|
14
|
+
"version": "0.11.2-alpha.1",
|
|
15
15
|
"schemaVersion": 2,
|
|
16
16
|
"schemaFingerprint": "2c478dc5301786b76c6990e38e1659d621d7712fdfa785bf115b1275b9804877",
|
|
17
17
|
"migrations": [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"conformance": "axiom.conformance.v5",
|
|
3
3
|
"baseContract": "axiom.server.v7",
|
|
4
|
-
"release": "0.11.
|
|
4
|
+
"release": "0.11.2-alpha.1",
|
|
5
5
|
"description": "Portable migration conformance fixtures (spec11 §84-86). Each file carries a compiled axiom.server.v7 target Server IR with its MigrationDef chain, the schema version the persisted data starts at, the source rows, any destructive approvals, and the exact expected outcome. Running one needs only a row store and the semantics in docs/MIGRATIONS.md + docs/AUTHORITY.md. Every fixture must produce equivalent target data on the memory and SQLite providers (spec11 §83).",
|
|
6
6
|
"fixtures": [
|
|
7
7
|
{
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"contract": "axiom.server.v7",
|
|
11
11
|
"id": "migration-conformance",
|
|
12
12
|
"name": "Migration Conformance",
|
|
13
|
-
"version": "0.11.
|
|
13
|
+
"version": "0.11.2-alpha.1",
|
|
14
14
|
"schemaVersion": 2,
|
|
15
15
|
"schemaFingerprint": "f9f0f88b1d0cd25eb570c1401fe3f96ca899330d8c210e79ef50a10e91ad236d",
|
|
16
16
|
"migrations": [
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"contract": "axiom.server.v7",
|
|
12
12
|
"id": "migration-conformance",
|
|
13
13
|
"name": "Migration Conformance",
|
|
14
|
-
"version": "0.11.
|
|
14
|
+
"version": "0.11.2-alpha.1",
|
|
15
15
|
"schemaVersion": 2,
|
|
16
16
|
"schemaFingerprint": "2c478dc5301786b76c6990e38e1659d621d7712fdfa785bf115b1275b9804877",
|
|
17
17
|
"migrations": [
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"contract": "axiom.server.v7",
|
|
11
11
|
"id": "migration-conformance",
|
|
12
12
|
"name": "Migration Conformance",
|
|
13
|
-
"version": "0.11.
|
|
13
|
+
"version": "0.11.2-alpha.1",
|
|
14
14
|
"schemaVersion": 3,
|
|
15
15
|
"schemaFingerprint": "5f584b6aacbe13facfb360b562bdfa020f96e5f4e6d7f63c3d0dc7edacc207eb",
|
|
16
16
|
"migrations": [
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"contract": "axiom.server.v7",
|
|
12
12
|
"id": "migration-conformance",
|
|
13
13
|
"name": "Migration Conformance",
|
|
14
|
-
"version": "0.11.
|
|
14
|
+
"version": "0.11.2-alpha.1",
|
|
15
15
|
"schemaVersion": 2,
|
|
16
16
|
"schemaFingerprint": "428fde4036f9c14ec63905ce893e907318542fd1a89e6f946b4400713c80a4de",
|
|
17
17
|
"migrations": [
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"contract": "axiom.server.v7",
|
|
11
11
|
"id": "migration-conformance",
|
|
12
12
|
"name": "Migration Conformance",
|
|
13
|
-
"version": "0.11.
|
|
13
|
+
"version": "0.11.2-alpha.1",
|
|
14
14
|
"schemaVersion": 2,
|
|
15
15
|
"schemaFingerprint": "4b8e155b1b02f04b28cadd78f580b4ac35f5ba0149e4809e31bbabd145852af4",
|
|
16
16
|
"migrations": [
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"contract": "axiom.server.v7",
|
|
11
11
|
"id": "migration-conformance",
|
|
12
12
|
"name": "Migration Conformance",
|
|
13
|
-
"version": "0.11.
|
|
13
|
+
"version": "0.11.2-alpha.1",
|
|
14
14
|
"schemaVersion": 2,
|
|
15
15
|
"schemaFingerprint": "f9f0f88b1d0cd25eb570c1401fe3f96ca899330d8c210e79ef50a10e91ad236d",
|
|
16
16
|
"migrations": [
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"contract": "axiom.server.v7",
|
|
12
12
|
"id": "migration-conformance",
|
|
13
13
|
"name": "Migration Conformance",
|
|
14
|
-
"version": "0.11.
|
|
14
|
+
"version": "0.11.2-alpha.1",
|
|
15
15
|
"schemaVersion": 2,
|
|
16
16
|
"schemaFingerprint": "2c478dc5301786b76c6990e38e1659d621d7712fdfa785bf115b1275b9804877",
|
|
17
17
|
"migrations": [
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"contract": "axiom.server.v7",
|
|
11
11
|
"id": "migration-conformance",
|
|
12
12
|
"name": "Migration Conformance",
|
|
13
|
-
"version": "0.11.
|
|
13
|
+
"version": "0.11.2-alpha.1",
|
|
14
14
|
"schemaVersion": 2,
|
|
15
15
|
"schemaFingerprint": "f9f0f88b1d0cd25eb570c1401fe3f96ca899330d8c210e79ef50a10e91ad236d",
|
|
16
16
|
"migrations": [
|
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ export * from './migration-eval.js';
|
|
|
31
31
|
export * from './migration-executor.js';
|
|
32
32
|
export * from './migration-execute.js';
|
|
33
33
|
export * from './migration-conformance.js';
|
|
34
|
+
export { SqliteContentionError, isSqliteContentionError, runWithBusyHandling, DEFAULT_BUSY_TIMEOUT_MS, DEFAULT_BUSY_ATTEMPTS, DEFAULT_BUSY_BACKOFF_MS, } from './sqlite-contention.js';
|
|
35
|
+
export type { BusyHandlingOptions } from './sqlite-contention.js';
|
|
34
36
|
export { createSqliteMigrationStore, createSqliteRowStore, isSqliteMigrationAvailable, } from './sqlite-migration.js';
|
|
35
37
|
export type { SqliteMigrationStoreOptions, SqliteRowStoreOptions } from './sqlite-migration.js';
|
|
36
38
|
export { createSqliteDataProvider, isSqliteAvailable as isSqliteDataProviderAvailable } from './sqlite-data-provider.js';
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ export * from './migration-eval.js';
|
|
|
22
22
|
export * from './migration-executor.js';
|
|
23
23
|
export * from './migration-execute.js';
|
|
24
24
|
export * from './migration-conformance.js';
|
|
25
|
+
export { SqliteContentionError, isSqliteContentionError, runWithBusyHandling, DEFAULT_BUSY_TIMEOUT_MS, DEFAULT_BUSY_ATTEMPTS, DEFAULT_BUSY_BACKOFF_MS, } from './sqlite-contention.js';
|
|
25
26
|
export { createSqliteMigrationStore, createSqliteRowStore, isSqliteMigrationAvailable, } from './sqlite-migration.js';
|
|
26
27
|
export { createSqliteDataProvider, isSqliteAvailable as isSqliteDataProviderAvailable } from './sqlite-data-provider.js';
|
|
27
28
|
export * from './effects.js';
|
|
@@ -18,16 +18,28 @@ import type { SchemaGateResult } from './migration-gate.js';
|
|
|
18
18
|
*/
|
|
19
19
|
/**
|
|
20
20
|
* The authority to run a migration. Deliberately **not** the nullable `PRINCIPAL` an action
|
|
21
|
-
* authorization reads (spec11 §74): it is a distinct, host-minted
|
|
22
|
-
*
|
|
21
|
+
* authorization reads (spec11 §74): it is a distinct, host-minted capability.
|
|
22
|
+
*
|
|
23
|
+
* Authorization is by **provenance, not shape** (spec11.1 §16-17). The object returned by
|
|
24
|
+
* `migrationAuthority()` is registered in a process-private `WeakSet` and frozen;
|
|
25
|
+
* `isMigrationPrincipal` checks membership of that set. An object a consumer builds with the
|
|
26
|
+
* same visible fields — `{ kind: 'axiom.migration-authority', grantedBy: 'operator' }` — or a
|
|
27
|
+
* spread copy `{ ...migrationAuthority('operator') }` is **not** in the set and is rejected.
|
|
28
|
+
* `grantedBy` remains visible as a descriptive audit label; it is not the source of
|
|
29
|
+
* authorization.
|
|
30
|
+
*
|
|
31
|
+
* The capability is process-local (spec11.1 §19) and is never serialized (spec11.1 §18):
|
|
32
|
+
* migration durability lives in the `MigrationMetadataStore`, the lease and the checkpoints,
|
|
33
|
+
* not in a portable token.
|
|
23
34
|
*/
|
|
24
35
|
export interface MigrationPrincipal {
|
|
25
36
|
readonly kind: 'axiom.migration-authority';
|
|
26
|
-
/** A
|
|
37
|
+
/** A descriptive audit label — an operator id, a deploy job. Not the source of authorization. */
|
|
27
38
|
readonly grantedBy: string;
|
|
28
39
|
}
|
|
29
|
-
/**
|
|
40
|
+
/** Mint migration authority. Only a host calls this; the result cannot be reconstructed by shape. */
|
|
30
41
|
export declare function migrationAuthority(grantedBy: string): MigrationPrincipal;
|
|
42
|
+
/** True only for the exact object `migrationAuthority()` returned — provenance, not duck typing. */
|
|
31
43
|
export declare function isMigrationPrincipal(value: unknown): value is MigrationPrincipal;
|
|
32
44
|
export interface ExecuteMigrationOptions {
|
|
33
45
|
ir: ServerIR;
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { MIGRATION_DIAGNOSTIC_CODES } from './migration.js';
|
|
2
2
|
import { planMigration } from './migration.js';
|
|
3
|
-
import { runMigration } from './migration-executor.js';
|
|
3
|
+
import { classifyMigrationContention, runMigration } from './migration-executor.js';
|
|
4
4
|
import { evaluateSchemaGate } from './migration-gate.js';
|
|
5
|
-
|
|
5
|
+
import { isSqliteContentionError } from './sqlite-contention.js';
|
|
6
|
+
/** Process-private registry of genuine, host-minted migration capabilities. */
|
|
7
|
+
const MINTED_AUTHORITIES = new WeakSet();
|
|
8
|
+
/** Mint migration authority. Only a host calls this; the result cannot be reconstructed by shape. */
|
|
6
9
|
export function migrationAuthority(grantedBy) {
|
|
7
|
-
|
|
10
|
+
const principal = Object.freeze({
|
|
11
|
+
kind: 'axiom.migration-authority',
|
|
12
|
+
grantedBy: String(grantedBy),
|
|
13
|
+
});
|
|
14
|
+
MINTED_AUTHORITIES.add(principal);
|
|
15
|
+
return principal;
|
|
8
16
|
}
|
|
17
|
+
/** True only for the exact object `migrationAuthority()` returned — provenance, not duck typing. */
|
|
9
18
|
export function isMigrationPrincipal(value) {
|
|
10
|
-
return
|
|
11
|
-
value !== null &&
|
|
12
|
-
value.kind === 'axiom.migration-authority' &&
|
|
13
|
-
typeof value.grantedBy === 'string');
|
|
19
|
+
return typeof value === 'object' && value !== null && MINTED_AUTHORITIES.has(value);
|
|
14
20
|
}
|
|
15
21
|
export async function executeMigration(options) {
|
|
16
22
|
if (!isMigrationPrincipal(options.principal)) {
|
|
@@ -21,7 +27,28 @@ export async function executeMigration(options) {
|
|
|
21
27
|
};
|
|
22
28
|
}
|
|
23
29
|
const requiredVersion = options.ir.schemaVersion ?? 1;
|
|
24
|
-
|
|
30
|
+
// `executeMigration` itself never holds the migration lease — `runMigration` does — so a
|
|
31
|
+
// SQLite lock this orchestrator hits is always another runner establishing/holding
|
|
32
|
+
// ownership (spec11.2 §6, §15). Reconcile it to a semantic outcome, never a leaked
|
|
33
|
+
// provider error.
|
|
34
|
+
let record;
|
|
35
|
+
try {
|
|
36
|
+
record = await options.metadata.readSchema();
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (!isSqliteContentionError(error))
|
|
40
|
+
throw error;
|
|
41
|
+
const resolution = await classifyMigrationContention(options.metadata, requiredVersion, undefined, error);
|
|
42
|
+
if (resolution.kind === 'in-progress') {
|
|
43
|
+
return { ok: false, code: MIGRATION_DIAGNOSTIC_CODES.MIGRATION_IN_PROGRESS, message: resolution.message };
|
|
44
|
+
}
|
|
45
|
+
if (resolution.kind === 'failed') {
|
|
46
|
+
return { ok: false, code: MIGRATION_DIAGNOSTIC_CODES.MIGRATION_FAILED, message: resolution.message };
|
|
47
|
+
}
|
|
48
|
+
// already-at-target: a competing runner won the race. Re-read once — it succeeds now
|
|
49
|
+
// that the winner has released the writer lock — and fall through to the no-op path.
|
|
50
|
+
record = await options.metadata.readSchema();
|
|
51
|
+
}
|
|
25
52
|
const fromVersion = options.fromVersion ?? record?.schemaVersion ?? 1;
|
|
26
53
|
if (fromVersion === requiredVersion) {
|
|
27
54
|
// Nothing to do — surface it as success with an empty plan.
|
|
@@ -56,9 +83,34 @@ export async function executeMigration(options) {
|
|
|
56
83
|
return { ok: true, plan: planned.plan, run, gate };
|
|
57
84
|
}
|
|
58
85
|
export async function getMigrationStatus(metadata) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
86
|
+
// Host inspection must stay usable during a concurrent migration — a consumer may not be
|
|
87
|
+
// required to catch SQLite-native errors to read migration state (spec11.2 §12, §32).
|
|
88
|
+
// After the store's own bounded busy handling, residual contention on the
|
|
89
|
+
// `_axiom_migration_*` tables means a migration is active, so a read that still contends
|
|
90
|
+
// is reported as a coherent in-progress status rather than a thrown provider error.
|
|
91
|
+
let contended = false;
|
|
92
|
+
const read = async (fn) => {
|
|
93
|
+
try {
|
|
94
|
+
return await fn();
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (isSqliteContentionError(error)) {
|
|
98
|
+
contended = true;
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const record = await read(() => metadata.readSchema());
|
|
105
|
+
const lock = await read(() => metadata.readLock());
|
|
106
|
+
const checkpoint = await read(() => metadata.readCheckpoint());
|
|
107
|
+
const phase = lock
|
|
108
|
+
? 'in-progress'
|
|
109
|
+
: contended
|
|
110
|
+
? 'in-progress'
|
|
111
|
+
: checkpoint
|
|
112
|
+
? 'checkpointed'
|
|
113
|
+
: 'idle';
|
|
62
114
|
return {
|
|
63
115
|
schemaVersion: record?.schemaVersion ?? null,
|
|
64
116
|
schemaFingerprint: record?.schemaFingerprint ?? null,
|
|
@@ -77,6 +129,6 @@ export async function getMigrationStatus(metadata) {
|
|
|
77
129
|
batchCursor: checkpoint.batchCursor,
|
|
78
130
|
}
|
|
79
131
|
: null,
|
|
80
|
-
phase
|
|
132
|
+
phase,
|
|
81
133
|
};
|
|
82
134
|
}
|
|
@@ -57,5 +57,29 @@ export type RunMigrationResult = {
|
|
|
57
57
|
code: MigrationDiagnosticCode;
|
|
58
58
|
message: string;
|
|
59
59
|
};
|
|
60
|
+
/**
|
|
61
|
+
* A physical SQLite lock is not a semantic result (spec11.2 §5, §15). When a metadata or
|
|
62
|
+
* row-store call raises {@link SqliteContentionError} during a migration, this re-reads the
|
|
63
|
+
* migration metadata and answers in Axiom terms:
|
|
64
|
+
*
|
|
65
|
+
* - the persisted schema is already at (or past) the target → the race is already won →
|
|
66
|
+
* `already-at-target`;
|
|
67
|
+
* - a valid lease is held by *someone else* → `in-progress`;
|
|
68
|
+
* - this runner holds the lease and SQLite is still contended → an unrelated writer, not a
|
|
69
|
+
* migration owner → `failed`, with the physical cause retained (spec11.2 §35);
|
|
70
|
+
* - the metadata itself cannot be observed → only migration runners contend those tables, so
|
|
71
|
+
* without a lease of our own that means another migration is active → `in-progress`
|
|
72
|
+
* (spec11.2 §8, §11).
|
|
73
|
+
*/
|
|
74
|
+
export type ContentionResolution = {
|
|
75
|
+
kind: 'already-at-target';
|
|
76
|
+
} | {
|
|
77
|
+
kind: 'in-progress';
|
|
78
|
+
message: string;
|
|
79
|
+
} | {
|
|
80
|
+
kind: 'failed';
|
|
81
|
+
message: string;
|
|
82
|
+
};
|
|
83
|
+
export declare function classifyMigrationContention(metadata: MigrationMetadataStore, targetVersion: number, ownToken: string | undefined, error: unknown): Promise<ContentionResolution>;
|
|
60
84
|
export declare function runMigration(ir: ServerIR, plan: SemanticMigrationPlan, metadata: MigrationMetadataStore, target: MigrationRowStore | MigrationDataset, options: RunMigrationOptions): Promise<RunMigrationResult>;
|
|
61
85
|
//# sourceMappingURL=migration-executor.d.ts.map
|