@kontextmind/kxm 0.7.23 → 0.7.24
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/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +47 -5
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime.js +307 -7
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +300 -6
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/cli/vnext.ts +46 -3
- package/plugins/kxm/src/cli.ts +1 -1
- package/plugins/kxm/src/database.ts +3 -1
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/vnext-config.ts +31 -0
- package/plugins/kxm/src/vnext-engine-fold.ts +24 -0
- package/plugins/kxm/src/vnext-engine.ts +172 -2
- package/plugins/kxm/src/vnext-runtime-owner.ts +1 -0
- package/plugins/kxm/src/vnext-runtime-store.ts +151 -2
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +23 -2
- package/schemas/vnext/common.schema.json +1 -0
- package/schemas/vnext/drive-receipt.schema.json +79 -0
- package/schemas/vnext/run-event.schema.json +10 -0
|
@@ -16261,6 +16261,7 @@ var VnextSchemaRegistry = class {
|
|
|
16261
16261
|
migrationReceiptValidator;
|
|
16262
16262
|
permissionDiffValidator;
|
|
16263
16263
|
runEventValidator;
|
|
16264
|
+
driveReceiptValidator;
|
|
16264
16265
|
constructor(schemasDir = DEFAULT_SCHEMA_DIR) {
|
|
16265
16266
|
this.schemasDir = resolve(schemasDir);
|
|
16266
16267
|
this.ajv = new import__.Ajv2020({ allErrors: true, strict: true, strictRequired: false });
|
|
@@ -16277,6 +16278,7 @@ var VnextSchemaRegistry = class {
|
|
|
16277
16278
|
const migrationReceiptFile = "migration-receipt.schema.json";
|
|
16278
16279
|
const permissionDiffFile = "permission-diff.schema.json";
|
|
16279
16280
|
const runEventFile = "run-event.schema.json";
|
|
16281
|
+
const driveReceiptFile = "drive-receipt.schema.json";
|
|
16280
16282
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, localBindingsFile)));
|
|
16281
16283
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, templateProvenanceFile)));
|
|
16282
16284
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, initOperationFile)));
|
|
@@ -16285,6 +16287,7 @@ var VnextSchemaRegistry = class {
|
|
|
16285
16287
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, migrationReceiptFile)));
|
|
16286
16288
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, permissionDiffFile)));
|
|
16287
16289
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, runEventFile)));
|
|
16290
|
+
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, driveReceiptFile)));
|
|
16288
16291
|
for (const [kind, definition] of Object.entries(RESOURCE_SCHEMA)) {
|
|
16289
16292
|
const validator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${definition.file}`);
|
|
16290
16293
|
if (!validator) throw new Error(`schema did not compile: ${definition.file}`);
|
|
@@ -16298,6 +16301,7 @@ var VnextSchemaRegistry = class {
|
|
|
16298
16301
|
const migrationReceiptValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${migrationReceiptFile}`);
|
|
16299
16302
|
const permissionDiffValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${permissionDiffFile}`);
|
|
16300
16303
|
const runEventValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${runEventFile}`);
|
|
16304
|
+
const driveReceiptValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${driveReceiptFile}`);
|
|
16301
16305
|
if (!localBindingsValidator) throw new Error(`schema did not compile: ${localBindingsFile}`);
|
|
16302
16306
|
if (!templateProvenanceValidator) throw new Error(`schema did not compile: ${templateProvenanceFile}`);
|
|
16303
16307
|
if (!initOperationValidator) throw new Error(`schema did not compile: ${initOperationFile}`);
|
|
@@ -16306,6 +16310,7 @@ var VnextSchemaRegistry = class {
|
|
|
16306
16310
|
if (!migrationReceiptValidator) throw new Error(`schema did not compile: ${migrationReceiptFile}`);
|
|
16307
16311
|
if (!permissionDiffValidator) throw new Error(`schema did not compile: ${permissionDiffFile}`);
|
|
16308
16312
|
if (!runEventValidator) throw new Error(`schema did not compile: ${runEventFile}`);
|
|
16313
|
+
if (!driveReceiptValidator) throw new Error(`schema did not compile: ${driveReceiptFile}`);
|
|
16309
16314
|
this.localBindingsValidator = localBindingsValidator;
|
|
16310
16315
|
this.templateProvenanceValidator = templateProvenanceValidator;
|
|
16311
16316
|
this.initOperationValidator = initOperationValidator;
|
|
@@ -16314,6 +16319,7 @@ var VnextSchemaRegistry = class {
|
|
|
16314
16319
|
this.migrationReceiptValidator = migrationReceiptValidator;
|
|
16315
16320
|
this.permissionDiffValidator = permissionDiffValidator;
|
|
16316
16321
|
this.runEventValidator = runEventValidator;
|
|
16322
|
+
this.driveReceiptValidator = driveReceiptValidator;
|
|
16317
16323
|
}
|
|
16318
16324
|
validate(kind, value, file) {
|
|
16319
16325
|
const definition = RESOURCE_SCHEMA[kind];
|
|
@@ -16346,6 +16352,9 @@ var VnextSchemaRegistry = class {
|
|
|
16346
16352
|
validatePermissionDiff(value, file) {
|
|
16347
16353
|
return this.validateAuxiliary(value, file, "kxm.permission-diff.v1", this.permissionDiffValidator);
|
|
16348
16354
|
}
|
|
16355
|
+
validateDriveReceipt(value, file) {
|
|
16356
|
+
return this.validateAuxiliary(value, file, "kxm.drive-receipt.v1", this.driveReceiptValidator);
|
|
16357
|
+
}
|
|
16349
16358
|
validateAuxiliary(value, file, identity, validator) {
|
|
16350
16359
|
if (value.schema !== identity) {
|
|
16351
16360
|
return [issue2("schema", "schema_identity_mismatch", file, `expected ${identity}, received ${String(value.schema)}`)];
|
|
@@ -16366,6 +16375,25 @@ function validateRunEvent(value, file) {
|
|
|
16366
16375
|
)]);
|
|
16367
16376
|
}
|
|
16368
16377
|
}
|
|
16378
|
+
function validateDriveReceipt(value, file) {
|
|
16379
|
+
const registry = cachedRunEventRegistry ??= new VnextSchemaRegistry();
|
|
16380
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.drive-receipt.v1") {
|
|
16381
|
+
throw new VnextConfigError([issue2(
|
|
16382
|
+
"schema",
|
|
16383
|
+
"drive_receipt_invalid",
|
|
16384
|
+
file,
|
|
16385
|
+
"expected kxm.drive-receipt.v1"
|
|
16386
|
+
)]);
|
|
16387
|
+
}
|
|
16388
|
+
if (!registry.driveReceiptValidator(value)) {
|
|
16389
|
+
throw new VnextConfigError([issue2(
|
|
16390
|
+
"schema",
|
|
16391
|
+
"drive_receipt_invalid",
|
|
16392
|
+
file,
|
|
16393
|
+
registry.ajv.errorsText(registry.driveReceiptValidator.errors, { separator: "; " })
|
|
16394
|
+
)]);
|
|
16395
|
+
}
|
|
16396
|
+
}
|
|
16369
16397
|
function schemaIssue(file, error) {
|
|
16370
16398
|
const location = error.instancePath || "/";
|
|
16371
16399
|
const suffix = error.params && "additionalProperty" in error.params ? ` (${String(error.params.additionalProperty)})` : "";
|
|
@@ -17817,7 +17845,36 @@ var VnextRuntimeRegistry = class {
|
|
|
17817
17845
|
}
|
|
17818
17846
|
};
|
|
17819
17847
|
var VNEXT_RUN_EVENT_SCHEMA = "kxm.run-event.v1";
|
|
17820
|
-
|
|
17848
|
+
function hashVnextDriveLog(events) {
|
|
17849
|
+
const body = events.map((event) => `${event.eventId}:${event.sequence}`).join("\n");
|
|
17850
|
+
return `sha256:${createHash3("sha256").update(body, "utf8").digest("hex")}`;
|
|
17851
|
+
}
|
|
17852
|
+
function verifyVnextDriveReceipt(receipt, events, foldedStatus, binding) {
|
|
17853
|
+
const reasons = [];
|
|
17854
|
+
if (receipt.runId !== binding.runId) {
|
|
17855
|
+
reasons.push(`runId ${receipt.runId} != binding ${binding.runId}`);
|
|
17856
|
+
}
|
|
17857
|
+
if (receipt.driveId !== binding.driveId) {
|
|
17858
|
+
reasons.push(`driveId ${receipt.driveId} != binding ${binding.driveId}`);
|
|
17859
|
+
}
|
|
17860
|
+
const last = events[events.length - 1];
|
|
17861
|
+
const currentLast = last?.sequence ?? 0;
|
|
17862
|
+
if (receipt.lastSequence !== currentLast) {
|
|
17863
|
+
reasons.push(`lastSequence ${receipt.lastSequence} != log ${currentLast}`);
|
|
17864
|
+
}
|
|
17865
|
+
const prefix = events.filter((event) => event.sequence >= 1 && event.sequence <= receipt.lastSequence);
|
|
17866
|
+
if (hashVnextDriveLog(prefix) !== receipt.logHash) {
|
|
17867
|
+
reasons.push("logHash mismatch");
|
|
17868
|
+
}
|
|
17869
|
+
if (receipt.settlement.status !== foldedStatus) {
|
|
17870
|
+
reasons.push(`settlement.status ${receipt.settlement.status} != folded ${foldedStatus}`);
|
|
17871
|
+
}
|
|
17872
|
+
if (reasons.length === 0) return { verified: true };
|
|
17873
|
+
return { verified: false, divergence: reasons.join("; ") };
|
|
17874
|
+
}
|
|
17875
|
+
var VNEXT_EVENT_STORE_SCHEMA_VERSION = 4;
|
|
17876
|
+
var VNEXT_DRIVE_RECEIPT_SCHEMA = "kxm.drive-receipt.v1";
|
|
17877
|
+
var DRIVE_RECEIPT_MAX_BYTES = 8 * 1024;
|
|
17821
17878
|
var EVENT_STORE_TABLES = {
|
|
17822
17879
|
runs: ["run_id", "project_id", "home_runtime_id", "workflow_id", "prompt_sha256", "status", "config_revision", "memory_revision", "executor_policy_revision", "tool_policy_revision", "created_at", "updated_at"],
|
|
17823
17880
|
events: ["project_id", "run_id", "sequence", "event_id", "event_type", "command_id", "occurred_at", "recorded_at", "monotonic_ns", "config_revision", "memory_revision", "executor_policy_revision", "tool_policy_revision", "payload", "schema", "home_runtime_id"],
|
|
@@ -17896,7 +17953,8 @@ var EVENT_STORE_TABLES = {
|
|
|
17896
17953
|
"outcome",
|
|
17897
17954
|
"settled_event_id",
|
|
17898
17955
|
"content_hash"
|
|
17899
|
-
]
|
|
17956
|
+
],
|
|
17957
|
+
drive_receipts: ["drive_id", "run_id", "project_id", "opened_sequence", "last_sequence", "closed_at", "schema", "receipt"]
|
|
17900
17958
|
};
|
|
17901
17959
|
var EVENT_STORE_SCHEMA = `
|
|
17902
17960
|
CREATE TABLE runs (
|
|
@@ -18038,7 +18096,40 @@ CREATE TABLE gate_evidence (
|
|
|
18038
18096
|
) STRICT;
|
|
18039
18097
|
CREATE UNIQUE INDEX gate_evidence_key ON gate_evidence(run_id, step_id, step_attempt, evidence_key) WHERE evidence_key IS NOT NULL;
|
|
18040
18098
|
CREATE INDEX gate_evidence_run ON gate_evidence(run_id);
|
|
18099
|
+
CREATE TABLE drive_receipts (
|
|
18100
|
+
drive_id TEXT PRIMARY KEY,
|
|
18101
|
+
run_id TEXT NOT NULL,
|
|
18102
|
+
project_id TEXT NOT NULL,
|
|
18103
|
+
opened_sequence INTEGER NOT NULL,
|
|
18104
|
+
last_sequence INTEGER NOT NULL,
|
|
18105
|
+
closed_at TEXT NOT NULL,
|
|
18106
|
+
schema TEXT NOT NULL,
|
|
18107
|
+
receipt TEXT NOT NULL
|
|
18108
|
+
) STRICT;
|
|
18109
|
+
CREATE INDEX drive_receipts_run ON drive_receipts(run_id);
|
|
18110
|
+
`;
|
|
18111
|
+
var DRIVE_RECEIPTS_DDL = `
|
|
18112
|
+
CREATE TABLE IF NOT EXISTS drive_receipts (
|
|
18113
|
+
drive_id TEXT PRIMARY KEY,
|
|
18114
|
+
run_id TEXT NOT NULL,
|
|
18115
|
+
project_id TEXT NOT NULL,
|
|
18116
|
+
opened_sequence INTEGER NOT NULL,
|
|
18117
|
+
last_sequence INTEGER NOT NULL,
|
|
18118
|
+
closed_at TEXT NOT NULL,
|
|
18119
|
+
schema TEXT NOT NULL,
|
|
18120
|
+
receipt TEXT NOT NULL
|
|
18121
|
+
) STRICT;
|
|
18122
|
+
CREATE INDEX IF NOT EXISTS drive_receipts_run ON drive_receipts(run_id);
|
|
18041
18123
|
`;
|
|
18124
|
+
var EVENT_STORE_MIGRATIONS = [
|
|
18125
|
+
{
|
|
18126
|
+
fromVersion: 3,
|
|
18127
|
+
toVersion: 4,
|
|
18128
|
+
migrate(database) {
|
|
18129
|
+
database.exec(DRIVE_RECEIPTS_DDL);
|
|
18130
|
+
}
|
|
18131
|
+
}
|
|
18132
|
+
];
|
|
18042
18133
|
var VnextRunEventStore = class {
|
|
18043
18134
|
path;
|
|
18044
18135
|
database;
|
|
@@ -18047,7 +18138,8 @@ var VnextRunEventStore = class {
|
|
|
18047
18138
|
this.database = openDatabase(this.path, "run event store", {
|
|
18048
18139
|
schema: EVENT_STORE_SCHEMA,
|
|
18049
18140
|
version: VNEXT_EVENT_STORE_SCHEMA_VERSION,
|
|
18050
|
-
tables: EVENT_STORE_TABLES
|
|
18141
|
+
tables: EVENT_STORE_TABLES,
|
|
18142
|
+
migrations: EVENT_STORE_MIGRATIONS
|
|
18051
18143
|
});
|
|
18052
18144
|
}
|
|
18053
18145
|
close() {
|
|
@@ -18204,6 +18296,38 @@ var VnextRunEventStore = class {
|
|
|
18204
18296
|
ON CONFLICT(run_id) DO UPDATE SET last_sequence = excluded.last_sequence, state = excluded.state
|
|
18205
18297
|
`).run(row.runId, row.lastSequence, row.state);
|
|
18206
18298
|
}
|
|
18299
|
+
insertDriveReceipt(receipt) {
|
|
18300
|
+
validateDriveReceipt(receipt, "drive-receipt");
|
|
18301
|
+
const canonical = vnextCanonicalJson(receipt);
|
|
18302
|
+
if (Buffer.byteLength(canonical, "utf8") > DRIVE_RECEIPT_MAX_BYTES) {
|
|
18303
|
+
throw runtimeError("drive_receipt_invalid", receipt.driveId, `drive receipt exceeds ${DRIVE_RECEIPT_MAX_BYTES} bytes`);
|
|
18304
|
+
}
|
|
18305
|
+
const result = this.database.prepare(`
|
|
18306
|
+
INSERT INTO drive_receipts (drive_id, run_id, project_id, opened_sequence, last_sequence, closed_at, schema, receipt)
|
|
18307
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
18308
|
+
ON CONFLICT(drive_id) DO NOTHING
|
|
18309
|
+
`).run(
|
|
18310
|
+
receipt.driveId,
|
|
18311
|
+
receipt.runId,
|
|
18312
|
+
receipt.projectId,
|
|
18313
|
+
receipt.openedSequence,
|
|
18314
|
+
receipt.lastSequence,
|
|
18315
|
+
receipt.closedAt,
|
|
18316
|
+
receipt.schema,
|
|
18317
|
+
canonical
|
|
18318
|
+
);
|
|
18319
|
+
return { inserted: Number(result.changes) === 1 };
|
|
18320
|
+
}
|
|
18321
|
+
driveReceipt(driveId) {
|
|
18322
|
+
const row = this.database.prepare("SELECT receipt FROM drive_receipts WHERE drive_id = ?").get(driveId);
|
|
18323
|
+
return row ? parseDriveReceipt(row.receipt) : void 0;
|
|
18324
|
+
}
|
|
18325
|
+
driveReceiptsForRun(runId, limit = 20) {
|
|
18326
|
+
const rows = this.database.prepare(`
|
|
18327
|
+
SELECT receipt FROM drive_receipts WHERE run_id = ? ORDER BY closed_at DESC, drive_id DESC LIMIT ?
|
|
18328
|
+
`).all(runId, limit);
|
|
18329
|
+
return rows.map((row) => parseDriveReceipt(row.receipt));
|
|
18330
|
+
}
|
|
18207
18331
|
insertCapability(row) {
|
|
18208
18332
|
this.database.prepare(`
|
|
18209
18333
|
INSERT INTO attempt_capabilities (attempt_id, run_id, assignment_id, step_id, step_attempt, producer_id, capability_hash, state)
|
|
@@ -18438,6 +18562,11 @@ var VnextRunEventStore = class {
|
|
|
18438
18562
|
}
|
|
18439
18563
|
}
|
|
18440
18564
|
};
|
|
18565
|
+
function parseDriveReceipt(raw) {
|
|
18566
|
+
const parsed = JSON.parse(raw);
|
|
18567
|
+
validateDriveReceipt(parsed, "drive-receipt");
|
|
18568
|
+
return parsed;
|
|
18569
|
+
}
|
|
18441
18570
|
function runFromRow(row) {
|
|
18442
18571
|
if (typeof row.memory_revision !== "string" || row.memory_revision.length === 0) {
|
|
18443
18572
|
throw runtimeError("runtime_schema_shape_invalid", row.run_id, "memory_revision is required");
|
|
@@ -19066,6 +19195,9 @@ function foldVnextRunState(run, plan, events, options = {}) {
|
|
|
19066
19195
|
case "run.cancel_requested":
|
|
19067
19196
|
foldCancelRequested(state, event);
|
|
19068
19197
|
break;
|
|
19198
|
+
case "run.drive_opened":
|
|
19199
|
+
foldDriveOpened(state, event);
|
|
19200
|
+
break;
|
|
19069
19201
|
case "step.entered":
|
|
19070
19202
|
foldStepEntered(state, plan, event);
|
|
19071
19203
|
break;
|
|
@@ -19277,6 +19409,17 @@ function isProvenFailure(state, plan) {
|
|
|
19277
19409
|
const used = state.stepAttempts[stepId] ?? 0;
|
|
19278
19410
|
return Boolean(step && used >= step.maxAttempts);
|
|
19279
19411
|
}
|
|
19412
|
+
function foldDriveOpened(state, event) {
|
|
19413
|
+
if (state.status !== "running" && state.status !== "blocked_uncertain") {
|
|
19414
|
+
throw runtimeError("run_events_illegal", state.runId, "run.drive_opened is only legal while running or blocked_uncertain");
|
|
19415
|
+
}
|
|
19416
|
+
const driveId = stringPayload(event, "driveId");
|
|
19417
|
+
const mode = stringPayload(event, "mode");
|
|
19418
|
+
if (mode !== "simulated" && mode !== "live") {
|
|
19419
|
+
throw runtimeError("run_events_illegal", state.runId, "run.drive_opened mode must be simulated or live");
|
|
19420
|
+
}
|
|
19421
|
+
state.drive = { driveId, mode, openedSequence: event.sequence };
|
|
19422
|
+
}
|
|
19280
19423
|
function foldCancelRequested(state, event) {
|
|
19281
19424
|
if (state.cancelRequested || TERMINAL_RUN.has(state.status)) {
|
|
19282
19425
|
throw runtimeError("run_events_illegal", state.runId, "duplicate run.cancel_requested is not legal");
|
|
@@ -19925,7 +20068,8 @@ function freezeState(state) {
|
|
|
19925
20068
|
transitionsUsed: state.transitionsUsed,
|
|
19926
20069
|
cancelRequested: state.cancelRequested,
|
|
19927
20070
|
...state.terminalReason !== void 0 ? { terminalReason: state.terminalReason } : {},
|
|
19928
|
-
...state.terminalReason !== void 0 && state.status === "failed" ? { failureReason: state.terminalReason } : {}
|
|
20071
|
+
...state.terminalReason !== void 0 && state.status === "failed" ? { failureReason: state.terminalReason } : {},
|
|
20072
|
+
...state.drive !== void 0 ? { drive: Object.freeze({ ...state.drive }) } : {}
|
|
19929
20073
|
};
|
|
19930
20074
|
return Object.freeze(frozen);
|
|
19931
20075
|
}
|
|
@@ -23839,6 +23983,110 @@ async function driveAdmitted(context, runId, producer, token, options = {}) {
|
|
|
23839
23983
|
function newDriveId(runId, token, runtimeId, monotonicNs) {
|
|
23840
23984
|
return `drv_${createHash10("sha256").update(`${runId}\0${token}\0${runtimeId}\0${monotonicNs}`, "utf8").digest("hex").slice(0, 24)}`;
|
|
23841
23985
|
}
|
|
23986
|
+
function driveErrorSummary(error) {
|
|
23987
|
+
const raw = error instanceof VnextConfigError ? error.issues[0]?.code : void 0;
|
|
23988
|
+
const className = raw && /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/.test(raw) ? raw : "drive-failure";
|
|
23989
|
+
return { class: className, component: "vnext-engine", retryable: false };
|
|
23990
|
+
}
|
|
23991
|
+
function receiptHandoff(handoff) {
|
|
23992
|
+
return {
|
|
23993
|
+
reason: handoff.reason,
|
|
23994
|
+
detail: handoff.detail,
|
|
23995
|
+
...handoff.field !== void 0 ? { field: handoff.field } : {},
|
|
23996
|
+
...handoff.stepId !== void 0 ? { stepId: handoff.stepId } : {}
|
|
23997
|
+
};
|
|
23998
|
+
}
|
|
23999
|
+
function appendDriveOpened(context, runId, driveId, mode) {
|
|
24000
|
+
return context.eventStore.transaction(() => {
|
|
24001
|
+
const run = requireRun(context, runId);
|
|
24002
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
24003
|
+
const sequence = context.eventStore.nextSequence(runId);
|
|
24004
|
+
const event = {
|
|
24005
|
+
...vnextEventBase(context, run, now, vnextMonotonicNs()),
|
|
24006
|
+
eventId: newVnextEventId(),
|
|
24007
|
+
eventType: "run.drive_opened",
|
|
24008
|
+
sequence,
|
|
24009
|
+
payload: { driveId, mode }
|
|
24010
|
+
};
|
|
24011
|
+
context.eventStore.appendEvent(event);
|
|
24012
|
+
const next = foldStoredVnextRun(context, run);
|
|
24013
|
+
persistVnextRunState(context, runId, next, sequence);
|
|
24014
|
+
return { sequence, occurredAt: now };
|
|
24015
|
+
});
|
|
24016
|
+
}
|
|
24017
|
+
function recordDriveReceipt(context, session, closeInfo) {
|
|
24018
|
+
if (isVnextRuntimeContextClosed(context)) return;
|
|
24019
|
+
try {
|
|
24020
|
+
const run = context.eventStore.run(session.runId);
|
|
24021
|
+
if (!run) return;
|
|
24022
|
+
const state = foldStoredVnextRun(context, run);
|
|
24023
|
+
const events = context.eventStore.events(session.runId, 0, 1e6);
|
|
24024
|
+
if (events.length === 0) return;
|
|
24025
|
+
const opened = state.drive;
|
|
24026
|
+
const openedEvent = opened ? events.find((event) => event.sequence === opened.openedSequence && event.eventType === "run.drive_opened") : events.find((event) => event.eventType === "run.drive_opened" && event.payload.driveId === session.driveId);
|
|
24027
|
+
const last = events[events.length - 1];
|
|
24028
|
+
const openedSequence = opened?.openedSequence ?? openedEvent?.sequence;
|
|
24029
|
+
if (openedSequence === void 0) return;
|
|
24030
|
+
const receipt = {
|
|
24031
|
+
schema: VNEXT_DRIVE_RECEIPT_SCHEMA,
|
|
24032
|
+
driveId: session.driveId,
|
|
24033
|
+
runId: session.runId,
|
|
24034
|
+
projectId: run.projectId,
|
|
24035
|
+
homeRuntimeId: session.homeRuntimeId,
|
|
24036
|
+
mode: session.mode,
|
|
24037
|
+
openedAt: openedEvent?.occurredAt ?? session.openedAt,
|
|
24038
|
+
openedSequence,
|
|
24039
|
+
closedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24040
|
+
lastSequence: last.sequence,
|
|
24041
|
+
logHash: hashVnextDriveLog(events.filter((event) => event.sequence >= 1 && event.sequence <= last.sequence)),
|
|
24042
|
+
settlement: {
|
|
24043
|
+
kind: closeInfo.kind,
|
|
24044
|
+
status: state.status,
|
|
24045
|
+
reason: closeInfo.reason ?? state.terminalReason ?? "",
|
|
24046
|
+
...closeInfo.kind === "handoff" && closeInfo.handoff ? { handoff: receiptHandoff(closeInfo.handoff) } : {},
|
|
24047
|
+
...closeInfo.error !== void 0 ? { error: closeInfo.error } : {}
|
|
24048
|
+
},
|
|
24049
|
+
budget: null,
|
|
24050
|
+
producer: { id: closeInfo.producerId, closed: closeInfo.producerClosed }
|
|
24051
|
+
};
|
|
24052
|
+
context.eventStore.insertDriveReceipt(receipt);
|
|
24053
|
+
} catch {
|
|
24054
|
+
}
|
|
24055
|
+
}
|
|
24056
|
+
function vnextDrivePollProjection(context, runId, state) {
|
|
24057
|
+
if (!state.drive) return void 0;
|
|
24058
|
+
const events = context.eventStore.events(runId, 0, 1e6);
|
|
24059
|
+
const openedEvent = events.find((event) => event.sequence === state.drive.openedSequence);
|
|
24060
|
+
let receipt = null;
|
|
24061
|
+
let unreadable = false;
|
|
24062
|
+
try {
|
|
24063
|
+
receipt = context.eventStore.driveReceipt(state.drive.driveId) ?? null;
|
|
24064
|
+
} catch {
|
|
24065
|
+
unreadable = true;
|
|
24066
|
+
}
|
|
24067
|
+
const projection = {
|
|
24068
|
+
driveId: state.drive.driveId,
|
|
24069
|
+
mode: state.drive.mode,
|
|
24070
|
+
openedAt: openedEvent?.occurredAt ?? "",
|
|
24071
|
+
receipt: unreadable ? null : receipt,
|
|
24072
|
+
verified: false
|
|
24073
|
+
};
|
|
24074
|
+
if (unreadable) {
|
|
24075
|
+
projection.divergence = "receipt unreadable";
|
|
24076
|
+
return projection;
|
|
24077
|
+
}
|
|
24078
|
+
if (!receipt) {
|
|
24079
|
+
projection.divergence = "no receipt";
|
|
24080
|
+
return projection;
|
|
24081
|
+
}
|
|
24082
|
+
const checked = verifyVnextDriveReceipt(receipt, events, state.status, {
|
|
24083
|
+
runId,
|
|
24084
|
+
driveId: state.drive.driveId
|
|
24085
|
+
});
|
|
24086
|
+
projection.verified = checked.verified;
|
|
24087
|
+
if (checked.divergence !== void 0) projection.divergence = checked.divergence;
|
|
24088
|
+
return projection;
|
|
24089
|
+
}
|
|
23842
24090
|
function inflightAttemptId(state) {
|
|
23843
24091
|
const leftover = unreconciledPanelAttemptId(state);
|
|
23844
24092
|
if (leftover) return leftover;
|
|
@@ -23986,13 +24234,15 @@ var VnextRunScheduler = class _VnextRunScheduler {
|
|
|
23986
24234
|
throw error;
|
|
23987
24235
|
}
|
|
23988
24236
|
const driveId = newDriveId(runId, token, this.context.homeRuntimeId, vnextMonotonicNs());
|
|
24237
|
+
const openedMeta = appendDriveOpened(this.context, runId, driveId, options.mode);
|
|
23989
24238
|
const session = {
|
|
23990
24239
|
driveId,
|
|
23991
24240
|
runId,
|
|
23992
24241
|
token,
|
|
23993
24242
|
homeRuntimeId: this.context.homeRuntimeId,
|
|
23994
24243
|
mode: options.mode,
|
|
23995
|
-
openedAt:
|
|
24244
|
+
openedAt: openedMeta.occurredAt,
|
|
24245
|
+
producerId: producer.id,
|
|
23996
24246
|
controller: new AbortController(),
|
|
23997
24247
|
settled
|
|
23998
24248
|
};
|
|
@@ -24001,10 +24251,30 @@ var VnextRunScheduler = class _VnextRunScheduler {
|
|
|
24001
24251
|
resolveOpen({ driveId, settled });
|
|
24002
24252
|
try {
|
|
24003
24253
|
const result = await driveAdmitted(this.context, runId, producer, token, driveOptions);
|
|
24254
|
+
const kind = result.handoff ? "handoff" : isTerminalRunStatus(result.state.status) ? "terminal" : "unsettled";
|
|
24255
|
+
recordDriveReceipt(this.context, session, {
|
|
24256
|
+
kind,
|
|
24257
|
+
...result.state.terminalReason !== void 0 ? { reason: result.state.terminalReason } : result.handoff ? { reason: result.handoff.reason } : {},
|
|
24258
|
+
...result.handoff ? { handoff: result.handoff } : {},
|
|
24259
|
+
producerId: producer.id,
|
|
24260
|
+
producerClosed: false
|
|
24261
|
+
});
|
|
24004
24262
|
pending.resolve(result);
|
|
24005
24263
|
return result;
|
|
24006
24264
|
} catch (error) {
|
|
24007
24265
|
recordBareDriveFailure(this.context, runId);
|
|
24266
|
+
let foldedStatus;
|
|
24267
|
+
try {
|
|
24268
|
+
foldedStatus = foldStoredVnextRun(this.context, requireRun(this.context, runId)).status;
|
|
24269
|
+
} catch {
|
|
24270
|
+
foldedStatus = void 0;
|
|
24271
|
+
}
|
|
24272
|
+
recordDriveReceipt(this.context, session, {
|
|
24273
|
+
kind: foldedStatus !== void 0 && isTerminalRunStatus(foldedStatus) ? "terminal" : "unsettled",
|
|
24274
|
+
error: driveErrorSummary(error),
|
|
24275
|
+
producerId: producer.id,
|
|
24276
|
+
producerClosed: false
|
|
24277
|
+
});
|
|
24008
24278
|
pending.reject(error);
|
|
24009
24279
|
throw error;
|
|
24010
24280
|
}
|
|
@@ -26248,6 +26518,19 @@ async function waitForDriveSessions(settled, graceMs) {
|
|
|
26248
26518
|
if (timeout !== void 0) clearTimeout(timeout);
|
|
26249
26519
|
}
|
|
26250
26520
|
}
|
|
26521
|
+
async function driveSessionStillPending(settled) {
|
|
26522
|
+
let pending = true;
|
|
26523
|
+
void settled.then(
|
|
26524
|
+
() => {
|
|
26525
|
+
pending = false;
|
|
26526
|
+
},
|
|
26527
|
+
() => {
|
|
26528
|
+
pending = false;
|
|
26529
|
+
}
|
|
26530
|
+
);
|
|
26531
|
+
await Promise.resolve();
|
|
26532
|
+
return pending;
|
|
26533
|
+
}
|
|
26251
26534
|
async function startVnextRuntimeSupervisor(options = {}) {
|
|
26252
26535
|
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
26253
26536
|
const paths = vnextRuntimePaths(options.stateRoot !== void 0 ? { stateRoot: options.stateRoot } : {});
|
|
@@ -26351,7 +26634,9 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26351
26634
|
const bundle = loadVnextProject(projectRoot, {});
|
|
26352
26635
|
if (request.method === "GET" && !sub) {
|
|
26353
26636
|
const projected = rebuildVnextRunProjection(context, runId);
|
|
26354
|
-
|
|
26637
|
+
const folded = foldStoredVnextRun(context, projected);
|
|
26638
|
+
const drive = vnextDrivePollProjection(context, runId, folded);
|
|
26639
|
+
sendJson(response, 200, { ok: true, run: projected, ...drive !== void 0 ? { drive } : {} });
|
|
26355
26640
|
return;
|
|
26356
26641
|
}
|
|
26357
26642
|
if (request.method === "POST" && sub === "drive") {
|
|
@@ -26568,6 +26853,15 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26568
26853
|
}
|
|
26569
26854
|
}
|
|
26570
26855
|
await waitForDriveSessions(openSessions.map(({ session }) => session.settled), runtimeStopGraceMs());
|
|
26856
|
+
for (const { context, session } of openSessions) {
|
|
26857
|
+
if (!await driveSessionStillPending(session.settled)) continue;
|
|
26858
|
+
recordDriveReceipt(context, session, {
|
|
26859
|
+
kind: "unsettled",
|
|
26860
|
+
reason: "runtime_shutdown_grace_expired",
|
|
26861
|
+
producerId: session.producerId,
|
|
26862
|
+
producerClosed: false
|
|
26863
|
+
});
|
|
26864
|
+
}
|
|
26571
26865
|
for (const context of contexts.values()) closeVnextRuntimeContext(context);
|
|
26572
26866
|
contexts.clear();
|
|
26573
26867
|
const closed = new Promise((resolveStop) => server.close(() => resolveStop()));
|
package/plugins/kxm/package.json
CHANGED
|
@@ -473,6 +473,42 @@ export async function cmdVnextRun(runtime: Runtime, workflow: string | undefined
|
|
|
473
473
|
}
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
+
interface VnextStatusDrive {
|
|
477
|
+
driveId?: string;
|
|
478
|
+
mode?: string;
|
|
479
|
+
openedAt?: string;
|
|
480
|
+
receipt?: {
|
|
481
|
+
settlement?: { kind?: string; status?: string; reason?: string };
|
|
482
|
+
} | null;
|
|
483
|
+
verified?: boolean;
|
|
484
|
+
divergence?: string;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function formatDriveStatusLine(runStatus: string, drive: VnextStatusDrive | undefined): string | undefined {
|
|
488
|
+
if (!drive || typeof drive.driveId !== "string" || drive.driveId.length === 0) return undefined;
|
|
489
|
+
const receipt = drive.receipt ?? null;
|
|
490
|
+
if (receipt === null) {
|
|
491
|
+
if (runStatus === "running" || runStatus === "blocked_uncertain") {
|
|
492
|
+
return `drive ${drive.driveId}: open`;
|
|
493
|
+
}
|
|
494
|
+
return `drive ${drive.driveId}: no receipt (orphaned)`;
|
|
495
|
+
}
|
|
496
|
+
const kind = receipt.settlement?.kind;
|
|
497
|
+
const reason = typeof receipt.settlement?.reason === "string" ? receipt.settlement.reason : "";
|
|
498
|
+
if (kind === "unsettled") {
|
|
499
|
+
return `drive ${drive.driveId}: unsettled ${reason}`.trimEnd();
|
|
500
|
+
}
|
|
501
|
+
if (kind === "handoff") {
|
|
502
|
+
return drive.verified === true
|
|
503
|
+
? `drive ${drive.driveId}: handoff (receipt verified)`
|
|
504
|
+
: `drive ${drive.driveId}: handoff`;
|
|
505
|
+
}
|
|
506
|
+
if (drive.verified === true) {
|
|
507
|
+
return `drive ${drive.driveId}: completed (receipt verified)`;
|
|
508
|
+
}
|
|
509
|
+
return `drive ${drive.driveId}: completed`;
|
|
510
|
+
}
|
|
511
|
+
|
|
476
512
|
export async function cmdVnextRunStatus(runtime: Runtime, runId: string): Promise<number> {
|
|
477
513
|
try {
|
|
478
514
|
const projectRoot = discoverVnextProjectRoot(runtime.cwd);
|
|
@@ -480,10 +516,17 @@ export async function cmdVnextRunStatus(runtime: Runtime, runId: string): Promis
|
|
|
480
516
|
print(runtime.io, runtime.json, { ok: false, command: "runs status", error: "project_required" }, "kxm runs status requires a vNext project (run kxm init first)");
|
|
481
517
|
return 1;
|
|
482
518
|
}
|
|
483
|
-
const supervisor = await ensureVnextSupervisor({ env: runtime.env });
|
|
484
|
-
const result = await vnextRuntimeRequest(supervisor, "GET", `/v1/runs/${encodeURIComponent(runId)}?projectRoot=${encodeURIComponent(projectRoot)}`);
|
|
519
|
+
const supervisor = await (vnextDriveCliSeams.ensureSupervisor ?? ensureVnextSupervisor)({ env: runtime.env });
|
|
520
|
+
const result = await (vnextDriveCliSeams.runtimeRequest ?? vnextRuntimeRequest)(supervisor, "GET", `/v1/runs/${encodeURIComponent(runId)}?projectRoot=${encodeURIComponent(projectRoot)}`);
|
|
485
521
|
const run = result.run as { runId: string; status: string; workflowId: string; configRevision: string; updatedAt: string };
|
|
486
|
-
|
|
522
|
+
const drive = result.drive as VnextStatusDrive | undefined;
|
|
523
|
+
const driveLine = formatDriveStatusLine(run.status, drive);
|
|
524
|
+
print(
|
|
525
|
+
runtime.io,
|
|
526
|
+
runtime.json,
|
|
527
|
+
{ ok: true, command: "runs status", run, ...(drive !== undefined ? { drive } : {}) },
|
|
528
|
+
`run ${run.runId}: ${run.status} (workflow ${run.workflowId}, updated ${run.updatedAt})${driveLine ? `\n${driveLine}` : ""}`,
|
|
529
|
+
);
|
|
487
530
|
return 0;
|
|
488
531
|
} catch (error) {
|
|
489
532
|
if (error instanceof VnextConfigError) {
|
package/plugins/kxm/src/cli.ts
CHANGED
|
@@ -382,7 +382,7 @@ function createProgram(ctx: CliContext, result: { code: number }): Command {
|
|
|
382
382
|
}));
|
|
383
383
|
const runCmd = addGlobalOptions(program.command("runs").description("Inspect vNext runs"));
|
|
384
384
|
runCmd.helpCommand("help", "Show runs help");
|
|
385
|
-
addGlobalOptions(runCmd.command("status").description("Show the projected status of a run"))
|
|
385
|
+
addGlobalOptions(runCmd.command("status").description("Show the projected status of a run, including durable drive receipt state (open / receipt verified / unsettled / orphaned)"))
|
|
386
386
|
.argument("<runId>", "Run id")
|
|
387
387
|
.action(async function runStatusAction(this: Command, runId: string) {
|
|
388
388
|
result.code = await cmdVnextRunStatus(runtimeFrom(ctx, this), runId);
|
|
@@ -420,7 +420,7 @@ export function discoverProjectStores(projectRoot: string, options: { hubDataPat
|
|
|
420
420
|
stores.push({
|
|
421
421
|
storeId: `events:${key}`,
|
|
422
422
|
sourcePath: join(eventsDir, entry.name),
|
|
423
|
-
maxSupportedVersion:
|
|
423
|
+
maxSupportedVersion: 4,
|
|
424
424
|
});
|
|
425
425
|
}
|
|
426
426
|
}
|
|
@@ -537,6 +537,8 @@ export function restoreBackup(
|
|
|
537
537
|
let maxSupported = 3;
|
|
538
538
|
if (store.storeId === "registry" || store.storeId === "binding-store") {
|
|
539
539
|
maxSupported = 1;
|
|
540
|
+
} else if (store.storeId.startsWith("events:")) {
|
|
541
|
+
maxSupported = 4;
|
|
540
542
|
}
|
|
541
543
|
|
|
542
544
|
let targetPath = store.sourcePath;
|
|
@@ -8,7 +8,7 @@ import { AGENT_COMMANDS_MAP, enforceToolPolicy, getMcpTools, reconcileInbox } fr
|
|
|
8
8
|
import { deliverInboxNotification } from "./inbox.ts";
|
|
9
9
|
import type { HubEvent, MessageRecord } from "./protocol.ts";
|
|
10
10
|
|
|
11
|
-
const VERSION = "0.7.
|
|
11
|
+
const VERSION = "0.7.24";
|
|
12
12
|
const inbox = new Map<string, MessageRecord>();
|
|
13
13
|
const notifiedInbox = new Set<string>();
|
|
14
14
|
let meshClient: HubClient | undefined;
|
|
@@ -166,6 +166,7 @@ export class VnextSchemaRegistry {
|
|
|
166
166
|
readonly migrationReceiptValidator: ValidateFunction;
|
|
167
167
|
readonly permissionDiffValidator: ValidateFunction;
|
|
168
168
|
readonly runEventValidator: ValidateFunction;
|
|
169
|
+
readonly driveReceiptValidator: ValidateFunction;
|
|
169
170
|
|
|
170
171
|
constructor(schemasDir = DEFAULT_SCHEMA_DIR) {
|
|
171
172
|
this.schemasDir = resolve(schemasDir);
|
|
@@ -183,6 +184,7 @@ export class VnextSchemaRegistry {
|
|
|
183
184
|
const migrationReceiptFile = "migration-receipt.schema.json";
|
|
184
185
|
const permissionDiffFile = "permission-diff.schema.json";
|
|
185
186
|
const runEventFile = "run-event.schema.json";
|
|
187
|
+
const driveReceiptFile = "drive-receipt.schema.json";
|
|
186
188
|
this.ajv.addSchema(readJsonObject(join(this.schemasDir, localBindingsFile)));
|
|
187
189
|
this.ajv.addSchema(readJsonObject(join(this.schemasDir, templateProvenanceFile)));
|
|
188
190
|
this.ajv.addSchema(readJsonObject(join(this.schemasDir, initOperationFile)));
|
|
@@ -191,6 +193,7 @@ export class VnextSchemaRegistry {
|
|
|
191
193
|
this.ajv.addSchema(readJsonObject(join(this.schemasDir, migrationReceiptFile)));
|
|
192
194
|
this.ajv.addSchema(readJsonObject(join(this.schemasDir, permissionDiffFile)));
|
|
193
195
|
this.ajv.addSchema(readJsonObject(join(this.schemasDir, runEventFile)));
|
|
196
|
+
this.ajv.addSchema(readJsonObject(join(this.schemasDir, driveReceiptFile)));
|
|
194
197
|
for (const [kind, definition] of Object.entries(RESOURCE_SCHEMA) as [VnextResourceKind, { identity: string; file: string }][]) {
|
|
195
198
|
const validator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${definition.file}`);
|
|
196
199
|
if (!validator) throw new Error(`schema did not compile: ${definition.file}`);
|
|
@@ -204,6 +207,7 @@ export class VnextSchemaRegistry {
|
|
|
204
207
|
const migrationReceiptValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${migrationReceiptFile}`);
|
|
205
208
|
const permissionDiffValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${permissionDiffFile}`);
|
|
206
209
|
const runEventValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${runEventFile}`);
|
|
210
|
+
const driveReceiptValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${driveReceiptFile}`);
|
|
207
211
|
if (!localBindingsValidator) throw new Error(`schema did not compile: ${localBindingsFile}`);
|
|
208
212
|
if (!templateProvenanceValidator) throw new Error(`schema did not compile: ${templateProvenanceFile}`);
|
|
209
213
|
if (!initOperationValidator) throw new Error(`schema did not compile: ${initOperationFile}`);
|
|
@@ -212,6 +216,7 @@ export class VnextSchemaRegistry {
|
|
|
212
216
|
if (!migrationReceiptValidator) throw new Error(`schema did not compile: ${migrationReceiptFile}`);
|
|
213
217
|
if (!permissionDiffValidator) throw new Error(`schema did not compile: ${permissionDiffFile}`);
|
|
214
218
|
if (!runEventValidator) throw new Error(`schema did not compile: ${runEventFile}`);
|
|
219
|
+
if (!driveReceiptValidator) throw new Error(`schema did not compile: ${driveReceiptFile}`);
|
|
215
220
|
this.localBindingsValidator = localBindingsValidator;
|
|
216
221
|
this.templateProvenanceValidator = templateProvenanceValidator;
|
|
217
222
|
this.initOperationValidator = initOperationValidator;
|
|
@@ -220,6 +225,7 @@ export class VnextSchemaRegistry {
|
|
|
220
225
|
this.migrationReceiptValidator = migrationReceiptValidator;
|
|
221
226
|
this.permissionDiffValidator = permissionDiffValidator;
|
|
222
227
|
this.runEventValidator = runEventValidator;
|
|
228
|
+
this.driveReceiptValidator = driveReceiptValidator;
|
|
223
229
|
}
|
|
224
230
|
|
|
225
231
|
validate(kind: VnextResourceKind, value: JsonObject, file: string): VnextConfigIssue[] {
|
|
@@ -261,6 +267,10 @@ export class VnextSchemaRegistry {
|
|
|
261
267
|
return this.validateAuxiliary(value, file, "kxm.permission-diff.v1", this.permissionDiffValidator);
|
|
262
268
|
}
|
|
263
269
|
|
|
270
|
+
validateDriveReceipt(value: JsonObject, file: string): VnextConfigIssue[] {
|
|
271
|
+
return this.validateAuxiliary(value, file, "kxm.drive-receipt.v1", this.driveReceiptValidator);
|
|
272
|
+
}
|
|
273
|
+
|
|
264
274
|
private validateAuxiliary(value: JsonObject, file: string, identity: string, validator: ValidateFunction): VnextConfigIssue[] {
|
|
265
275
|
if (value.schema !== identity) {
|
|
266
276
|
return [issue("schema", "schema_identity_mismatch", file, `expected ${identity}, received ${String(value.schema)}`)];
|
|
@@ -285,6 +295,27 @@ export function validateRunEvent(value: unknown, file: string): void {
|
|
|
285
295
|
}
|
|
286
296
|
}
|
|
287
297
|
|
|
298
|
+
/** Validate a drive receipt against `kxm.drive-receipt.v1`. Throws `drive_receipt_invalid`. */
|
|
299
|
+
export function validateDriveReceipt(value: unknown, file: string): void {
|
|
300
|
+
const registry = (cachedRunEventRegistry ??= new VnextSchemaRegistry());
|
|
301
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || (value as JsonObject).schema !== "kxm.drive-receipt.v1") {
|
|
302
|
+
throw new VnextConfigError([issue(
|
|
303
|
+
"schema",
|
|
304
|
+
"drive_receipt_invalid",
|
|
305
|
+
file,
|
|
306
|
+
"expected kxm.drive-receipt.v1",
|
|
307
|
+
)]);
|
|
308
|
+
}
|
|
309
|
+
if (!registry.driveReceiptValidator(value)) {
|
|
310
|
+
throw new VnextConfigError([issue(
|
|
311
|
+
"schema",
|
|
312
|
+
"drive_receipt_invalid",
|
|
313
|
+
file,
|
|
314
|
+
registry.ajv.errorsText(registry.driveReceiptValidator.errors, { separator: "; " }),
|
|
315
|
+
)]);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
288
319
|
function schemaIssue(file: string, error: ErrorObject): VnextConfigIssue {
|
|
289
320
|
const location = error.instancePath || "/";
|
|
290
321
|
const suffix = error.params && "additionalProperty" in error.params
|