@kontextmind/kxm 0.7.53 → 0.7.54
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/CHANGELOG.md +18 -1
- package/docs/agent-skills.md +1 -1
- package/docs/configuration.md +0 -3
- package/docs/contracts/migration.md +50 -258
- package/docs/contracts/validation.md +15 -33
- package/docs/test-matrix.md +1 -1
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +376 -2133
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +18 -98
- package/plugins/kxm/dist/runtime.js +18 -98
- package/plugins/kxm/dist/server.js +1 -1
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +4 -7
- package/plugins/kxm/src/autocomplete.ts +0 -3
- package/plugins/kxm/src/cli/project.ts +2 -133
- package/plugins/kxm/src/cli.ts +3 -24
- package/plugins/kxm/src/database.ts +1 -1
- package/plugins/kxm/src/init.ts +10 -6
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/project-config.ts +54 -173
- package/schemas/README.md +0 -3
- package/plugins/kxm/src/migrate.ts +0 -1848
- package/schemas/migration-decision.schema.json +0 -26
- package/schemas/migration-plan.schema.json +0 -123
- package/schemas/migration-receipt.schema.json +0 -52
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
type KxmInitializationPlan,
|
|
13
13
|
} from "../project-config.ts";
|
|
14
14
|
import { initializeKxmProject } from "../init.ts";
|
|
15
|
-
import { applyKxmMigration, planKxmMigration, verifyKxmMigration } from "../migrate.ts";
|
|
16
15
|
import { diffKxmProjectAgainstRevision, formatKxmPermissionDiff } from "../permission.ts";
|
|
17
16
|
import { readKxmLocalBindings, kxmUserStateRoot } from "../bindings.ts";
|
|
18
17
|
import { loadKxmProject } from "../project-config.ts";
|
|
@@ -151,8 +150,8 @@ export async function cmdKxmInit(
|
|
|
151
150
|
if (runtime.dryRun) {
|
|
152
151
|
return finishInit(0, `init plan: ${initialized.plan.mode}`);
|
|
153
152
|
}
|
|
154
|
-
const next = initialized.plan.mode === "
|
|
155
|
-
? "legacy state
|
|
153
|
+
const next = initialized.plan.mode === "legacy"
|
|
154
|
+
? "legacy state is not migrated by this build: initialise a fresh project directory and copy the YAML definitions you want to keep"
|
|
156
155
|
: initialized.repairPlan?.issues.length
|
|
157
156
|
? "managed-template repair is blocked by conflicts or authority changes; local files were preserved"
|
|
158
157
|
: "partial or provenance-free KXM state requires explicit repair; no files were overwritten";
|
|
@@ -176,136 +175,6 @@ export async function cmdKxmInit(
|
|
|
176
175
|
}
|
|
177
176
|
}
|
|
178
177
|
|
|
179
|
-
export async function cmdKxmMigratePlan(runtime: Runtime): Promise<number> {
|
|
180
|
-
if (runtime.workspaceFlag !== undefined) {
|
|
181
|
-
print(runtime.io, runtime.json, {
|
|
182
|
-
ok: false,
|
|
183
|
-
command: "migrate plan",
|
|
184
|
-
error: "workspace_option_unsupported",
|
|
185
|
-
}, "kxm migrate discovers the authoritative Git root from the current directory; --workspace is not supported");
|
|
186
|
-
return 2;
|
|
187
|
-
}
|
|
188
|
-
try {
|
|
189
|
-
const result = planKxmMigration(runtime.cwd, {});
|
|
190
|
-
const ambiguities = (result.plan.ambiguities as Array<{ key: string; message: string }> | undefined) ?? [];
|
|
191
|
-
const unmapped = (result.plan.unmapped as unknown[] | undefined) ?? [];
|
|
192
|
-
const payload = {
|
|
193
|
-
ok: result.plan.canApply === true,
|
|
194
|
-
command: "migrate plan",
|
|
195
|
-
plan: result.plan,
|
|
196
|
-
plannedOnly: result.plan.canApply !== true,
|
|
197
|
-
};
|
|
198
|
-
if (result.plan.canApply === true) {
|
|
199
|
-
print(runtime.io, runtime.json, payload, `migration plan: ${ambiguities.length} ambiguities, ${unmapped.length} preserved fields; ready to apply`);
|
|
200
|
-
return 0;
|
|
201
|
-
}
|
|
202
|
-
print(
|
|
203
|
-
runtime.io,
|
|
204
|
-
runtime.json,
|
|
205
|
-
payload,
|
|
206
|
-
`migration plan requires ${ambiguities.length} reviewed decision(s):\n${ambiguities.map((candidate) => ` - ${candidate.key}: ${candidate.message}`).join("\n")}`,
|
|
207
|
-
);
|
|
208
|
-
return 1;
|
|
209
|
-
} catch (error) {
|
|
210
|
-
if (error instanceof KxmConfigError) {
|
|
211
|
-
print(runtime.io, runtime.json, { ok: false, command: "migrate plan", error: "migration_plan_failed", issues: error.issues }, `migration plan failed: ${error.message}`);
|
|
212
|
-
return 1;
|
|
213
|
-
}
|
|
214
|
-
print(runtime.io, runtime.json, { ok: false, command: "migrate plan", error: "migration_plan_io_failed" }, "migration plan failed because a local filesystem operation did not complete");
|
|
215
|
-
return 1;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export async function cmdKxmMigrateApply(runtime: Runtime, options: { decisions?: string | undefined; projectId?: string | undefined; name?: string | undefined }): Promise<number> {
|
|
220
|
-
if (runtime.workspaceFlag !== undefined) {
|
|
221
|
-
print(runtime.io, runtime.json, {
|
|
222
|
-
ok: false,
|
|
223
|
-
command: "migrate apply",
|
|
224
|
-
error: "workspace_option_unsupported",
|
|
225
|
-
}, "kxm migrate discovers the authoritative Git root from the current directory; --workspace is not supported");
|
|
226
|
-
return 2;
|
|
227
|
-
}
|
|
228
|
-
try {
|
|
229
|
-
const result = applyKxmMigration(runtime.cwd, {
|
|
230
|
-
...(options.decisions?.trim() ? { decisionsFile: options.decisions.trim() } : {}),
|
|
231
|
-
...(options.projectId?.trim() ? { projectId: options.projectId.trim() } : {}),
|
|
232
|
-
...(options.name?.trim() ? { projectName: options.name.trim() } : {}),
|
|
233
|
-
localStateRoot: kxmUserStateRoot({ env: runtime.env }),
|
|
234
|
-
dryRun: runtime.dryRun,
|
|
235
|
-
});
|
|
236
|
-
const payload = {
|
|
237
|
-
ok: result.action !== "planned" || (runtime.dryRun === true && result.plan?.canApply === true),
|
|
238
|
-
command: "migrate apply",
|
|
239
|
-
action: result.action,
|
|
240
|
-
files: result.files,
|
|
241
|
-
...(result.configRevision ? { configRevision: result.configRevision } : {}),
|
|
242
|
-
...(result.receiptPath ? { receiptPath: result.receiptPath } : {}),
|
|
243
|
-
plannedOnly: result.action === "planned",
|
|
244
|
-
};
|
|
245
|
-
if (result.action === "applied") {
|
|
246
|
-
print(runtime.io, runtime.json, payload, `migration applied: ${result.files.length} resources installed, receipt at ${result.receiptPath ?? ""}`);
|
|
247
|
-
return 0;
|
|
248
|
-
}
|
|
249
|
-
if (result.action === "already-migrated") {
|
|
250
|
-
print(runtime.io, runtime.json, payload, "migration receipt already exists; nothing to apply");
|
|
251
|
-
return 0;
|
|
252
|
-
}
|
|
253
|
-
if (runtime.dryRun && result.plan?.canApply === true) {
|
|
254
|
-
print(runtime.io, runtime.json, payload, `migration dry run: ${result.files.length} resources would be installed`);
|
|
255
|
-
return 0;
|
|
256
|
-
}
|
|
257
|
-
const ambiguities = (result.plan?.ambiguities as Array<{ key: string; message: string }> | undefined) ?? [];
|
|
258
|
-
print(
|
|
259
|
-
runtime.io,
|
|
260
|
-
runtime.json,
|
|
261
|
-
{ ...payload, plan: result.plan },
|
|
262
|
-
`migration blocked by ${ambiguities.length} unresolved decision(s); review 'kxm migrate plan' and pass --decisions:\n${ambiguities.map((candidate) => ` - ${candidate.key}: ${candidate.message}`).join("\n")}`,
|
|
263
|
-
);
|
|
264
|
-
return 1;
|
|
265
|
-
} catch (error) {
|
|
266
|
-
if (error instanceof KxmConfigError) {
|
|
267
|
-
print(runtime.io, runtime.json, { ok: false, command: "migrate apply", error: "migration_apply_failed", issues: error.issues }, `migration apply failed: ${error.message}`);
|
|
268
|
-
return 1;
|
|
269
|
-
}
|
|
270
|
-
print(runtime.io, runtime.json, { ok: false, command: "migrate apply", error: "migration_apply_io_failed" }, "migration apply failed because a local filesystem operation did not complete");
|
|
271
|
-
return 1;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
export async function cmdKxmMigrateVerify(runtime: Runtime): Promise<number> {
|
|
276
|
-
if (runtime.workspaceFlag !== undefined) {
|
|
277
|
-
print(runtime.io, runtime.json, {
|
|
278
|
-
ok: false,
|
|
279
|
-
command: "migrate verify",
|
|
280
|
-
error: "workspace_option_unsupported",
|
|
281
|
-
}, "kxm migrate discovers the authoritative Git root from the current directory; --workspace is not supported");
|
|
282
|
-
return 2;
|
|
283
|
-
}
|
|
284
|
-
let result: ReturnType<typeof verifyKxmMigration>;
|
|
285
|
-
try {
|
|
286
|
-
result = verifyKxmMigration(runtime.cwd, {});
|
|
287
|
-
} catch (error) {
|
|
288
|
-
if (error instanceof KxmConfigError) {
|
|
289
|
-
print(runtime.io, runtime.json, { ok: false, command: "migrate verify", error: "migration_verify_failed", issues: error.issues }, `migration verification failed: ${error.message}`);
|
|
290
|
-
return 1;
|
|
291
|
-
}
|
|
292
|
-
print(runtime.io, runtime.json, { ok: false, command: "migrate verify", error: "migration_verify_io_failed" }, "migration verification failed because a local filesystem operation did not complete");
|
|
293
|
-
return 1;
|
|
294
|
-
}
|
|
295
|
-
const payload = {
|
|
296
|
-
ok: result.ok,
|
|
297
|
-
command: "migrate verify",
|
|
298
|
-
...(result.configRevision ? { configRevision: result.configRevision } : {}),
|
|
299
|
-
issues: result.issues,
|
|
300
|
-
};
|
|
301
|
-
if (result.ok) {
|
|
302
|
-
print(runtime.io, runtime.json, payload, `migration receipt verified: legacy sources unchanged, target bundle matches ${result.configRevision ?? ""}`);
|
|
303
|
-
return 0;
|
|
304
|
-
}
|
|
305
|
-
print(runtime.io, runtime.json, payload, `migration verification failed:\n${result.issues.map((issue) => ` - ${issue.file}: ${issue.code}: ${issue.message}`).join("\n")}`);
|
|
306
|
-
return 1;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
178
|
export async function cmdBackup(runtime: Runtime, options: { out?: string | undefined }): Promise<number> {
|
|
310
179
|
try {
|
|
311
180
|
const { manifest, outDir } = createBackup({
|
package/plugins/kxm/src/cli.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - Role & workflow governance (`kxm role`, `kxm workflow`, `kxm gate`, `kxm signal`)
|
|
8
8
|
* - Task & goal management (`kxm goal`, `kxm task`, `kxm suggest`, `kxm studio`)
|
|
9
9
|
* - Knowledge, skills, and memory (`kxm context`, `kxm skills`, `kxm memory`)
|
|
10
|
-
* - KXM runtime & initialization (`kxm init`, `kxm
|
|
10
|
+
* - KXM runtime & initialization (`kxm init`, `kxm trust`, `kxm run`, `kxm harness`)
|
|
11
11
|
* - Hub, workers, and dashboard (`kxm hub`, `kxm worker`, `kxm dash`, `kxm session`, `kxm auth`)
|
|
12
12
|
* - System, update, and configuration (`kxm update`, `kxm config`, `kxm completion`, `kxm improve`)
|
|
13
13
|
*/
|
|
@@ -98,10 +98,7 @@ import {
|
|
|
98
98
|
|
|
99
99
|
import {
|
|
100
100
|
cmdKxmInit,
|
|
101
|
-
|
|
102
|
-
cmdKxmMigrateApply,
|
|
103
|
-
cmdKxmMigrateVerify,
|
|
104
|
-
cmdBackup,
|
|
101
|
+
cmdBackup,
|
|
105
102
|
cmdRestore,
|
|
106
103
|
cmdKxmTrust,
|
|
107
104
|
cmdKxmRun,
|
|
@@ -334,7 +331,7 @@ function createProgram(ctx: CliContext, result: { code: number }): Command {
|
|
|
334
331
|
.helpCommand("help", "Show help");
|
|
335
332
|
addGlobalOptions(program);
|
|
336
333
|
|
|
337
|
-
program.command("init").description("Create, validate, or
|
|
334
|
+
program.command("init").description("Create, validate, repair, or join a KXM project")
|
|
338
335
|
.option("--json", "Print machine-readable JSON")
|
|
339
336
|
.option("--dry-run", "Plan without making changes")
|
|
340
337
|
.option("--name <name>", "Project display name for a new project")
|
|
@@ -347,24 +344,6 @@ function createProgram(ctx: CliContext, result: { code: number }): Command {
|
|
|
347
344
|
});
|
|
348
345
|
});
|
|
349
346
|
|
|
350
|
-
const migrate = addGlobalOptions(program.command("migrate").description("Plan, apply, and verify legacy JSON configuration migration"));
|
|
351
|
-
migrate.helpCommand("help", "Show migrate help");
|
|
352
|
-
addGlobalOptions(migrate.command("plan").description("Compute the deterministic legacy-to-KXM migration plan without writes"))
|
|
353
|
-
.action(async function migratePlanAction(this: Command) {
|
|
354
|
-
result.code = await cmdKxmMigratePlan(runtimeFrom(ctx, this));
|
|
355
|
-
});
|
|
356
|
-
addGlobalOptions(migrate.command("apply").description("Install a reviewed migration with a hash-linked receipt"))
|
|
357
|
-
.option("--decisions <file>", "Reviewed kxm.migration-decision.v1 YAML file")
|
|
358
|
-
.option("--project-id <id>", "Stable project ID for controlled provisioning")
|
|
359
|
-
.option("--name <name>", "Project display name")
|
|
360
|
-
.action(async function migrateApplyAction(this: Command, options: { decisions?: string; projectId?: string; name?: string }) {
|
|
361
|
-
result.code = await cmdKxmMigrateApply(runtimeFrom(ctx, this), options);
|
|
362
|
-
});
|
|
363
|
-
addGlobalOptions(migrate.command("verify").description("Verify a migration receipt against current sources and target bundle"))
|
|
364
|
-
.action(async function migrateVerifyAction(this: Command) {
|
|
365
|
-
result.code = await cmdKxmMigrateVerify(runtimeFrom(ctx, this));
|
|
366
|
-
});
|
|
367
|
-
|
|
368
347
|
addGlobalOptions(program.command("backup").description("Create a verified SQLite backup of all stores with a hashed manifest"))
|
|
369
348
|
.option("--out <dir>", "Directory to write backup and manifest")
|
|
370
349
|
.action(async function backupAction(this: Command, options: { out?: string }) {
|
|
@@ -179,7 +179,7 @@ export function openDatabase(file: string, description: string, spec: DatabaseSc
|
|
|
179
179
|
throw databaseError(
|
|
180
180
|
"runtime_schema_outdated",
|
|
181
181
|
file,
|
|
182
|
-
`${description} is schema version ${version}; this build requires ${spec.version}. Delete the state file (
|
|
182
|
+
`${description} is schema version ${version}; this build requires ${spec.version}. Delete the state file to start fresh and let its owning process recreate it (\`kxm hub start\` for hub state, the Runtime for registry/event stores); \`kxm init\` is project-only and rebuilds no database — upgrading old state in place is deliberately unsupported`,
|
|
183
183
|
);
|
|
184
184
|
}
|
|
185
185
|
|
package/plugins/kxm/src/init.ts
CHANGED
|
@@ -207,9 +207,18 @@ function initializeKxmProjectAtGitRoot(
|
|
|
207
207
|
const loaderOptions = configOptions(options, repositoryBindings);
|
|
208
208
|
const transactionOptions = repairOptions(options, repositoryBindings);
|
|
209
209
|
|
|
210
|
+
const plan = planKxmInitialization(start, loaderOptions);
|
|
211
|
+
if (plan.mode === "legacy") {
|
|
212
|
+
// Classified **before** any transaction or lock work. A legacy tree used to reach the
|
|
213
|
+
// recovery branch first, so an interrupted create/repair journal made ordinary init
|
|
214
|
+
// acquire the project mutation lock, clean or resume the transaction, and write files
|
|
215
|
+
// while reporting `mode: "legacy"`. Nothing is resumed, cleaned, or written here: the
|
|
216
|
+
// pending journal stays exactly as it is until the legacy inputs are dealt with.
|
|
217
|
+
return { action: "planned", plan, ...(plan.projectRoot ? { projectRoot: plan.projectRoot } : {}), files: [] };
|
|
218
|
+
}
|
|
219
|
+
|
|
210
220
|
if (hasKxmInitTransaction(gitRoot)) {
|
|
211
221
|
const operation = inspectKxmInitTransaction(gitRoot, options.schemasDir);
|
|
212
|
-
const plan = planKxmInitialization(start, loaderOptions);
|
|
213
222
|
if (!operation) {
|
|
214
223
|
if (options.dryRun) return { action: "planned", plan, projectRoot: gitRoot, resumePending: true, files: [] };
|
|
215
224
|
if (!mutationLock) throw new Error("project mutation lock is required to clean an empty transaction");
|
|
@@ -267,11 +276,6 @@ function initializeKxmProjectAtGitRoot(
|
|
|
267
276
|
}
|
|
268
277
|
}
|
|
269
278
|
|
|
270
|
-
const plan = planKxmInitialization(start, loaderOptions);
|
|
271
|
-
if (plan.mode === "migrate") {
|
|
272
|
-
return { action: "planned", plan, ...(plan.projectRoot ? { projectRoot: plan.projectRoot } : {}), files: [] };
|
|
273
|
-
}
|
|
274
|
-
|
|
275
279
|
if (plan.mode === "repair") {
|
|
276
280
|
const projectRoot = plan.projectRoot ?? gitRoot;
|
|
277
281
|
let templateRepair: KxmTemplateRepairPlan | undefined;
|
|
@@ -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.54";
|
|
12
12
|
const inbox = new Map<string, MessageRecord>();
|
|
13
13
|
const notifiedInbox = new Set<string>();
|
|
14
14
|
let meshClient: HubClient | undefined;
|
|
@@ -58,7 +58,6 @@ export interface KxmProjectBundle {
|
|
|
58
58
|
environments: readonly KxmResource[];
|
|
59
59
|
gateRegistry?: KxmResource;
|
|
60
60
|
templateProvenance?: JsonObject;
|
|
61
|
-
migrationReceipt?: JsonObject;
|
|
62
61
|
resources: readonly KxmResource[];
|
|
63
62
|
configRevision: string;
|
|
64
63
|
}
|
|
@@ -69,11 +68,9 @@ export interface KxmConfigOptions {
|
|
|
69
68
|
registeredExecutors?: Iterable<string>;
|
|
70
69
|
registeredToolPresets?: Iterable<string>;
|
|
71
70
|
registeredHarnesses?: Iterable<string>;
|
|
72
|
-
/** Internal: migration apply validates the staged target before the receipt exists. */
|
|
73
|
-
allowUnreceiptedLegacyConfig?: boolean;
|
|
74
71
|
}
|
|
75
72
|
|
|
76
|
-
export type KxmInitializationMode = "create" | "
|
|
73
|
+
export type KxmInitializationMode = "create" | "legacy" | "repair" | "ready";
|
|
77
74
|
|
|
78
75
|
export interface KxmInitializationPlan {
|
|
79
76
|
mode: KxmInitializationMode;
|
|
@@ -161,9 +158,6 @@ export class KxmSchemaRegistry {
|
|
|
161
158
|
readonly localBindingsValidator: ValidateFunction;
|
|
162
159
|
readonly templateProvenanceValidator: ValidateFunction;
|
|
163
160
|
readonly initOperationValidator: ValidateFunction;
|
|
164
|
-
readonly migrationPlanValidator: ValidateFunction;
|
|
165
|
-
readonly migrationDecisionValidator: ValidateFunction;
|
|
166
|
-
readonly migrationReceiptValidator: ValidateFunction;
|
|
167
161
|
readonly permissionDiffValidator: ValidateFunction;
|
|
168
162
|
readonly runEventValidator: ValidateFunction;
|
|
169
163
|
readonly driveReceiptValidator: ValidateFunction;
|
|
@@ -181,9 +175,6 @@ export class KxmSchemaRegistry {
|
|
|
181
175
|
const localBindingsFile = "local-repository-bindings.schema.json";
|
|
182
176
|
const templateProvenanceFile = "template-provenance.schema.json";
|
|
183
177
|
const initOperationFile = "init-operation.schema.json";
|
|
184
|
-
const migrationPlanFile = "migration-plan.schema.json";
|
|
185
|
-
const migrationDecisionFile = "migration-decision.schema.json";
|
|
186
|
-
const migrationReceiptFile = "migration-receipt.schema.json";
|
|
187
178
|
const permissionDiffFile = "permission-diff.schema.json";
|
|
188
179
|
const runEventFile = "run-event.schema.json";
|
|
189
180
|
const driveReceiptFile = "drive-receipt.schema.json";
|
|
@@ -192,9 +183,6 @@ export class KxmSchemaRegistry {
|
|
|
192
183
|
this.ajv.addSchema(readJsonObject(join(this.schemasDir, localBindingsFile)));
|
|
193
184
|
this.ajv.addSchema(readJsonObject(join(this.schemasDir, templateProvenanceFile)));
|
|
194
185
|
this.ajv.addSchema(readJsonObject(join(this.schemasDir, initOperationFile)));
|
|
195
|
-
this.ajv.addSchema(readJsonObject(join(this.schemasDir, migrationPlanFile)));
|
|
196
|
-
this.ajv.addSchema(readJsonObject(join(this.schemasDir, migrationDecisionFile)));
|
|
197
|
-
this.ajv.addSchema(readJsonObject(join(this.schemasDir, migrationReceiptFile)));
|
|
198
186
|
this.ajv.addSchema(readJsonObject(join(this.schemasDir, permissionDiffFile)));
|
|
199
187
|
this.ajv.addSchema(readJsonObject(join(this.schemasDir, runEventFile)));
|
|
200
188
|
this.ajv.addSchema(readJsonObject(join(this.schemasDir, driveReceiptFile)));
|
|
@@ -208,9 +196,6 @@ export class KxmSchemaRegistry {
|
|
|
208
196
|
const localBindingsValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${localBindingsFile}`);
|
|
209
197
|
const templateProvenanceValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${templateProvenanceFile}`);
|
|
210
198
|
const initOperationValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${initOperationFile}`);
|
|
211
|
-
const migrationPlanValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${migrationPlanFile}`);
|
|
212
|
-
const migrationDecisionValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${migrationDecisionFile}`);
|
|
213
|
-
const migrationReceiptValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${migrationReceiptFile}`);
|
|
214
199
|
const permissionDiffValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${permissionDiffFile}`);
|
|
215
200
|
const runEventValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${runEventFile}`);
|
|
216
201
|
const driveReceiptValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${driveReceiptFile}`);
|
|
@@ -219,9 +204,6 @@ export class KxmSchemaRegistry {
|
|
|
219
204
|
if (!localBindingsValidator) throw new Error(`schema did not compile: ${localBindingsFile}`);
|
|
220
205
|
if (!templateProvenanceValidator) throw new Error(`schema did not compile: ${templateProvenanceFile}`);
|
|
221
206
|
if (!initOperationValidator) throw new Error(`schema did not compile: ${initOperationFile}`);
|
|
222
|
-
if (!migrationPlanValidator) throw new Error(`schema did not compile: ${migrationPlanFile}`);
|
|
223
|
-
if (!migrationDecisionValidator) throw new Error(`schema did not compile: ${migrationDecisionFile}`);
|
|
224
|
-
if (!migrationReceiptValidator) throw new Error(`schema did not compile: ${migrationReceiptFile}`);
|
|
225
207
|
if (!permissionDiffValidator) throw new Error(`schema did not compile: ${permissionDiffFile}`);
|
|
226
208
|
if (!runEventValidator) throw new Error(`schema did not compile: ${runEventFile}`);
|
|
227
209
|
if (!driveReceiptValidator) throw new Error(`schema did not compile: ${driveReceiptFile}`);
|
|
@@ -230,9 +212,6 @@ export class KxmSchemaRegistry {
|
|
|
230
212
|
this.localBindingsValidator = localBindingsValidator;
|
|
231
213
|
this.templateProvenanceValidator = templateProvenanceValidator;
|
|
232
214
|
this.initOperationValidator = initOperationValidator;
|
|
233
|
-
this.migrationPlanValidator = migrationPlanValidator;
|
|
234
|
-
this.migrationDecisionValidator = migrationDecisionValidator;
|
|
235
|
-
this.migrationReceiptValidator = migrationReceiptValidator;
|
|
236
215
|
this.permissionDiffValidator = permissionDiffValidator;
|
|
237
216
|
this.runEventValidator = runEventValidator;
|
|
238
217
|
this.driveReceiptValidator = driveReceiptValidator;
|
|
@@ -263,18 +242,6 @@ export class KxmSchemaRegistry {
|
|
|
263
242
|
return this.validateAuxiliary(value, file, "kxm.init-operation.v1", this.initOperationValidator);
|
|
264
243
|
}
|
|
265
244
|
|
|
266
|
-
validateMigrationPlan(value: JsonObject, file: string): KxmConfigIssue[] {
|
|
267
|
-
return this.validateAuxiliary(value, file, "kxm.migration-plan.v1", this.migrationPlanValidator);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
validateMigrationDecision(value: JsonObject, file: string): KxmConfigIssue[] {
|
|
271
|
-
return this.validateAuxiliary(value, file, "kxm.migration-decision.v1", this.migrationDecisionValidator);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
validateMigrationReceipt(value: JsonObject, file: string): KxmConfigIssue[] {
|
|
275
|
-
return this.validateAuxiliary(value, file, "kxm.migration-receipt.v1", this.migrationReceiptValidator);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
245
|
validatePermissionDiff(value: JsonObject, file: string): KxmConfigIssue[] {
|
|
279
246
|
return this.validateAuxiliary(value, file, "kxm.permission-diff.v1", this.permissionDiffValidator);
|
|
280
247
|
}
|
|
@@ -1279,11 +1246,17 @@ export function assertNoRegisteredGates(options: object): void {
|
|
|
1279
1246
|
export function loadKxmProject(projectRoot: string, options: KxmConfigOptions = {}): KxmProjectBundle {
|
|
1280
1247
|
assertNoRegisteredGates(options);
|
|
1281
1248
|
const root = resolve(projectRoot);
|
|
1282
|
-
|
|
1283
|
-
if (
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1249
|
+
const legacyPresent = legacyConfigFilesAt(root);
|
|
1250
|
+
if (legacyPresent.length > 0) {
|
|
1251
|
+
// Nothing can unlock this any more. The migration path was deleted under the
|
|
1252
|
+
// single-operator decision, so a tree still holding legacy JSON fails closed here
|
|
1253
|
+
// rather than being loadable on proof of a past conversion.
|
|
1254
|
+
throw new KxmConfigError(legacyPresent.map((file) => issue(
|
|
1255
|
+
"semantic",
|
|
1256
|
+
"legacy_state_unsupported",
|
|
1257
|
+
file,
|
|
1258
|
+
"legacy JSON configuration is present and this build does not migrate it: delete these files once their YAML replacements exist in .kxm/, or initialise a fresh project",
|
|
1259
|
+
)));
|
|
1287
1260
|
}
|
|
1288
1261
|
const registry = new KxmSchemaRegistry(options.schemasDir);
|
|
1289
1262
|
const project = readResource(registry, root, join(root, ".kxm", "project.yaml"), ".kxm/project.yaml", "project");
|
|
@@ -1296,9 +1269,6 @@ export function loadKxmProject(projectRoot: string, options: KxmConfigOptions =
|
|
|
1296
1269
|
earlyIssues.push(issue("semantic", "template_provenance_project_mismatch", ".kxm/template-provenance.yaml", "template provenance belongs to a different project identity"));
|
|
1297
1270
|
}
|
|
1298
1271
|
}
|
|
1299
|
-
if (migrationReceipt && stringValue(migrationReceipt.projectId) !== stringValue(project.value.id)) {
|
|
1300
|
-
earlyIssues.push(issue("semantic", "migration_project_mismatch", KXM_MIGRATION_RECEIPT_PATH, "migration receipt belongs to a different project identity"));
|
|
1301
|
-
}
|
|
1302
1272
|
const declaredRepositoryIds = new Set(valuesOf(project.value, "repositories")
|
|
1303
1273
|
.map((candidate) => stringValue(objectValue(candidate)?.id))
|
|
1304
1274
|
.filter((candidate): candidate is string => candidate !== undefined));
|
|
@@ -1383,6 +1353,18 @@ export function loadKxmProject(projectRoot: string, options: KxmConfigOptions =
|
|
|
1383
1353
|
continue;
|
|
1384
1354
|
}
|
|
1385
1355
|
}
|
|
1356
|
+
// A member worktree is authoritative for its own repo.yaml/env.yaml, so the legacy
|
|
1357
|
+
// refusal has to cover it too — checking only the control root let a member full of
|
|
1358
|
+
// legacy JSON load normally. Throws before a single byte of that member is read.
|
|
1359
|
+
const memberLegacy = binding === root ? [] : legacyConfigFilesAt(binding);
|
|
1360
|
+
if (memberLegacy.length > 0) {
|
|
1361
|
+
throw new KxmConfigError(memberLegacy.map((file) => issue(
|
|
1362
|
+
"semantic",
|
|
1363
|
+
"legacy_state_unsupported",
|
|
1364
|
+
`${repositoryId}/${file}`,
|
|
1365
|
+
`repository ${repositoryId} holds legacy JSON configuration and this build does not migrate it: delete those files once their YAML replacements exist, or bind a clean worktree`,
|
|
1366
|
+
)));
|
|
1367
|
+
}
|
|
1386
1368
|
const foldedBinding = canonicalHostPath(binding).toLocaleLowerCase("en-US");
|
|
1387
1369
|
const priorBinding = seenBindings.get(foldedBinding);
|
|
1388
1370
|
if (priorBinding && priorBinding !== repositoryId) {
|
|
@@ -1434,64 +1416,12 @@ export function loadKxmProject(projectRoot: string, options: KxmConfigOptions =
|
|
|
1434
1416
|
environments,
|
|
1435
1417
|
...(gateRegistry ? { gateRegistry } : {}),
|
|
1436
1418
|
...(templateProvenance === undefined ? {} : { templateProvenance }),
|
|
1437
|
-
...(migrationReceipt === undefined ? {} : { migrationReceipt }),
|
|
1438
1419
|
resources,
|
|
1439
1420
|
configRevision: bundleRevision(resources),
|
|
1440
1421
|
};
|
|
1441
1422
|
}
|
|
1442
1423
|
|
|
1443
|
-
/**
|
|
1444
|
-
* Fully verify a migration receipt against the loaded target bundle: target
|
|
1445
|
-
* revision and per-resource bytes. Load-time coexistence only pins the
|
|
1446
|
-
* legacy sources; this stricter check backs `kxm migrate verify`.
|
|
1447
|
-
*/
|
|
1448
|
-
export function verifyKxmMigrationReceiptTarget(
|
|
1449
|
-
root: string,
|
|
1450
|
-
receipt: JsonObject,
|
|
1451
|
-
resources: readonly KxmResource[],
|
|
1452
|
-
configRevision: string,
|
|
1453
|
-
): KxmConfigIssue[] {
|
|
1454
|
-
const issues: KxmConfigIssue[] = [];
|
|
1455
|
-
if (receipt.configRevision !== configRevision) {
|
|
1456
|
-
issues.push(issue("semantic", "migration_target_changed", KXM_MIGRATION_RECEIPT_PATH, "target configuration revision no longer matches the migration receipt"));
|
|
1457
|
-
}
|
|
1458
|
-
const declared = new Map<string, JsonObject>();
|
|
1459
|
-
for (const candidate of Array.isArray(receipt.resources) ? receipt.resources : []) {
|
|
1460
|
-
if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) {
|
|
1461
|
-
const record = candidate as JsonObject;
|
|
1462
|
-
if (typeof record.path === "string") declared.set(record.path, record);
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
const installedPaths = new Set<string>();
|
|
1466
|
-
for (const resource of resources) {
|
|
1467
|
-
// Repository resources are loaded from their bound worktree paths; only
|
|
1468
|
-
// control-tree files participate in receipt byte checks.
|
|
1469
|
-
const relative = resource.file.startsWith(root) ? relativePortable(root, resource.file) : undefined;
|
|
1470
|
-
if (!relative) continue;
|
|
1471
|
-
installedPaths.add(relative);
|
|
1472
|
-
const record = declared.get(relative);
|
|
1473
|
-
if (!record) {
|
|
1474
|
-
issues.push(issue("semantic", "migration_target_unreceipted", relative, "installed resource is not covered by the migration receipt"));
|
|
1475
|
-
continue;
|
|
1476
|
-
}
|
|
1477
|
-
const current = hashFileRecord(root, relative);
|
|
1478
|
-
if (!current || current.sha256 !== record.sha256 || current.bytes !== record.bytes) {
|
|
1479
|
-
issues.push(issue("semantic", "migration_target_modified", relative, "installed resource bytes differ from the migration receipt"));
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
for (const path of declared.keys()) {
|
|
1483
|
-
if (!installedPaths.has(path)) {
|
|
1484
|
-
issues.push(issue("semantic", "migration_target_missing", path, "receipt-covered resource is not part of the validated configuration"));
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
return sortIssues(issues);
|
|
1488
|
-
}
|
|
1489
1424
|
|
|
1490
|
-
function relativePortable(root: string, absolute: string): string | undefined {
|
|
1491
|
-
const rel = relative(root, absolute);
|
|
1492
|
-
if (!rel || isAbsolute(rel) || rel === ".." || rel.startsWith(`..${sep}`)) return undefined;
|
|
1493
|
-
return rel.split(sep).join("/");
|
|
1494
|
-
}
|
|
1495
1425
|
|
|
1496
1426
|
function legacyInputsAt(root: string): string[] {
|
|
1497
1427
|
const candidates = [
|
|
@@ -1503,7 +1433,6 @@ function legacyInputsAt(root: string): string[] {
|
|
|
1503
1433
|
return candidates.filter((candidate) => existsSync(join(root, ...candidate.split("/"))));
|
|
1504
1434
|
}
|
|
1505
1435
|
|
|
1506
|
-
export const KXM_MIGRATION_RECEIPT_PATH = ".kxm/migration-receipt.yaml";
|
|
1507
1436
|
const LEGACY_CONFIG_FILES = [".kxm/config/agents.json", ".kxm/config/gates.json"] as const;
|
|
1508
1437
|
|
|
1509
1438
|
/** Individual legacy configuration files (not directories, not runtime state) that a migration receipt must cover. */
|
|
@@ -1540,83 +1469,9 @@ export function legacyConfigFilesAt(root: string): string[] {
|
|
|
1540
1469
|
return files;
|
|
1541
1470
|
}
|
|
1542
1471
|
|
|
1543
|
-
export interface KxmMigrationReceiptCheck {
|
|
1544
|
-
receipt?: JsonObject;
|
|
1545
|
-
issues: KxmConfigIssue[];
|
|
1546
|
-
}
|
|
1547
1472
|
|
|
1548
|
-
function hashFileRecord(root: string, relativePath: string): { path: string; sha256: string; bytes: number } | undefined {
|
|
1549
|
-
const absolute = join(root, ...relativePath.split("/"));
|
|
1550
|
-
if (!existsSync(absolute)) return undefined;
|
|
1551
|
-
const bytes = readFileSync(absolute);
|
|
1552
|
-
return {
|
|
1553
|
-
path: relativePath,
|
|
1554
|
-
sha256: `sha256:${createHash("sha256").update(bytes).digest("hex")}`,
|
|
1555
|
-
bytes: bytes.byteLength,
|
|
1556
|
-
};
|
|
1557
|
-
}
|
|
1558
1473
|
|
|
1559
|
-
export function migrationReceiptSelfHash(receipt: JsonObject): string {
|
|
1560
|
-
const { receiptSha256: _ignored, ...unsigned } = receipt;
|
|
1561
|
-
return `sha256:${createHash("sha256").update(kxmCanonicalJson(unsigned as JsonObject), "utf8").digest("hex")}`;
|
|
1562
|
-
}
|
|
1563
1474
|
|
|
1564
|
-
/**
|
|
1565
|
-
* Read and structurally verify a migration receipt: schema, self-hash, and
|
|
1566
|
-
* exact coverage of the legacy configuration files still present. Does not
|
|
1567
|
-
* compare the target configuration revision (computed by the caller).
|
|
1568
|
-
*/
|
|
1569
|
-
export function readKxmMigrationReceipt(root: string, options: KxmConfigOptions = {}): KxmMigrationReceiptCheck {
|
|
1570
|
-
const receiptPath = join(root, ...KXM_MIGRATION_RECEIPT_PATH.split("/"));
|
|
1571
|
-
const legacyFiles = legacyConfigFilesAt(root);
|
|
1572
|
-
if (!existsSync(receiptPath)) {
|
|
1573
|
-
return {
|
|
1574
|
-
issues: legacyFiles.map((file) => issue("semantic", "legacy_kxm_conflict", file, "legacy and KXM configuration cannot coexist before an accepted migration receipt")),
|
|
1575
|
-
};
|
|
1576
|
-
}
|
|
1577
|
-
const receiptStat = lstatSync(receiptPath);
|
|
1578
|
-
if (receiptStat.isSymbolicLink() || !receiptStat.isFile()) {
|
|
1579
|
-
return { issues: [issue("path", "migration_receipt_invalid", KXM_MIGRATION_RECEIPT_PATH, "migration receipt must be a regular file, not a link")] };
|
|
1580
|
-
}
|
|
1581
|
-
let receipt: JsonObject;
|
|
1582
|
-
try {
|
|
1583
|
-
receipt = parseRestrictedYaml(readFileSync(receiptPath), KXM_MIGRATION_RECEIPT_PATH);
|
|
1584
|
-
} catch (error) {
|
|
1585
|
-
if (error instanceof KxmConfigError) return { issues: [...error.issues] };
|
|
1586
|
-
throw error;
|
|
1587
|
-
}
|
|
1588
|
-
const registry = new KxmSchemaRegistry(options.schemasDir);
|
|
1589
|
-
const schemaIssues = registry.validateMigrationReceipt(receipt, KXM_MIGRATION_RECEIPT_PATH);
|
|
1590
|
-
if (schemaIssues.length > 0) return { issues: schemaIssues };
|
|
1591
|
-
if (receipt.receiptSha256 !== migrationReceiptSelfHash(receipt)) {
|
|
1592
|
-
return { issues: [issue("semantic", "migration_receipt_hash_mismatch", KXM_MIGRATION_RECEIPT_PATH, "receipt self-hash does not match its content")] };
|
|
1593
|
-
}
|
|
1594
|
-
const sources = (Array.isArray(receipt.sources) ? receipt.sources : [])
|
|
1595
|
-
.map((candidate) => (candidate && typeof candidate === "object" && !Array.isArray(candidate) ? (candidate as JsonObject).path : undefined))
|
|
1596
|
-
.filter((candidate): candidate is string => typeof candidate === "string")
|
|
1597
|
-
.sort(compareCodeUnits);
|
|
1598
|
-
const issues: KxmConfigIssue[] = [];
|
|
1599
|
-
const declared = new Set(sources);
|
|
1600
|
-
for (const file of legacyFiles) {
|
|
1601
|
-
if (!declared.has(file)) {
|
|
1602
|
-
issues.push(issue("semantic", "migration_source_unmigrated", file, "legacy configuration file is not covered by the migration receipt"));
|
|
1603
|
-
continue;
|
|
1604
|
-
}
|
|
1605
|
-
const record = (Array.isArray(receipt.sources) ? receipt.sources : [])
|
|
1606
|
-
.map((candidate) => (candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate as JsonObject : undefined))
|
|
1607
|
-
.find((candidate) => candidate?.path === file);
|
|
1608
|
-
const current = hashFileRecord(root, file);
|
|
1609
|
-
if (!current || current.sha256 !== record?.sha256 || current.bytes !== record?.bytes) {
|
|
1610
|
-
issues.push(issue("semantic", "migration_source_changed", file, "legacy configuration changed after the migration receipt was issued"));
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
for (const file of sources) {
|
|
1614
|
-
if (!legacyFiles.includes(file)) {
|
|
1615
|
-
issues.push(issue("semantic", "migration_source_missing", file, "receipt covers a legacy configuration file that no longer exists"));
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
return issues.length > 0 ? { issues } : { receipt, issues: [] };
|
|
1619
|
-
}
|
|
1620
1475
|
|
|
1621
1476
|
function discoverLegacyRoot(start: string): { root: string; inputs: string[] } | undefined {
|
|
1622
1477
|
let initial = resolve(start);
|
|
@@ -1628,7 +1483,11 @@ function discoverLegacyRoot(start: string): { root: string; inputs: string[] } |
|
|
|
1628
1483
|
return inputs.length > 0 ? { root: candidate, inputs } : undefined;
|
|
1629
1484
|
}
|
|
1630
1485
|
|
|
1631
|
-
/**
|
|
1486
|
+
/**
|
|
1487
|
+
* Classify init without changing files. Applying create/repair is a separate, explicit
|
|
1488
|
+
* operation; a legacy-only tree is **reported and never written** — this build carries no
|
|
1489
|
+
* migration path, so init says what to do instead of doing it.
|
|
1490
|
+
*/
|
|
1632
1491
|
export function planKxmInitialization(start = process.cwd(), options: KxmConfigOptions = {}): KxmInitializationPlan {
|
|
1633
1492
|
const inspectedFrom = resolve(start);
|
|
1634
1493
|
const projectRoot = discoverKxmProjectRoot(inspectedFrom);
|
|
@@ -1638,16 +1497,38 @@ export function planKxmInitialization(start = process.cwd(), options: KxmConfigO
|
|
|
1638
1497
|
return { mode: "ready", inspectedFrom, projectRoot, changesRequired: false, issues: [], legacyInputs: [], configRevision: bundle.configRevision };
|
|
1639
1498
|
} catch (error) {
|
|
1640
1499
|
if (!(error instanceof KxmConfigError)) throw error;
|
|
1500
|
+
const legacyIssues = error.issues.filter((candidate) => candidate.code === "legacy_state_unsupported");
|
|
1641
1501
|
const legacyInputs = legacyInputsAt(projectRoot);
|
|
1642
|
-
if (legacyInputs.length > 0) {
|
|
1643
|
-
|
|
1502
|
+
if (legacyInputs.length > 0 || legacyIssues.length > 0) {
|
|
1503
|
+
// Terminal for **any** legacy input, including one that lives in a bound member
|
|
1504
|
+
// worktree. Classifying a member's legacy JSON as `repair` let init take the
|
|
1505
|
+
// mutation lock and resume an interrupted create/repair journal, installing
|
|
1506
|
+
// resources into a tree the loader had just refused.
|
|
1507
|
+
return {
|
|
1508
|
+
mode: "legacy",
|
|
1509
|
+
inspectedFrom,
|
|
1510
|
+
projectRoot,
|
|
1511
|
+
legacyRoot: projectRoot,
|
|
1512
|
+
changesRequired: true,
|
|
1513
|
+
issues: [...error.issues, ...(legacyInputs.length > 0
|
|
1514
|
+
? [issue("discovery", "legacy_state_unsupported", ".kxm", "legacy configuration is present and this build does not migrate it: initialise a fresh project directory and carry over the YAML definitions you want to keep")]
|
|
1515
|
+
: [])],
|
|
1516
|
+
legacyInputs: [...new Set([...legacyInputs, ...legacyIssues.map((candidate) => candidate.file)])],
|
|
1517
|
+
};
|
|
1644
1518
|
}
|
|
1645
1519
|
return { mode: "repair", inspectedFrom, projectRoot, changesRequired: true, issues: error.issues, legacyInputs: [] };
|
|
1646
1520
|
}
|
|
1647
1521
|
}
|
|
1648
1522
|
const legacy = discoverLegacyRoot(inspectedFrom);
|
|
1649
1523
|
if (legacy) {
|
|
1650
|
-
return {
|
|
1524
|
+
return {
|
|
1525
|
+
mode: "legacy",
|
|
1526
|
+
inspectedFrom,
|
|
1527
|
+
legacyRoot: legacy.root,
|
|
1528
|
+
changesRequired: true,
|
|
1529
|
+
issues: [issue("discovery", "legacy_state_unsupported", legacy.root, "legacy KXM/Mesh configuration discovered; this build does not migrate it — initialise a fresh project directory and copy the YAML definitions you want to keep")],
|
|
1530
|
+
legacyInputs: legacy.inputs,
|
|
1531
|
+
};
|
|
1651
1532
|
}
|
|
1652
1533
|
const gitRoot = discoverGitRoot(inspectedFrom);
|
|
1653
1534
|
const candidateRoot = gitRoot ?? inspectedFrom;
|
package/schemas/README.md
CHANGED
|
@@ -14,9 +14,6 @@ Runtime-local records, and JSON events/results for the planned KXM contract.
|
|
|
14
14
|
| `template-provenance.schema.json` | `kxm.template-provenance.v1` |
|
|
15
15
|
| `local-repository-bindings.schema.json` | `kxm.local-repository-bindings.v1` |
|
|
16
16
|
| `init-operation.schema.json` | `kxm.init-operation.v1` |
|
|
17
|
-
| `migration-plan.schema.json` | `kxm.migration-plan.v1` |
|
|
18
|
-
| `migration-decision.schema.json` | `kxm.migration-decision.v1` |
|
|
19
|
-
| `migration-receipt.schema.json` | `kxm.migration-receipt.v1` |
|
|
20
17
|
| `permission-diff.schema.json` | `kxm.permission-diff.v1` |
|
|
21
18
|
| `run-event.schema.json` | `kxm.run-event.v1` |
|
|
22
19
|
| `coordinator.schema.json` | `kxm.coordinator.v1` |
|