@kontextmind/kxm 0.7.45 → 0.7.47
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/.claude-plugin/marketplace.json +1 -1
- package/docs/kb/qa-authentik-authentication.md +17 -0
- package/docs/kb/qa-extension-install-and-hub-bootstrap.md +17 -0
- package/docs/kb/qa-hub-on-a-public-host.md +17 -0
- package/docs/kb/qa-sqlite-vs-duckdb.md +17 -0
- package/docs/kb/qa-what-the-hub-stores.md +17 -0
- package/docs/operations.md +9 -1
- package/docs/troubleshooting.md +1 -1
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +118 -1
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +327 -2
- package/plugins/kxm/dist/runtime.js +329 -3
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/database.ts +4 -1
- package/plugins/kxm/src/intake.ts +536 -0
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/project-config.ts +44 -0
- package/plugins/kxm/src/runtime-store.ts +390 -2
- package/schemas/README.md +2 -0
- package/schemas/coordinator.schema.json +54 -0
- package/schemas/intake-message.schema.json +72 -0
|
@@ -16432,6 +16432,8 @@ var KxmSchemaRegistry = class {
|
|
|
16432
16432
|
permissionDiffValidator;
|
|
16433
16433
|
runEventValidator;
|
|
16434
16434
|
driveReceiptValidator;
|
|
16435
|
+
coordinatorValidator;
|
|
16436
|
+
intakeMessageValidator;
|
|
16435
16437
|
constructor(schemasDir = DEFAULT_SCHEMA_DIR) {
|
|
16436
16438
|
this.schemasDir = resolve(schemasDir);
|
|
16437
16439
|
this.ajv = new import__.Ajv2020({ allErrors: true, strict: true, strictRequired: false });
|
|
@@ -16449,6 +16451,8 @@ var KxmSchemaRegistry = class {
|
|
|
16449
16451
|
const permissionDiffFile = "permission-diff.schema.json";
|
|
16450
16452
|
const runEventFile = "run-event.schema.json";
|
|
16451
16453
|
const driveReceiptFile = "drive-receipt.schema.json";
|
|
16454
|
+
const coordinatorFile = "coordinator.schema.json";
|
|
16455
|
+
const intakeMessageFile = "intake-message.schema.json";
|
|
16452
16456
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, localBindingsFile)));
|
|
16453
16457
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, templateProvenanceFile)));
|
|
16454
16458
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, initOperationFile)));
|
|
@@ -16458,6 +16462,8 @@ var KxmSchemaRegistry = class {
|
|
|
16458
16462
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, permissionDiffFile)));
|
|
16459
16463
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, runEventFile)));
|
|
16460
16464
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, driveReceiptFile)));
|
|
16465
|
+
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, coordinatorFile)));
|
|
16466
|
+
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, intakeMessageFile)));
|
|
16461
16467
|
for (const [kind, definition] of Object.entries(RESOURCE_SCHEMA)) {
|
|
16462
16468
|
const validator = this.ajv.getSchema(`https://schemas.kxm.dev/${definition.file}`);
|
|
16463
16469
|
if (!validator) throw new Error(`schema did not compile: ${definition.file}`);
|
|
@@ -16472,6 +16478,8 @@ var KxmSchemaRegistry = class {
|
|
|
16472
16478
|
const permissionDiffValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${permissionDiffFile}`);
|
|
16473
16479
|
const runEventValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${runEventFile}`);
|
|
16474
16480
|
const driveReceiptValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${driveReceiptFile}`);
|
|
16481
|
+
const coordinatorValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${coordinatorFile}`);
|
|
16482
|
+
const intakeMessageValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${intakeMessageFile}`);
|
|
16475
16483
|
if (!localBindingsValidator) throw new Error(`schema did not compile: ${localBindingsFile}`);
|
|
16476
16484
|
if (!templateProvenanceValidator) throw new Error(`schema did not compile: ${templateProvenanceFile}`);
|
|
16477
16485
|
if (!initOperationValidator) throw new Error(`schema did not compile: ${initOperationFile}`);
|
|
@@ -16481,6 +16489,8 @@ var KxmSchemaRegistry = class {
|
|
|
16481
16489
|
if (!permissionDiffValidator) throw new Error(`schema did not compile: ${permissionDiffFile}`);
|
|
16482
16490
|
if (!runEventValidator) throw new Error(`schema did not compile: ${runEventFile}`);
|
|
16483
16491
|
if (!driveReceiptValidator) throw new Error(`schema did not compile: ${driveReceiptFile}`);
|
|
16492
|
+
if (!coordinatorValidator) throw new Error(`schema did not compile: ${coordinatorFile}`);
|
|
16493
|
+
if (!intakeMessageValidator) throw new Error(`schema did not compile: ${intakeMessageFile}`);
|
|
16484
16494
|
this.localBindingsValidator = localBindingsValidator;
|
|
16485
16495
|
this.templateProvenanceValidator = templateProvenanceValidator;
|
|
16486
16496
|
this.initOperationValidator = initOperationValidator;
|
|
@@ -16490,6 +16500,8 @@ var KxmSchemaRegistry = class {
|
|
|
16490
16500
|
this.permissionDiffValidator = permissionDiffValidator;
|
|
16491
16501
|
this.runEventValidator = runEventValidator;
|
|
16492
16502
|
this.driveReceiptValidator = driveReceiptValidator;
|
|
16503
|
+
this.coordinatorValidator = coordinatorValidator;
|
|
16504
|
+
this.intakeMessageValidator = intakeMessageValidator;
|
|
16493
16505
|
}
|
|
16494
16506
|
validate(kind, value, file) {
|
|
16495
16507
|
const definition = RESOURCE_SCHEMA[kind];
|
|
@@ -16564,6 +16576,34 @@ function validateDriveReceipt(value, file) {
|
|
|
16564
16576
|
)]);
|
|
16565
16577
|
}
|
|
16566
16578
|
}
|
|
16579
|
+
function validateCoordinator(value, file) {
|
|
16580
|
+
const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
|
|
16581
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.coordinator.v1") {
|
|
16582
|
+
throw new KxmConfigError([issue2("schema", "coordinator_invalid", file, "expected kxm.coordinator.v1")]);
|
|
16583
|
+
}
|
|
16584
|
+
if (!registry.coordinatorValidator(value)) {
|
|
16585
|
+
throw new KxmConfigError([issue2(
|
|
16586
|
+
"schema",
|
|
16587
|
+
"coordinator_invalid",
|
|
16588
|
+
file,
|
|
16589
|
+
registry.ajv.errorsText(registry.coordinatorValidator.errors, { separator: "; " })
|
|
16590
|
+
)]);
|
|
16591
|
+
}
|
|
16592
|
+
}
|
|
16593
|
+
function validateIntakeMessage(value, file) {
|
|
16594
|
+
const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
|
|
16595
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.intake-message.v1") {
|
|
16596
|
+
throw new KxmConfigError([issue2("schema", "intake_message_invalid", file, "expected kxm.intake-message.v1")]);
|
|
16597
|
+
}
|
|
16598
|
+
if (!registry.intakeMessageValidator(value)) {
|
|
16599
|
+
throw new KxmConfigError([issue2(
|
|
16600
|
+
"schema",
|
|
16601
|
+
"intake_message_invalid",
|
|
16602
|
+
file,
|
|
16603
|
+
registry.ajv.errorsText(registry.intakeMessageValidator.errors, { separator: "; " })
|
|
16604
|
+
)]);
|
|
16605
|
+
}
|
|
16606
|
+
}
|
|
16567
16607
|
function schemaIssue(file, error) {
|
|
16568
16608
|
const location = error.instancePath || "/";
|
|
16569
16609
|
const suffix = error.params && "additionalProperty" in error.params ? ` (${String(error.params.additionalProperty)})` : "";
|
|
@@ -18042,7 +18082,7 @@ function restoreBackup(manifestPathOrDir, options = {}) {
|
|
|
18042
18082
|
if (store.storeId === "registry" || store.storeId === "binding-store") {
|
|
18043
18083
|
maxSupported = 1;
|
|
18044
18084
|
} else if (store.storeId.startsWith("events:")) {
|
|
18045
|
-
maxSupported =
|
|
18085
|
+
maxSupported = 5;
|
|
18046
18086
|
}
|
|
18047
18087
|
let targetPath = store.sourcePath;
|
|
18048
18088
|
if (options.projectRoot && manifest.projectRoot && targetPath.startsWith(manifest.projectRoot)) {
|
|
@@ -18312,7 +18352,7 @@ function verifyKxmDriveReceipt(receipt, events, foldedStatus, binding) {
|
|
|
18312
18352
|
if (reasons.length === 0) return { verified: true };
|
|
18313
18353
|
return { verified: false, divergence: reasons.join("; ") };
|
|
18314
18354
|
}
|
|
18315
|
-
var KXM_EVENT_STORE_SCHEMA_VERSION =
|
|
18355
|
+
var KXM_EVENT_STORE_SCHEMA_VERSION = 5;
|
|
18316
18356
|
var KXM_DRIVE_RECEIPT_SCHEMA = "kxm.drive-receipt.v1";
|
|
18317
18357
|
var DRIVE_RECEIPT_MAX_BYTES = 8 * 1024;
|
|
18318
18358
|
var EVENT_STORE_TABLES = {
|
|
@@ -18394,8 +18434,32 @@ var EVENT_STORE_TABLES = {
|
|
|
18394
18434
|
"settled_event_id",
|
|
18395
18435
|
"content_hash"
|
|
18396
18436
|
],
|
|
18397
|
-
drive_receipts: ["drive_id", "run_id", "project_id", "opened_sequence", "last_sequence", "closed_at", "schema", "receipt"]
|
|
18437
|
+
drive_receipts: ["drive_id", "run_id", "project_id", "opened_sequence", "last_sequence", "closed_at", "schema", "receipt"],
|
|
18438
|
+
coordinators: [
|
|
18439
|
+
"coordinator_id",
|
|
18440
|
+
"project_id",
|
|
18441
|
+
"role",
|
|
18442
|
+
"channel",
|
|
18443
|
+
"ceiling_hash",
|
|
18444
|
+
"config_revision",
|
|
18445
|
+
"bound_at",
|
|
18446
|
+
"schema",
|
|
18447
|
+
"record"
|
|
18448
|
+
],
|
|
18449
|
+
intake_messages: [
|
|
18450
|
+
"message_id",
|
|
18451
|
+
"project_id",
|
|
18452
|
+
"coordinator_id",
|
|
18453
|
+
"idempotency_key",
|
|
18454
|
+
"content_hash",
|
|
18455
|
+
"received_at",
|
|
18456
|
+
"dispatch_state",
|
|
18457
|
+
"schema",
|
|
18458
|
+
"record"
|
|
18459
|
+
],
|
|
18460
|
+
project_controls: ["project_id", "paused", "reason", "updated_at", "actor", "schema", "record"]
|
|
18398
18461
|
};
|
|
18462
|
+
var KXM_EVENT_STORE_TABLE_NAMES = Object.keys(EVENT_STORE_TABLES).sort();
|
|
18399
18463
|
var EVENT_STORE_SCHEMA = `
|
|
18400
18464
|
CREATE TABLE runs (
|
|
18401
18465
|
run_id TEXT PRIMARY KEY,
|
|
@@ -18547,6 +18611,76 @@ CREATE TABLE drive_receipts (
|
|
|
18547
18611
|
receipt TEXT NOT NULL
|
|
18548
18612
|
) STRICT;
|
|
18549
18613
|
CREATE INDEX drive_receipts_run ON drive_receipts(run_id);
|
|
18614
|
+
CREATE TABLE coordinators (
|
|
18615
|
+
coordinator_id TEXT PRIMARY KEY,
|
|
18616
|
+
project_id TEXT NOT NULL,
|
|
18617
|
+
role TEXT NOT NULL,
|
|
18618
|
+
channel TEXT NOT NULL,
|
|
18619
|
+
ceiling_hash TEXT NOT NULL,
|
|
18620
|
+
config_revision TEXT NOT NULL,
|
|
18621
|
+
bound_at TEXT NOT NULL,
|
|
18622
|
+
schema TEXT NOT NULL,
|
|
18623
|
+
record TEXT NOT NULL
|
|
18624
|
+
) STRICT;
|
|
18625
|
+
CREATE UNIQUE INDEX coordinators_slot ON coordinators(project_id, role, channel);
|
|
18626
|
+
CREATE TABLE intake_messages (
|
|
18627
|
+
message_id TEXT PRIMARY KEY,
|
|
18628
|
+
project_id TEXT NOT NULL,
|
|
18629
|
+
coordinator_id TEXT NOT NULL,
|
|
18630
|
+
idempotency_key TEXT NOT NULL,
|
|
18631
|
+
content_hash TEXT NOT NULL,
|
|
18632
|
+
received_at TEXT NOT NULL,
|
|
18633
|
+
dispatch_state TEXT NOT NULL CHECK (dispatch_state IN ('ready','held_paused','admitted','refused')),
|
|
18634
|
+
schema TEXT NOT NULL,
|
|
18635
|
+
record TEXT NOT NULL
|
|
18636
|
+
) STRICT;
|
|
18637
|
+
CREATE UNIQUE INDEX intake_idempotency ON intake_messages(project_id, coordinator_id, idempotency_key);
|
|
18638
|
+
CREATE INDEX intake_dispatch ON intake_messages(project_id, dispatch_state, received_at, message_id);
|
|
18639
|
+
CREATE TABLE project_controls (
|
|
18640
|
+
project_id TEXT PRIMARY KEY,
|
|
18641
|
+
paused INTEGER NOT NULL CHECK (paused IN (0, 1)),
|
|
18642
|
+
reason TEXT,
|
|
18643
|
+
updated_at TEXT NOT NULL,
|
|
18644
|
+
actor TEXT NOT NULL,
|
|
18645
|
+
schema TEXT NOT NULL,
|
|
18646
|
+
record TEXT NOT NULL
|
|
18647
|
+
) STRICT;
|
|
18648
|
+
`;
|
|
18649
|
+
var COORDINATOR_INTAKE_DDL = `
|
|
18650
|
+
CREATE TABLE IF NOT EXISTS coordinators (
|
|
18651
|
+
coordinator_id TEXT PRIMARY KEY,
|
|
18652
|
+
project_id TEXT NOT NULL,
|
|
18653
|
+
role TEXT NOT NULL,
|
|
18654
|
+
channel TEXT NOT NULL,
|
|
18655
|
+
ceiling_hash TEXT NOT NULL,
|
|
18656
|
+
config_revision TEXT NOT NULL,
|
|
18657
|
+
bound_at TEXT NOT NULL,
|
|
18658
|
+
schema TEXT NOT NULL,
|
|
18659
|
+
record TEXT NOT NULL
|
|
18660
|
+
) STRICT;
|
|
18661
|
+
CREATE UNIQUE INDEX IF NOT EXISTS coordinators_slot ON coordinators(project_id, role, channel);
|
|
18662
|
+
CREATE TABLE IF NOT EXISTS intake_messages (
|
|
18663
|
+
message_id TEXT PRIMARY KEY,
|
|
18664
|
+
project_id TEXT NOT NULL,
|
|
18665
|
+
coordinator_id TEXT NOT NULL,
|
|
18666
|
+
idempotency_key TEXT NOT NULL,
|
|
18667
|
+
content_hash TEXT NOT NULL,
|
|
18668
|
+
received_at TEXT NOT NULL,
|
|
18669
|
+
dispatch_state TEXT NOT NULL CHECK (dispatch_state IN ('ready','held_paused','admitted','refused')),
|
|
18670
|
+
schema TEXT NOT NULL,
|
|
18671
|
+
record TEXT NOT NULL
|
|
18672
|
+
) STRICT;
|
|
18673
|
+
CREATE UNIQUE INDEX IF NOT EXISTS intake_idempotency ON intake_messages(project_id, coordinator_id, idempotency_key);
|
|
18674
|
+
CREATE INDEX IF NOT EXISTS intake_dispatch ON intake_messages(project_id, dispatch_state, received_at, message_id);
|
|
18675
|
+
CREATE TABLE IF NOT EXISTS project_controls (
|
|
18676
|
+
project_id TEXT PRIMARY KEY,
|
|
18677
|
+
paused INTEGER NOT NULL CHECK (paused IN (0, 1)),
|
|
18678
|
+
reason TEXT,
|
|
18679
|
+
updated_at TEXT NOT NULL,
|
|
18680
|
+
actor TEXT NOT NULL,
|
|
18681
|
+
schema TEXT NOT NULL,
|
|
18682
|
+
record TEXT NOT NULL
|
|
18683
|
+
) STRICT;
|
|
18550
18684
|
`;
|
|
18551
18685
|
var DRIVE_RECEIPTS_DDL = `
|
|
18552
18686
|
CREATE TABLE IF NOT EXISTS drive_receipts (
|
|
@@ -18568,6 +18702,13 @@ var EVENT_STORE_MIGRATIONS = [
|
|
|
18568
18702
|
migrate(database) {
|
|
18569
18703
|
database.exec(DRIVE_RECEIPTS_DDL);
|
|
18570
18704
|
}
|
|
18705
|
+
},
|
|
18706
|
+
{
|
|
18707
|
+
fromVersion: 4,
|
|
18708
|
+
toVersion: 5,
|
|
18709
|
+
migrate(database) {
|
|
18710
|
+
database.exec(COORDINATOR_INTAKE_DDL);
|
|
18711
|
+
}
|
|
18571
18712
|
}
|
|
18572
18713
|
];
|
|
18573
18714
|
var KxmRunEventStore = class {
|
|
@@ -18774,6 +18915,134 @@ var KxmRunEventStore = class {
|
|
|
18774
18915
|
}
|
|
18775
18916
|
});
|
|
18776
18917
|
}
|
|
18918
|
+
/** Insert a coordinator unless the (project, role, channel) slot is taken. */
|
|
18919
|
+
insertCoordinatorIfAbsent(row) {
|
|
18920
|
+
const result = this.database.prepare(`
|
|
18921
|
+
INSERT OR IGNORE INTO coordinators
|
|
18922
|
+
(coordinator_id, project_id, role, channel, ceiling_hash, config_revision, bound_at, schema, record)
|
|
18923
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
18924
|
+
`).run(
|
|
18925
|
+
row.coordinatorId,
|
|
18926
|
+
row.projectId,
|
|
18927
|
+
row.role,
|
|
18928
|
+
row.channel,
|
|
18929
|
+
row.ceilingHash,
|
|
18930
|
+
row.configRevision,
|
|
18931
|
+
row.boundAt,
|
|
18932
|
+
"kxm.coordinator.v1",
|
|
18933
|
+
row.record
|
|
18934
|
+
);
|
|
18935
|
+
return Number(result.changes) === 1;
|
|
18936
|
+
}
|
|
18937
|
+
coordinatorById(coordinatorId) {
|
|
18938
|
+
const row = this.database.prepare("SELECT * FROM coordinators WHERE coordinator_id = ?").get(coordinatorId);
|
|
18939
|
+
return row ? coordinatorFromRow(row) : void 0;
|
|
18940
|
+
}
|
|
18941
|
+
coordinatorInSlot(projectId, role, channel) {
|
|
18942
|
+
const row = this.database.prepare(`
|
|
18943
|
+
SELECT * FROM coordinators WHERE project_id = ? AND role = ? AND channel = ?
|
|
18944
|
+
`).get(projectId, role, channel);
|
|
18945
|
+
return row ? coordinatorFromRow(row) : void 0;
|
|
18946
|
+
}
|
|
18947
|
+
/**
|
|
18948
|
+
* Replace the coordinator holding a (project, role, channel) slot, guarded by
|
|
18949
|
+
* the identity that is currently there. A rebind therefore cannot clobber a
|
|
18950
|
+
* record that changed underneath it, and cannot leave two live identities for
|
|
18951
|
+
* one slot.
|
|
18952
|
+
*/
|
|
18953
|
+
replaceCoordinatorInSlot(expectedCoordinatorId, row) {
|
|
18954
|
+
const result = this.database.prepare(`
|
|
18955
|
+
UPDATE coordinators
|
|
18956
|
+
SET coordinator_id = ?, ceiling_hash = ?, config_revision = ?, bound_at = ?, schema = ?, record = ?
|
|
18957
|
+
WHERE project_id = ? AND role = ? AND channel = ? AND coordinator_id = ?
|
|
18958
|
+
`).run(
|
|
18959
|
+
row.coordinatorId,
|
|
18960
|
+
row.ceilingHash,
|
|
18961
|
+
row.configRevision,
|
|
18962
|
+
row.boundAt,
|
|
18963
|
+
"kxm.coordinator.v1",
|
|
18964
|
+
row.record,
|
|
18965
|
+
row.projectId,
|
|
18966
|
+
row.role,
|
|
18967
|
+
row.channel,
|
|
18968
|
+
expectedCoordinatorId
|
|
18969
|
+
);
|
|
18970
|
+
return Number(result.changes) === 1;
|
|
18971
|
+
}
|
|
18972
|
+
/** Insert an intake message unless its idempotency slot is taken. */
|
|
18973
|
+
insertIntakeMessageIfAbsent(row) {
|
|
18974
|
+
const result = this.database.prepare(`
|
|
18975
|
+
INSERT OR IGNORE INTO intake_messages
|
|
18976
|
+
(message_id, project_id, coordinator_id, idempotency_key, content_hash, received_at, dispatch_state, schema, record)
|
|
18977
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
18978
|
+
`).run(
|
|
18979
|
+
row.messageId,
|
|
18980
|
+
row.projectId,
|
|
18981
|
+
row.coordinatorId,
|
|
18982
|
+
row.idempotencyKey,
|
|
18983
|
+
row.contentHash,
|
|
18984
|
+
row.receivedAt,
|
|
18985
|
+
row.dispatchState,
|
|
18986
|
+
"kxm.intake-message.v1",
|
|
18987
|
+
row.record
|
|
18988
|
+
);
|
|
18989
|
+
return Number(result.changes) === 1;
|
|
18990
|
+
}
|
|
18991
|
+
intakeMessage(messageId) {
|
|
18992
|
+
const row = this.database.prepare("SELECT * FROM intake_messages WHERE message_id = ?").get(messageId);
|
|
18993
|
+
return row ? intakeFromRow(row) : void 0;
|
|
18994
|
+
}
|
|
18995
|
+
intakeBySlot(projectId, coordinatorId, idempotencyKey) {
|
|
18996
|
+
const row = this.database.prepare(`
|
|
18997
|
+
SELECT * FROM intake_messages WHERE project_id = ? AND coordinator_id = ? AND idempotency_key = ?
|
|
18998
|
+
`).get(projectId, coordinatorId, idempotencyKey);
|
|
18999
|
+
return row ? intakeFromRow(row) : void 0;
|
|
19000
|
+
}
|
|
19001
|
+
/** Intake rows in the given dispatch states, oldest first (stable, replay-safe order). */
|
|
19002
|
+
intakeInStates(projectId, states, limit = 100) {
|
|
19003
|
+
if (states.length === 0) return [];
|
|
19004
|
+
const placeholders = states.map(() => "?").join(", ");
|
|
19005
|
+
const rows = this.database.prepare(`
|
|
19006
|
+
SELECT * FROM intake_messages
|
|
19007
|
+
WHERE project_id = ? AND dispatch_state IN (${placeholders})
|
|
19008
|
+
ORDER BY received_at ASC, message_id ASC
|
|
19009
|
+
LIMIT ?
|
|
19010
|
+
`).all(projectId, ...states, limit);
|
|
19011
|
+
return rows.map(intakeFromRow);
|
|
19012
|
+
}
|
|
19013
|
+
/** Rewrite one intake row's dispatch state and record. Returns false when it raced away. */
|
|
19014
|
+
updateIntakeDispatch(messageId, expectState, next) {
|
|
19015
|
+
const result = this.database.prepare(`
|
|
19016
|
+
UPDATE intake_messages SET dispatch_state = ?, record = ?, schema = ?
|
|
19017
|
+
WHERE message_id = ? AND dispatch_state = ?
|
|
19018
|
+
`).run(next.state, next.record, "kxm.intake-message.v1", messageId, expectState);
|
|
19019
|
+
return Number(result.changes) === 1;
|
|
19020
|
+
}
|
|
19021
|
+
projectControl(projectId) {
|
|
19022
|
+
const row = this.database.prepare("SELECT * FROM project_controls WHERE project_id = ?").get(projectId);
|
|
19023
|
+
return row ? controlFromRow(row) : void 0;
|
|
19024
|
+
}
|
|
19025
|
+
putProjectControl(row) {
|
|
19026
|
+
this.database.prepare(`
|
|
19027
|
+
INSERT INTO project_controls (project_id, paused, reason, updated_at, actor, schema, record)
|
|
19028
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
19029
|
+
ON CONFLICT(project_id) DO UPDATE SET
|
|
19030
|
+
paused = excluded.paused,
|
|
19031
|
+
reason = excluded.reason,
|
|
19032
|
+
updated_at = excluded.updated_at,
|
|
19033
|
+
actor = excluded.actor,
|
|
19034
|
+
schema = excluded.schema,
|
|
19035
|
+
record = excluded.record
|
|
19036
|
+
`).run(
|
|
19037
|
+
row.projectId,
|
|
19038
|
+
row.paused ? 1 : 0,
|
|
19039
|
+
row.reason ?? null,
|
|
19040
|
+
row.updatedAt,
|
|
19041
|
+
row.actor,
|
|
19042
|
+
"kxm.project-control.v1",
|
|
19043
|
+
row.record
|
|
19044
|
+
);
|
|
19045
|
+
}
|
|
18777
19046
|
insertCapability(row) {
|
|
18778
19047
|
this.database.prepare(`
|
|
18779
19048
|
INSERT INTO attempt_capabilities (attempt_id, run_id, assignment_id, step_id, step_attempt, producer_id, capability_hash, state)
|
|
@@ -19008,6 +19277,62 @@ var KxmRunEventStore = class {
|
|
|
19008
19277
|
}
|
|
19009
19278
|
}
|
|
19010
19279
|
};
|
|
19280
|
+
function coordinatorFromRow(row) {
|
|
19281
|
+
const parsed = JSON.parse(row.record);
|
|
19282
|
+
validateCoordinator(parsed, row.coordinator_id);
|
|
19283
|
+
if (parsed.coordinatorId !== row.coordinator_id || parsed.ceilingHash !== row.ceiling_hash || parsed.configRevision !== row.config_revision || parsed.boundAt !== row.bound_at) {
|
|
19284
|
+
throw runtimeError("coordinator_record_divergent", row.coordinator_id, "coordinator columns do not match the persisted record");
|
|
19285
|
+
}
|
|
19286
|
+
return {
|
|
19287
|
+
coordinatorId: row.coordinator_id,
|
|
19288
|
+
projectId: row.project_id,
|
|
19289
|
+
role: row.role,
|
|
19290
|
+
channel: row.channel,
|
|
19291
|
+
ceilingHash: row.ceiling_hash,
|
|
19292
|
+
configRevision: row.config_revision,
|
|
19293
|
+
boundAt: row.bound_at,
|
|
19294
|
+
record: row.record
|
|
19295
|
+
};
|
|
19296
|
+
}
|
|
19297
|
+
function intakeFromRow(row) {
|
|
19298
|
+
const parsed = JSON.parse(row.record);
|
|
19299
|
+
validateIntakeMessage(parsed, row.message_id);
|
|
19300
|
+
if (parsed.messageId !== row.message_id || parsed.contentHash !== row.content_hash || parsed.receivedAt !== row.received_at || parsed.dispatch?.state !== row.dispatch_state) {
|
|
19301
|
+
throw runtimeError("intake_record_divergent", row.message_id, "intake columns do not match the persisted record");
|
|
19302
|
+
}
|
|
19303
|
+
return {
|
|
19304
|
+
messageId: row.message_id,
|
|
19305
|
+
projectId: row.project_id,
|
|
19306
|
+
coordinatorId: row.coordinator_id,
|
|
19307
|
+
idempotencyKey: row.idempotency_key,
|
|
19308
|
+
contentHash: row.content_hash,
|
|
19309
|
+
receivedAt: row.received_at,
|
|
19310
|
+
dispatchState: row.dispatch_state,
|
|
19311
|
+
record: row.record
|
|
19312
|
+
};
|
|
19313
|
+
}
|
|
19314
|
+
function controlFromRow(row) {
|
|
19315
|
+
const paused = row.paused === 1;
|
|
19316
|
+
const expected = kxmCanonicalJson({
|
|
19317
|
+
schema: "kxm.project-control.v1",
|
|
19318
|
+
projectId: row.project_id,
|
|
19319
|
+
paused,
|
|
19320
|
+
...row.reason !== null ? { reason: row.reason } : {},
|
|
19321
|
+
updatedAt: row.updated_at,
|
|
19322
|
+
actor: row.actor
|
|
19323
|
+
});
|
|
19324
|
+
if (row.record !== expected) {
|
|
19325
|
+
throw runtimeError("project_control_divergent", row.project_id, "project control record does not match its columns");
|
|
19326
|
+
}
|
|
19327
|
+
return {
|
|
19328
|
+
projectId: row.project_id,
|
|
19329
|
+
paused,
|
|
19330
|
+
...row.reason !== null ? { reason: row.reason } : {},
|
|
19331
|
+
updatedAt: row.updated_at,
|
|
19332
|
+
actor: row.actor,
|
|
19333
|
+
record: row.record
|
|
19334
|
+
};
|
|
19335
|
+
}
|
|
19011
19336
|
function parseDriveReceipt(raw) {
|
|
19012
19337
|
const parsed = JSON.parse(raw);
|
|
19013
19338
|
validateDriveReceipt(parsed, "drive-receipt");
|
|
@@ -29940,6 +30265,7 @@ export {
|
|
|
29940
30265
|
KXM_ABSENT_MEMORY_REVISION,
|
|
29941
30266
|
KXM_DRIVE_RECEIPT_SCHEMA,
|
|
29942
30267
|
KXM_EVENT_STORE_SCHEMA_VERSION,
|
|
30268
|
+
KXM_EVENT_STORE_TABLE_NAMES,
|
|
29943
30269
|
KXM_REGISTRY_SCHEMA_VERSION,
|
|
29944
30270
|
KXM_RUN_EVENT_SCHEMA,
|
|
29945
30271
|
KxmRunEventStore,
|
package/plugins/kxm/package.json
CHANGED
|
@@ -538,7 +538,10 @@ export function restoreBackup(
|
|
|
538
538
|
if (store.storeId === "registry" || store.storeId === "binding-store") {
|
|
539
539
|
maxSupported = 1;
|
|
540
540
|
} else if (store.storeId.startsWith("events:")) {
|
|
541
|
-
|
|
541
|
+
// Must track KXM_EVENT_STORE_SCHEMA_VERSION in runtime-store.ts. The pin is
|
|
542
|
+
// the e6 backup/restore round-trip test: bump one without the other and it
|
|
543
|
+
// refuses its own fresh backup.
|
|
544
|
+
maxSupported = 5;
|
|
542
545
|
}
|
|
543
546
|
|
|
544
547
|
let targetPath = store.sourcePath;
|