@klickd/core 4.0.0-preview.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/index.cjs +264 -2
- package/dist/index.d.cts +162 -1
- package/dist/index.d.ts +162 -1
- package/dist/index.js +256 -1
- package/dist/klickd-payload-v4-preview.schema-W7RY72VP.json +98 -0
- package/dist/klickd-payload-v4.schema-7UPTEJOY.json +354 -0
- package/dist/klickd-v4-preview.schema-QHITWMK6.json +95 -0
- package/dist/klickd-v4.schema-ZZGRYTLY.json +129 -0
- package/package.json +13 -4
package/dist/index.js
CHANGED
|
@@ -282,9 +282,264 @@ async function loadKlickd(input, options = {}) {
|
|
|
282
282
|
}
|
|
283
283
|
return payload;
|
|
284
284
|
}
|
|
285
|
+
|
|
286
|
+
// src/validate.ts
|
|
287
|
+
import payloadStrict from "./klickd-payload-v4.schema-7UPTEJOY.json";
|
|
288
|
+
import payloadPreview from "./klickd-payload-v4-preview.schema-W7RY72VP.json";
|
|
289
|
+
import unifiedStrict from "./klickd-v4.schema-ZZGRYTLY.json";
|
|
290
|
+
import unifiedPreview from "./klickd-v4-preview.schema-QHITWMK6.json";
|
|
291
|
+
var SCHEMAS = {
|
|
292
|
+
"payload-strict": payloadStrict,
|
|
293
|
+
"payload-preview": payloadPreview,
|
|
294
|
+
"unified-strict": unifiedStrict,
|
|
295
|
+
"unified-preview": unifiedPreview
|
|
296
|
+
};
|
|
297
|
+
function getBundledSchema(key) {
|
|
298
|
+
const s = SCHEMAS[key];
|
|
299
|
+
if (!s) {
|
|
300
|
+
throw new Error(`Unknown schema key: ${String(key)}`);
|
|
301
|
+
}
|
|
302
|
+
return JSON.parse(JSON.stringify(s));
|
|
303
|
+
}
|
|
304
|
+
function listBundledSchemas() {
|
|
305
|
+
return Object.keys(SCHEMAS);
|
|
306
|
+
}
|
|
307
|
+
var ajvBundle = null;
|
|
308
|
+
async function getAjv() {
|
|
309
|
+
if (ajvBundle) return ajvBundle;
|
|
310
|
+
let AjvCtor;
|
|
311
|
+
try {
|
|
312
|
+
const mod = await import("ajv/dist/2020.js");
|
|
313
|
+
AjvCtor = mod.default ?? mod;
|
|
314
|
+
} catch (e) {
|
|
315
|
+
throw new KlickdError(
|
|
316
|
+
"KLICKD_E_SCHEMA",
|
|
317
|
+
"validate() requires the optional 'ajv' dependency (>=8.12, Draft 2020-12). Install it with: npm install ajv",
|
|
318
|
+
HTTP_STATUS["KLICKD_E_SCHEMA"]
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
const ajv = new AjvCtor({ allErrors: true, strict: false, validateFormats: false });
|
|
322
|
+
for (const key of listBundledSchemas()) {
|
|
323
|
+
ajv.addSchema(SCHEMAS[key], `klickd:${key}`);
|
|
324
|
+
}
|
|
325
|
+
const validators = /* @__PURE__ */ new Map();
|
|
326
|
+
for (const key of listBundledSchemas()) {
|
|
327
|
+
validators.set(key, ajv.compile(SCHEMAS[key]));
|
|
328
|
+
}
|
|
329
|
+
ajvBundle = { validators };
|
|
330
|
+
return ajvBundle;
|
|
331
|
+
}
|
|
332
|
+
function keyFor(target, strict) {
|
|
333
|
+
return `${target}-${strict ? "strict" : "preview"}`;
|
|
334
|
+
}
|
|
335
|
+
function formatPath(instancePath) {
|
|
336
|
+
if (!instancePath || instancePath === "") return "<root>";
|
|
337
|
+
return instancePath.replace(/^\//, "");
|
|
338
|
+
}
|
|
339
|
+
async function validate(payload, options = {}) {
|
|
340
|
+
const strict = options.strict ?? true;
|
|
341
|
+
const target = options.target ?? "payload";
|
|
342
|
+
if (target !== "payload" && target !== "unified") {
|
|
343
|
+
throw new Error(`target must be 'payload' or 'unified', got ${String(target)}`);
|
|
344
|
+
}
|
|
345
|
+
const { validators } = await getAjv();
|
|
346
|
+
const validator = validators.get(keyFor(target, strict));
|
|
347
|
+
if (!validator) {
|
|
348
|
+
throw new Error(`No bundled validator for ${target}-${strict ? "strict" : "preview"}`);
|
|
349
|
+
}
|
|
350
|
+
if (validator(payload)) return;
|
|
351
|
+
const errors = validator.errors ?? [];
|
|
352
|
+
const summary = errors.slice(0, 8).map((e) => `${formatPath(e.instancePath)}: ${(e.message ?? "").slice(0, 200)}`);
|
|
353
|
+
const extra = errors.length > 8 ? ` (+${errors.length - 8} more)` : "";
|
|
354
|
+
throw new KlickdError(
|
|
355
|
+
"KLICKD_E_SCHEMA",
|
|
356
|
+
`v4 ${strict ? "strict" : "preview"} ${target} validation failed${extra}: ${summary.join(" | ")}`,
|
|
357
|
+
HTTP_STATUS["KLICKD_E_SCHEMA"]
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
async function validateIterErrors(payload, options = {}) {
|
|
361
|
+
const strict = options.strict ?? true;
|
|
362
|
+
const target = options.target ?? "payload";
|
|
363
|
+
if (target !== "payload" && target !== "unified") {
|
|
364
|
+
throw new Error(`target must be 'payload' or 'unified', got ${String(target)}`);
|
|
365
|
+
}
|
|
366
|
+
const { validators } = await getAjv();
|
|
367
|
+
const validator = validators.get(keyFor(target, strict));
|
|
368
|
+
if (!validator) {
|
|
369
|
+
throw new Error(`No bundled validator for ${target}-${strict ? "strict" : "preview"}`);
|
|
370
|
+
}
|
|
371
|
+
if (validator(payload)) return [];
|
|
372
|
+
return (validator.errors ?? []).map((e) => ({
|
|
373
|
+
path: formatPath(e.instancePath),
|
|
374
|
+
message: e.message ?? ""
|
|
375
|
+
}));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/migrate.ts
|
|
379
|
+
var V3_SCHEMA_VERSIONS = /* @__PURE__ */ new Set([
|
|
380
|
+
"3.0",
|
|
381
|
+
"3.1",
|
|
382
|
+
"3.2",
|
|
383
|
+
"3.3",
|
|
384
|
+
"3.4",
|
|
385
|
+
"3.5"
|
|
386
|
+
]);
|
|
387
|
+
var V4_SCHEMA_VERSIONS = /* @__PURE__ */ new Set(["4.0", "4.0.0-preview.1"]);
|
|
388
|
+
var RESERVED_PROFILE_KINDS = /* @__PURE__ */ new Set([
|
|
389
|
+
"learner",
|
|
390
|
+
"agent",
|
|
391
|
+
"team",
|
|
392
|
+
"robot",
|
|
393
|
+
"creator"
|
|
394
|
+
]);
|
|
395
|
+
function isPlainObject(value) {
|
|
396
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
397
|
+
}
|
|
398
|
+
function utcNowIso() {
|
|
399
|
+
const now = /* @__PURE__ */ new Date();
|
|
400
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
401
|
+
return `${now.getUTCFullYear()}-${pad(now.getUTCMonth() + 1)}-${pad(now.getUTCDate())}T${pad(now.getUTCHours())}:${pad(now.getUTCMinutes())}:${pad(now.getUTCSeconds())}Z`;
|
|
402
|
+
}
|
|
403
|
+
function deepClone(value) {
|
|
404
|
+
return structuredClone(value);
|
|
405
|
+
}
|
|
406
|
+
function needsMigration(payload) {
|
|
407
|
+
if (!isPlainObject(payload)) return false;
|
|
408
|
+
const ver = payload["payload_schema_version"];
|
|
409
|
+
if (ver === void 0 || ver === null) return true;
|
|
410
|
+
if (typeof ver !== "string") return false;
|
|
411
|
+
if (V4_SCHEMA_VERSIONS.has(ver)) return false;
|
|
412
|
+
if (V3_SCHEMA_VERSIONS.has(ver)) return true;
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
function migratePayload(payload, options = {}) {
|
|
416
|
+
if (!isPlainObject(payload)) {
|
|
417
|
+
throw new KlickdError(
|
|
418
|
+
"KLICKD_E_SCHEMA",
|
|
419
|
+
`migratePayload requires a plain object payload; got ${typeof payload}`,
|
|
420
|
+
HTTP_STATUS["KLICKD_E_SCHEMA"]
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
const incomingVersion = payload["payload_schema_version"];
|
|
424
|
+
if (incomingVersion !== void 0 && typeof incomingVersion === "string" && !V3_SCHEMA_VERSIONS.has(incomingVersion) && !V4_SCHEMA_VERSIONS.has(incomingVersion)) {
|
|
425
|
+
throw new KlickdError(
|
|
426
|
+
"KLICKD_E_SCHEMA",
|
|
427
|
+
`migratePayload does not recognize payload_schema_version=${JSON.stringify(incomingVersion)}; expected v3.x (3.0..3.5) or v4 (4.0 / 4.0.0-preview.1)`,
|
|
428
|
+
HTTP_STATUS["KLICKD_E_SCHEMA"]
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
const out = deepClone(payload);
|
|
432
|
+
const {
|
|
433
|
+
sourceVersion,
|
|
434
|
+
migratedAt,
|
|
435
|
+
profileKind = "learner",
|
|
436
|
+
migrationReportRef,
|
|
437
|
+
backupRef
|
|
438
|
+
} = options;
|
|
439
|
+
if (typeof incomingVersion === "string" && V4_SCHEMA_VERSIONS.has(incomingVersion)) {
|
|
440
|
+
if (migrationReportRef === void 0 && backupRef === void 0) {
|
|
441
|
+
return out;
|
|
442
|
+
}
|
|
443
|
+
const existing = isPlainObject(out["migration"]) ? out["migration"] : {};
|
|
444
|
+
if (migrationReportRef !== void 0) {
|
|
445
|
+
existing["migration_report_ref"] = migrationReportRef;
|
|
446
|
+
}
|
|
447
|
+
if (backupRef !== void 0) {
|
|
448
|
+
existing["backup_ref"] = backupRef;
|
|
449
|
+
}
|
|
450
|
+
if (existing["source_version"] === void 0) {
|
|
451
|
+
existing["source_version"] = incomingVersion;
|
|
452
|
+
}
|
|
453
|
+
existing["migrated_at"] = migratedAt ?? utcNowIso();
|
|
454
|
+
out["migration"] = existing;
|
|
455
|
+
return out;
|
|
456
|
+
}
|
|
457
|
+
out["payload_schema_version"] = "4.0";
|
|
458
|
+
if (out["profile_kind"] === void 0) {
|
|
459
|
+
out["profile_kind"] = profileKind;
|
|
460
|
+
}
|
|
461
|
+
const migrationBlock = {
|
|
462
|
+
source_version: sourceVersion ?? (typeof incomingVersion === "string" ? incomingVersion : "3.x"),
|
|
463
|
+
migrated_at: migratedAt ?? utcNowIso()
|
|
464
|
+
};
|
|
465
|
+
if (migrationReportRef !== void 0) {
|
|
466
|
+
migrationBlock["migration_report_ref"] = migrationReportRef;
|
|
467
|
+
}
|
|
468
|
+
if (backupRef !== void 0) {
|
|
469
|
+
migrationBlock["backup_ref"] = backupRef;
|
|
470
|
+
}
|
|
471
|
+
out["migration"] = migrationBlock;
|
|
472
|
+
return out;
|
|
473
|
+
}
|
|
474
|
+
function migratePayloadIterWarnings(payload) {
|
|
475
|
+
const warnings = [];
|
|
476
|
+
if (!isPlainObject(payload)) {
|
|
477
|
+
warnings.push({ path: "<root>", message: "payload is not a JSON object" });
|
|
478
|
+
return warnings;
|
|
479
|
+
}
|
|
480
|
+
const ver = payload["payload_schema_version"];
|
|
481
|
+
if (ver === void 0) {
|
|
482
|
+
if (payload["domain_schema_version"] === void 0) {
|
|
483
|
+
warnings.push({
|
|
484
|
+
path: "<root>",
|
|
485
|
+
message: "no payload_schema_version and no domain_schema_version; pin sourceVersion explicitly when migrating"
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
} else if (typeof ver === "string" && !V3_SCHEMA_VERSIONS.has(ver) && !V4_SCHEMA_VERSIONS.has(ver)) {
|
|
489
|
+
warnings.push({
|
|
490
|
+
path: "/payload_schema_version",
|
|
491
|
+
message: `unknown payload_schema_version ${JSON.stringify(ver)}; migrator will refuse`
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
const ctx = payload["context"];
|
|
495
|
+
if (isPlainObject(ctx)) {
|
|
496
|
+
const decisions = ctx["decisions_locked"];
|
|
497
|
+
if (Array.isArray(decisions)) {
|
|
498
|
+
decisions.forEach((d, i) => {
|
|
499
|
+
if (typeof d === "string" && d.length > 1024) {
|
|
500
|
+
warnings.push({
|
|
501
|
+
path: `/context/decisions_locked/${i}`,
|
|
502
|
+
message: `entry exceeds 1024 chars (${d.length}); some readers will truncate`
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
const ethics = payload["ethics"];
|
|
509
|
+
const veto = payload["human_veto_policy"];
|
|
510
|
+
if (isPlainObject(ethics) && isPlainObject(veto)) {
|
|
511
|
+
const locked = ethics["locked_actions"];
|
|
512
|
+
const appliesTo = veto["applies_to"];
|
|
513
|
+
if (Array.isArray(locked) && Array.isArray(appliesTo)) {
|
|
514
|
+
const lockedSet = new Set(locked.filter((x) => typeof x === "string"));
|
|
515
|
+
const overlap = appliesTo.filter((x) => typeof x === "string" && lockedSet.has(x)).sort();
|
|
516
|
+
if (overlap.length > 0) {
|
|
517
|
+
warnings.push({
|
|
518
|
+
path: "/human_veto_policy/applies_to",
|
|
519
|
+
message: "overlaps with /ethics/locked_actions: " + overlap.map((x) => JSON.stringify(x)).join(", ")
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
const pk = payload["profile_kind"];
|
|
525
|
+
if (typeof pk === "string" && !RESERVED_PROFILE_KINDS.has(pk)) {
|
|
526
|
+
warnings.push({
|
|
527
|
+
path: "/profile_kind",
|
|
528
|
+
message: `non-reserved profile_kind ${JSON.stringify(pk)}; readers MAY treat as extension`
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
return warnings;
|
|
532
|
+
}
|
|
285
533
|
export {
|
|
286
534
|
HTTP_STATUS,
|
|
287
535
|
KlickdError,
|
|
536
|
+
getBundledSchema,
|
|
537
|
+
listBundledSchemas,
|
|
288
538
|
loadKlickd,
|
|
289
|
-
|
|
539
|
+
migratePayload,
|
|
540
|
+
migratePayloadIterWarnings,
|
|
541
|
+
needsMigration,
|
|
542
|
+
saveKlickd,
|
|
543
|
+
validate,
|
|
544
|
+
validateIterErrors
|
|
290
545
|
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://klickd.app/schemas/v4-preview/klickd-payload.schema.json",
|
|
4
|
+
"title": "klickd Payload v4 Preview (PERMISSIVE, NON-NORMATIVE)",
|
|
5
|
+
"description": "PREVIEW schema for the .klickd v4 inner payload, targeting v4.0.0-preview.1. This schema is INTENTIONALLY PERMISSIVE: it accepts and preserves draft v4 structures (media_profile, verification_gates, human_veto_policy, claim_sources, verification_artifacts, migration, context_cost, profile_kind) without performing strict validation. additionalProperties is true at every level so that unknown fields round-trip verbatim. This schema MUST NOT be used to reject a file that is otherwise a valid v3.5.1 file. The normative schemas remain klickd-envelope-v3.schema.json and klickd-payload-v3.schema.json. See SPEC.md §33 and docs/rfcs/ for the design source.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": true,
|
|
8
|
+
"properties": {
|
|
9
|
+
"preview": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Marks a file as belonging to a specific preview iteration. Recommended value for files written against this schema: 'v4.0.0-preview.1'. Absent in v3.x files."
|
|
12
|
+
},
|
|
13
|
+
"payload_schema_version": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Payload schema version. PERMISSIVE in this preview: any string is accepted. A future strict v4 schema MAY pin this to a concrete value."
|
|
16
|
+
},
|
|
17
|
+
"domain_schema_version": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Domain-specific schema version (e.g. 'education-1.2'). PERMISSIVE in this preview."
|
|
20
|
+
},
|
|
21
|
+
"profile_kind": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Top-level discriminator for the profile shape. Common values: 'learner', 'agent', 'team', 'robot'. v3.x is implicitly 'learner'; the preview makes this explicit and extensible. Custom strings are permitted."
|
|
24
|
+
},
|
|
25
|
+
"media_profile": {
|
|
26
|
+
"description": "RFC-001: portable, hash-referenced media context. Bytes live outside the .klickd file by default; the payload carries metadata, hashes, and (optionally) inline base64 below a documented threshold. PERMISSIVE: structure is not enforced here.",
|
|
27
|
+
"type": ["object", "array", "null"],
|
|
28
|
+
"additionalProperties": true
|
|
29
|
+
},
|
|
30
|
+
"verification_gates": {
|
|
31
|
+
"description": "RFC-002 v1: user's preferred friction profile, mapping action class to gate level (silent / warn / confirm / block / require-owner). PERMISSIVE: not enforced here.",
|
|
32
|
+
"type": ["object", "array", "null"],
|
|
33
|
+
"additionalProperties": true
|
|
34
|
+
},
|
|
35
|
+
"human_veto_policy": {
|
|
36
|
+
"description": "RFC-002 v1: standing rules about when a human MUST be in the loop, regardless of agent confidence. Overrides any lower gate. PERMISSIVE: not enforced here.",
|
|
37
|
+
"type": ["object", "null"],
|
|
38
|
+
"additionalProperties": true
|
|
39
|
+
},
|
|
40
|
+
"claim_sources": {
|
|
41
|
+
"description": "RFC-002 v1 + v2-additive: declarative preferences for where to ground factual claims, and records of what was actually used. PERMISSIVE: not enforced here.",
|
|
42
|
+
"type": ["object", "null"],
|
|
43
|
+
"additionalProperties": true
|
|
44
|
+
},
|
|
45
|
+
"verification_artifacts": {
|
|
46
|
+
"description": "RFC-002 §8b.8: pointer ledger of outputs already produced by expensive verification commands (test suites, builds, web fetches, DOI resolutions). MUST be a pointer ledger, not a payload sink. PERMISSIVE: structure is not enforced here.",
|
|
47
|
+
"type": ["array", "null"]
|
|
48
|
+
},
|
|
49
|
+
"error_journal": {
|
|
50
|
+
"description": "RFC-002 v1: append-only lessons learned that should influence future gate evaluation. PERMISSIVE: not enforced here.",
|
|
51
|
+
"type": ["array", "null"]
|
|
52
|
+
},
|
|
53
|
+
"risk_thresholds": {
|
|
54
|
+
"description": "RFC-002 v1: numeric / categorical knobs (e.g. public_reach, financial_amount_eur_max_silent). PERMISSIVE.",
|
|
55
|
+
"type": ["object", "null"],
|
|
56
|
+
"additionalProperties": true
|
|
57
|
+
},
|
|
58
|
+
"preflight_checks": {
|
|
59
|
+
"description": "RFC-002 v1: small, named checks an agent SHOULD run before acting on certain classes. PERMISSIVE.",
|
|
60
|
+
"type": ["array", "null"]
|
|
61
|
+
},
|
|
62
|
+
"contract_tests": {
|
|
63
|
+
"description": "RFC-002 v2-additive: machine-checkable contract tests bound to action classes. PERMISSIVE.",
|
|
64
|
+
"type": ["array", "null"]
|
|
65
|
+
},
|
|
66
|
+
"success_criteria": {
|
|
67
|
+
"description": "RFC-002 v2-additive: declarative success criteria per action class. PERMISSIVE.",
|
|
68
|
+
"type": ["object", "array", "null"],
|
|
69
|
+
"additionalProperties": true
|
|
70
|
+
},
|
|
71
|
+
"reversibility": {
|
|
72
|
+
"description": "RFC-002 v2-additive: declared reversibility for action classes. PERMISSIVE.",
|
|
73
|
+
"type": ["object", "null"],
|
|
74
|
+
"additionalProperties": true
|
|
75
|
+
},
|
|
76
|
+
"blast_radius": {
|
|
77
|
+
"description": "RFC-002 v2-additive: declared blast radius for action classes. PERMISSIVE.",
|
|
78
|
+
"type": ["object", "null"],
|
|
79
|
+
"additionalProperties": true
|
|
80
|
+
},
|
|
81
|
+
"migration": {
|
|
82
|
+
"description": "RFC-004: optional migration metadata block. Audit-only in this preview — no migration tooling ships with v4.0.0-preview.1. PERMISSIVE.",
|
|
83
|
+
"type": ["object", "null"],
|
|
84
|
+
"additionalProperties": true,
|
|
85
|
+
"properties": {
|
|
86
|
+
"source_version": {"type": "string"},
|
|
87
|
+
"migrated_at": {"type": "string"},
|
|
88
|
+
"migration_report_ref": {"type": "string"},
|
|
89
|
+
"backup_ref": {"type": "string"}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"context_cost": {
|
|
93
|
+
"description": "Research/benchmark track (benchmarks/context_cost/RFC.md): optional fields recording measured 'repeated context waste' for this profile. No normative semantics depend on this field. PERMISSIVE.",
|
|
94
|
+
"type": ["object", "null"],
|
|
95
|
+
"additionalProperties": true
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|