arkaik 0.1.2 → 0.3.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/dist/io.js CHANGED
@@ -513,7 +513,7 @@ __export(core_exports2, {
513
513
  parse: () => parse,
514
514
  parseAsync: () => parseAsync,
515
515
  prettifyError: () => prettifyError,
516
- process: () => process,
516
+ process: () => process2,
517
517
  regexes: () => regexes_exports,
518
518
  registry: () => registry,
519
519
  safeDecode: () => safeDecode,
@@ -11438,7 +11438,7 @@ function initializeContext(params) {
11438
11438
  external: params?.external ?? void 0
11439
11439
  };
11440
11440
  }
11441
- function process(schema, ctx, _params = { path: [], schemaPath: [] }) {
11441
+ function process2(schema, ctx, _params = { path: [], schemaPath: [] }) {
11442
11442
  var _a3;
11443
11443
  const def = schema._zod.def;
11444
11444
  const seen = ctx.seen.get(schema);
@@ -11475,7 +11475,7 @@ function process(schema, ctx, _params = { path: [], schemaPath: [] }) {
11475
11475
  if (parent) {
11476
11476
  if (!result.ref)
11477
11477
  result.ref = parent;
11478
- process(parent, ctx, params);
11478
+ process2(parent, ctx, params);
11479
11479
  ctx.seen.get(parent).isParent = true;
11480
11480
  }
11481
11481
  }
@@ -11763,14 +11763,14 @@ function isTransforming(_schema, _ctx) {
11763
11763
  }
11764
11764
  var createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
11765
11765
  const ctx = initializeContext({ ...params, processors });
11766
- process(schema, ctx);
11766
+ process2(schema, ctx);
11767
11767
  extractDefs(ctx, schema);
11768
11768
  return finalize(ctx, schema);
11769
11769
  };
11770
11770
  var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {
11771
11771
  const { libraryOptions, target } = params ?? {};
11772
11772
  const ctx = initializeContext({ ...libraryOptions ?? {}, target, io, processors });
11773
- process(schema, ctx);
11773
+ process2(schema, ctx);
11774
11774
  extractDefs(ctx, schema);
11775
11775
  return finalize(ctx, schema);
11776
11776
  };
@@ -12016,7 +12016,7 @@ var arrayProcessor = (schema, ctx, _json, params) => {
12016
12016
  if (typeof maximum === "number")
12017
12017
  json2.maxItems = maximum;
12018
12018
  json2.type = "array";
12019
- json2.items = process(def.element, ctx, {
12019
+ json2.items = process2(def.element, ctx, {
12020
12020
  ...params,
12021
12021
  path: [...params.path, "items"]
12022
12022
  });
@@ -12028,7 +12028,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
12028
12028
  json2.properties = {};
12029
12029
  const shape = def.shape;
12030
12030
  for (const key in shape) {
12031
- json2.properties[key] = process(shape[key], ctx, {
12031
+ json2.properties[key] = process2(shape[key], ctx, {
12032
12032
  ...params,
12033
12033
  path: [...params.path, "properties", key]
12034
12034
  });
@@ -12051,7 +12051,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
12051
12051
  if (ctx.io === "output")
12052
12052
  json2.additionalProperties = false;
12053
12053
  } else if (def.catchall) {
12054
- json2.additionalProperties = process(def.catchall, ctx, {
12054
+ json2.additionalProperties = process2(def.catchall, ctx, {
12055
12055
  ...params,
12056
12056
  path: [...params.path, "additionalProperties"]
12057
12057
  });
@@ -12060,7 +12060,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
12060
12060
  var unionProcessor = (schema, ctx, json2, params) => {
12061
12061
  const def = schema._zod.def;
12062
12062
  const isExclusive = def.inclusive === false;
12063
- const options = def.options.map((x, i) => process(x, ctx, {
12063
+ const options = def.options.map((x, i) => process2(x, ctx, {
12064
12064
  ...params,
12065
12065
  path: [...params.path, isExclusive ? "oneOf" : "anyOf", i]
12066
12066
  }));
@@ -12072,11 +12072,11 @@ var unionProcessor = (schema, ctx, json2, params) => {
12072
12072
  };
12073
12073
  var intersectionProcessor = (schema, ctx, json2, params) => {
12074
12074
  const def = schema._zod.def;
12075
- const a = process(def.left, ctx, {
12075
+ const a = process2(def.left, ctx, {
12076
12076
  ...params,
12077
12077
  path: [...params.path, "allOf", 0]
12078
12078
  });
12079
- const b = process(def.right, ctx, {
12079
+ const b = process2(def.right, ctx, {
12080
12080
  ...params,
12081
12081
  path: [...params.path, "allOf", 1]
12082
12082
  });
@@ -12093,11 +12093,11 @@ var tupleProcessor = (schema, ctx, _json, params) => {
12093
12093
  json2.type = "array";
12094
12094
  const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
12095
12095
  const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
12096
- const prefixItems = def.items.map((x, i) => process(x, ctx, {
12096
+ const prefixItems = def.items.map((x, i) => process2(x, ctx, {
12097
12097
  ...params,
12098
12098
  path: [...params.path, prefixPath, i]
12099
12099
  }));
12100
- const rest = def.rest ? process(def.rest, ctx, {
12100
+ const rest = def.rest ? process2(def.rest, ctx, {
12101
12101
  ...params,
12102
12102
  path: [...params.path, restPath, ...ctx.target === "openapi-3.0" ? [def.items.length] : []]
12103
12103
  }) : null;
@@ -12137,7 +12137,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
12137
12137
  const keyBag = keyType._zod.bag;
12138
12138
  const patterns = keyBag?.patterns;
12139
12139
  if (def.mode === "loose" && patterns && patterns.size > 0) {
12140
- const valueSchema = process(def.valueType, ctx, {
12140
+ const valueSchema = process2(def.valueType, ctx, {
12141
12141
  ...params,
12142
12142
  path: [...params.path, "patternProperties", "*"]
12143
12143
  });
@@ -12147,12 +12147,12 @@ var recordProcessor = (schema, ctx, _json, params) => {
12147
12147
  }
12148
12148
  } else {
12149
12149
  if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") {
12150
- json2.propertyNames = process(def.keyType, ctx, {
12150
+ json2.propertyNames = process2(def.keyType, ctx, {
12151
12151
  ...params,
12152
12152
  path: [...params.path, "propertyNames"]
12153
12153
  });
12154
12154
  }
12155
- json2.additionalProperties = process(def.valueType, ctx, {
12155
+ json2.additionalProperties = process2(def.valueType, ctx, {
12156
12156
  ...params,
12157
12157
  path: [...params.path, "additionalProperties"]
12158
12158
  });
@@ -12167,7 +12167,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
12167
12167
  };
12168
12168
  var nullableProcessor = (schema, ctx, json2, params) => {
12169
12169
  const def = schema._zod.def;
12170
- const inner = process(def.innerType, ctx, params);
12170
+ const inner = process2(def.innerType, ctx, params);
12171
12171
  const seen = ctx.seen.get(schema);
12172
12172
  if (ctx.target === "openapi-3.0") {
12173
12173
  seen.ref = def.innerType;
@@ -12178,20 +12178,20 @@ var nullableProcessor = (schema, ctx, json2, params) => {
12178
12178
  };
12179
12179
  var nonoptionalProcessor = (schema, ctx, _json, params) => {
12180
12180
  const def = schema._zod.def;
12181
- process(def.innerType, ctx, params);
12181
+ process2(def.innerType, ctx, params);
12182
12182
  const seen = ctx.seen.get(schema);
12183
12183
  seen.ref = def.innerType;
12184
12184
  };
12185
12185
  var defaultProcessor = (schema, ctx, json2, params) => {
12186
12186
  const def = schema._zod.def;
12187
- process(def.innerType, ctx, params);
12187
+ process2(def.innerType, ctx, params);
12188
12188
  const seen = ctx.seen.get(schema);
12189
12189
  seen.ref = def.innerType;
12190
12190
  json2.default = JSON.parse(JSON.stringify(def.defaultValue));
12191
12191
  };
12192
12192
  var prefaultProcessor = (schema, ctx, json2, params) => {
12193
12193
  const def = schema._zod.def;
12194
- process(def.innerType, ctx, params);
12194
+ process2(def.innerType, ctx, params);
12195
12195
  const seen = ctx.seen.get(schema);
12196
12196
  seen.ref = def.innerType;
12197
12197
  if (ctx.io === "input")
@@ -12199,7 +12199,7 @@ var prefaultProcessor = (schema, ctx, json2, params) => {
12199
12199
  };
12200
12200
  var catchProcessor = (schema, ctx, json2, params) => {
12201
12201
  const def = schema._zod.def;
12202
- process(def.innerType, ctx, params);
12202
+ process2(def.innerType, ctx, params);
12203
12203
  const seen = ctx.seen.get(schema);
12204
12204
  seen.ref = def.innerType;
12205
12205
  let catchValue;
@@ -12214,32 +12214,32 @@ var pipeProcessor = (schema, ctx, _json, params) => {
12214
12214
  const def = schema._zod.def;
12215
12215
  const inIsTransform = def.in._zod.traits.has("$ZodTransform");
12216
12216
  const innerType = ctx.io === "input" ? inIsTransform ? def.out : def.in : def.out;
12217
- process(innerType, ctx, params);
12217
+ process2(innerType, ctx, params);
12218
12218
  const seen = ctx.seen.get(schema);
12219
12219
  seen.ref = innerType;
12220
12220
  };
12221
12221
  var readonlyProcessor = (schema, ctx, json2, params) => {
12222
12222
  const def = schema._zod.def;
12223
- process(def.innerType, ctx, params);
12223
+ process2(def.innerType, ctx, params);
12224
12224
  const seen = ctx.seen.get(schema);
12225
12225
  seen.ref = def.innerType;
12226
12226
  json2.readOnly = true;
12227
12227
  };
12228
12228
  var promiseProcessor = (schema, ctx, _json, params) => {
12229
12229
  const def = schema._zod.def;
12230
- process(def.innerType, ctx, params);
12230
+ process2(def.innerType, ctx, params);
12231
12231
  const seen = ctx.seen.get(schema);
12232
12232
  seen.ref = def.innerType;
12233
12233
  };
12234
12234
  var optionalProcessor = (schema, ctx, _json, params) => {
12235
12235
  const def = schema._zod.def;
12236
- process(def.innerType, ctx, params);
12236
+ process2(def.innerType, ctx, params);
12237
12237
  const seen = ctx.seen.get(schema);
12238
12238
  seen.ref = def.innerType;
12239
12239
  };
12240
12240
  var lazyProcessor = (schema, ctx, _json, params) => {
12241
12241
  const innerType = schema._zod.innerType;
12242
- process(innerType, ctx, params);
12242
+ process2(innerType, ctx, params);
12243
12243
  const seen = ctx.seen.get(schema);
12244
12244
  seen.ref = innerType;
12245
12245
  };
@@ -12291,7 +12291,7 @@ function toJSONSchema(input, params) {
12291
12291
  const defs = {};
12292
12292
  for (const entry of registry2._idmap.entries()) {
12293
12293
  const [_, schema] = entry;
12294
- process(schema, ctx2);
12294
+ process2(schema, ctx2);
12295
12295
  }
12296
12296
  const schemas = {};
12297
12297
  const external = {
@@ -12314,7 +12314,7 @@ function toJSONSchema(input, params) {
12314
12314
  return { schemas };
12315
12315
  }
12316
12316
  const ctx = initializeContext({ ...params, processors: allProcessors });
12317
- process(input, ctx);
12317
+ process2(input, ctx);
12318
12318
  extractDefs(ctx, input);
12319
12319
  return finalize(ctx, input);
12320
12320
  }
@@ -12372,7 +12372,7 @@ var JSONSchemaGenerator = class {
12372
12372
  * This must be called before emit().
12373
12373
  */
12374
12374
  process(schema, _params = { path: [], schemaPath: [] }) {
12375
- return process(schema, this.ctx, _params);
12375
+ return process2(schema, this.ctx, _params);
12376
12376
  }
12377
12377
  /**
12378
12378
  * Emit the final JSON Schema after processing.
@@ -14929,6 +14929,7 @@ function crossCheckJournal(bundle) {
14929
14929
  });
14930
14930
  const everNodes = new Set(snapshotNodeStatus.keys());
14931
14931
  const everEdges = new Set(snapshotEdgeIds);
14932
+ const baselined = /* @__PURE__ */ new Set();
14932
14933
  for (const { ev } of valid) {
14933
14934
  if (ev.type === "node.created") {
14934
14935
  const nid = str(ev.node_id);
@@ -14936,6 +14937,14 @@ function crossCheckJournal(bundle) {
14936
14937
  } else if (ev.type === "edge.added") {
14937
14938
  const eid = str(ev.edge_id);
14938
14939
  if (eid) everEdges.add(eid);
14940
+ } else if (ev.type === "journal.baseline" && Array.isArray(ev.node_ids)) {
14941
+ for (const raw of ev.node_ids) {
14942
+ const nid = str(raw);
14943
+ if (nid) {
14944
+ baselined.add(nid);
14945
+ everNodes.add(nid);
14946
+ }
14947
+ }
14939
14948
  }
14940
14949
  }
14941
14950
  const ordered = orderEvents(valid.map((v) => v.ev));
@@ -15001,7 +15010,7 @@ function crossCheckJournal(bundle) {
15001
15010
  }
15002
15011
  }
15003
15012
  for (const [nodeId, status] of snapshotNodeStatus) {
15004
- if (!created.has(nodeId)) {
15013
+ if (!created.has(nodeId) && !baselined.has(nodeId)) {
15005
15014
  findings.push({
15006
15015
  path: "journal",
15007
15016
  rule: "journal-missing-node-created",
@@ -15033,6 +15042,26 @@ function crossCheckJournal(bundle) {
15033
15042
  }
15034
15043
  return findings;
15035
15044
  }
15045
+ function missingProvenanceNodeIds(snapshotNodeIds, events) {
15046
+ const covered = /* @__PURE__ */ new Set();
15047
+ for (const ev of events) {
15048
+ if (ev.type === "node.created") {
15049
+ if (typeof ev.node_id === "string") covered.add(ev.node_id);
15050
+ } else if (ev.type === "journal.baseline" && Array.isArray(ev.node_ids)) {
15051
+ for (const id of ev.node_ids) {
15052
+ if (typeof id === "string") covered.add(id);
15053
+ }
15054
+ }
15055
+ }
15056
+ const missing = [];
15057
+ const seen = /* @__PURE__ */ new Set();
15058
+ for (const id of snapshotNodeIds) {
15059
+ if (covered.has(id) || seen.has(id)) continue;
15060
+ seen.add(id);
15061
+ missing.push(id);
15062
+ }
15063
+ return missing;
15064
+ }
15036
15065
 
15037
15066
  // ../schema/src/journal-events.ts
15038
15067
  var envelope = {
@@ -15099,7 +15128,12 @@ var DeliverableShippedEventSchema = external_exports.object({
15099
15128
  summary: external_exports.string().optional(),
15100
15129
  url: external_exports.string().optional(),
15101
15130
  node_ids: external_exports.array(external_exports.string()).optional(),
15102
- platform: PlatformSchema.optional()
15131
+ platform: PlatformSchema.optional(),
15132
+ lab_note: external_exports.object({
15133
+ en: external_exports.object({ title: external_exports.string(), summary: external_exports.string() }).catchall(external_exports.unknown()),
15134
+ fr: external_exports.object({ title: external_exports.string().optional(), summary: external_exports.string().optional() }).catchall(external_exports.unknown()).optional(),
15135
+ suggested: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
15136
+ }).catchall(external_exports.unknown()).optional().meta({ description: "The Lab Note a merged PR carried (slice 3); en.title/en.summary required when present." })
15103
15137
  }).catchall(external_exports.unknown());
15104
15138
  var IdeaProposedEventSchema = external_exports.object({
15105
15139
  ...envelope,
@@ -15134,6 +15168,70 @@ var RefStatusChangedEventSchema = external_exports.object({
15134
15168
  to: external_exports.string(),
15135
15169
  synced_at: external_exports.string()
15136
15170
  }).catchall(external_exports.unknown());
15171
+ var JournalBaselineEventSchema = external_exports.object({ ...envelope, type: external_exports.literal("journal.baseline"), node_ids: external_exports.array(external_exports.string()) }).catchall(external_exports.unknown());
15172
+ var QualityAuditCompletedEventSchema = external_exports.object({
15173
+ ...envelope,
15174
+ type: external_exports.literal("quality.audit.completed"),
15175
+ audit_id: external_exports.string(),
15176
+ framework_version: external_exports.string(),
15177
+ commit: external_exports.string().optional(),
15178
+ scores: external_exports.record(external_exports.string(), external_exports.record(external_exports.string(), external_exports.number())).optional(),
15179
+ counts: external_exports.object({
15180
+ critical: external_exports.number().optional(),
15181
+ high: external_exports.number().optional(),
15182
+ medium: external_exports.number().optional(),
15183
+ low: external_exports.number().optional(),
15184
+ info: external_exports.number().optional()
15185
+ }).catchall(external_exports.unknown()).optional()
15186
+ }).catchall(external_exports.unknown());
15187
+ var QualityFindingOpenedEventSchema = external_exports.object({
15188
+ ...envelope,
15189
+ type: external_exports.literal("quality.finding.opened"),
15190
+ finding_id: external_exports.string(),
15191
+ criterion_id: external_exports.string(),
15192
+ surface: external_exports.string(),
15193
+ severity: external_exports.string(),
15194
+ priority: external_exports.string(),
15195
+ title: external_exports.string(),
15196
+ node_ids: external_exports.array(external_exports.string()).optional(),
15197
+ issue_url: external_exports.string().optional()
15198
+ }).catchall(external_exports.unknown());
15199
+ var QualityFindingResolvedEventSchema = external_exports.object({
15200
+ ...envelope,
15201
+ type: external_exports.literal("quality.finding.resolved"),
15202
+ finding_id: external_exports.string(),
15203
+ resolved_by: external_exports.string().optional(),
15204
+ node_ids: external_exports.array(external_exports.string()).optional()
15205
+ }).catchall(external_exports.unknown());
15206
+ var QualitySignalTrippedEventSchema = external_exports.object({
15207
+ ...envelope,
15208
+ type: external_exports.literal("quality.signal.tripped"),
15209
+ criterion_id: external_exports.string(),
15210
+ surface: external_exports.string(),
15211
+ signal: external_exports.string(),
15212
+ detail: external_exports.string().optional()
15213
+ }).catchall(external_exports.unknown());
15214
+ var JOURNAL_EVENT_SCHEMAS = {
15215
+ "node.created": NodeCreatedEventSchema,
15216
+ "node.updated": NodeUpdatedEventSchema,
15217
+ "node.status_changed": NodeStatusChangedEventSchema,
15218
+ "decision.status_changed": DecisionStatusChangedEventSchema,
15219
+ "node.deleted": NodeDeletedEventSchema,
15220
+ "edge.added": EdgeAddedEventSchema,
15221
+ "edge.removed": EdgeRemovedEventSchema,
15222
+ "release.tagged": ReleaseTaggedEventSchema,
15223
+ "deliverable.shipped": DeliverableShippedEventSchema,
15224
+ "idea.proposed": IdeaProposedEventSchema,
15225
+ "request.filed": RequestFiledEventSchema,
15226
+ "ref.added": RefAddedEventSchema,
15227
+ "ref.removed": RefRemovedEventSchema,
15228
+ "ref.status_changed": RefStatusChangedEventSchema,
15229
+ "journal.baseline": JournalBaselineEventSchema,
15230
+ "quality.audit.completed": QualityAuditCompletedEventSchema,
15231
+ "quality.finding.opened": QualityFindingOpenedEventSchema,
15232
+ "quality.finding.resolved": QualityFindingResolvedEventSchema,
15233
+ "quality.signal.tripped": QualitySignalTrippedEventSchema
15234
+ };
15137
15235
  var KnownJournalEventSchema = external_exports.union([
15138
15236
  NodeCreatedEventSchema,
15139
15237
  NodeUpdatedEventSchema,
@@ -15148,9 +15246,114 @@ var KnownJournalEventSchema = external_exports.union([
15148
15246
  RequestFiledEventSchema,
15149
15247
  RefAddedEventSchema,
15150
15248
  RefRemovedEventSchema,
15151
- RefStatusChangedEventSchema
15249
+ RefStatusChangedEventSchema,
15250
+ JournalBaselineEventSchema,
15251
+ QualityAuditCompletedEventSchema,
15252
+ QualityFindingOpenedEventSchema,
15253
+ QualityFindingResolvedEventSchema,
15254
+ QualitySignalTrippedEventSchema
15152
15255
  ]);
15153
15256
 
15257
+ // ../schema/src/quality-schemas.ts
15258
+ var GradeSchema = external_exports.enum(["A", "B", "C", "D", "E"]);
15259
+ var KritikScalesSchema = external_exports.object({
15260
+ grades: external_exports.partialRecord(GradeSchema, external_exports.number()).optional(),
15261
+ severity_buckets: external_exports.partialRecord(external_exports.enum(["critical", "high", "medium", "low", "info"]), external_exports.tuple([external_exports.number(), external_exports.number()])).optional(),
15262
+ caps: external_exports.object({ critical_open: GradeSchema.optional(), high_open: GradeSchema.optional() }).catchall(external_exports.unknown()).optional()
15263
+ }).catchall(external_exports.unknown()).meta({
15264
+ id: "KritikScales",
15265
+ description: "Overridable maturity/severity/grade scales carried by a criteria pack. Absent entries fall back to the framework defaults (packages/kritik-library/SPEC.md \xA7 4)."
15266
+ });
15267
+ var KritikReferenceSchema = external_exports.object({ title: external_exports.string(), anchor: external_exports.string().optional(), url: external_exports.string().optional() }).catchall(external_exports.unknown()).meta({ id: "KritikReference", description: "A stable external anchor behind a criterion (OWASP control, GDPR article, WCAG SC\u2026)." });
15268
+ var KritikIssueTemplateSchema = external_exports.object({
15269
+ title_template: external_exports.string().optional(),
15270
+ labels: external_exports.array(external_exports.string()).optional(),
15271
+ body_skeleton: external_exports.string().optional()
15272
+ }).catchall(external_exports.unknown()).meta({ id: "KritikIssueTemplate", description: "Per-criterion issue skeleton \u2014 what `arkaik kritik issue` prefills." });
15273
+ var KritikDomainSchema = external_exports.object({ code: external_exports.string(), name: external_exports.string(), description: external_exports.string().optional() }).catchall(external_exports.unknown()).meta({ id: "KritikDomain", description: "A top-level criteria category owning a roll-up row in the matrix." });
15274
+ var KritikCriterionSchema = external_exports.object({
15275
+ id: external_exports.string().meta({ description: "Stable identifier, `<DOMAIN>-NN`. Never redefined \u2014 retired via superseded_by." }),
15276
+ domain: external_exports.string(),
15277
+ subcategory: external_exports.string().optional(),
15278
+ name: external_exports.string().optional(),
15279
+ question: external_exports.string().optional(),
15280
+ definition: external_exports.string().optional(),
15281
+ rationale: external_exports.string().optional(),
15282
+ applies_to: external_exports.array(external_exports.string()).optional().meta({ description: "Surface ids where the criterion is meaningful; absence means N/A." }),
15283
+ level_anchors: external_exports.record(external_exports.string(), external_exports.string()).optional().meta({ description: "Observable descriptions per maturity level, keyed l0\u2026l4." }),
15284
+ default_impact: external_exports.number().optional(),
15285
+ weight: external_exports.number().optional().meta({ description: "Weight in the domain roll-up (1\u20133; 3 = load-bearing). Absent means 1." }),
15286
+ references: external_exports.array(KritikReferenceSchema).optional(),
15287
+ checklist: external_exports.array(external_exports.string()).optional(),
15288
+ signals: external_exports.array(external_exports.string()).optional().meta({ description: "Mechanically checkable monitoring hooks run between audits." }),
15289
+ remediation: external_exports.string().optional(),
15290
+ issue: KritikIssueTemplateSchema.optional(),
15291
+ superseded_by: external_exports.string().optional()
15292
+ }).catchall(external_exports.unknown()).meta({ id: "KritikCriterion", description: "The atomic auditable unit (packages/kritik-library/SPEC.md \xA7 5)." });
15293
+ var KritikLibrarySchema = external_exports.object({
15294
+ version: external_exports.string().meta({ description: "Semver of the pack; matrices are comparable only within a major." }),
15295
+ domains: external_exports.array(KritikDomainSchema),
15296
+ criteria: external_exports.array(KritikCriterionSchema),
15297
+ scales: KritikScalesSchema.optional()
15298
+ }).catchall(external_exports.unknown()).meta({
15299
+ id: "KritikLibrary",
15300
+ description: "A versioned criteria pack. Embedded on export; repos may instead pin it as a sidecar file."
15301
+ });
15302
+ var SurfaceDefSchema = external_exports.object({
15303
+ id: external_exports.string(),
15304
+ title: external_exports.string(),
15305
+ platform: PlatformSchema.optional().meta({
15306
+ description: "Optional bridge to PLATFORM_IDS where a surface is also a view-shipping platform. Surfaces are not platforms (RFC \xA7 2)."
15307
+ }),
15308
+ path: external_exports.string().optional()
15309
+ }).catchall(external_exports.unknown()).meta({ id: "SurfaceDef", description: "One independently assessable body of code \u2014 an audit target, chosen at install." });
15310
+ var QualityProfileSchema = external_exports.object({
15311
+ surfaces: external_exports.array(SurfaceDefSchema),
15312
+ domain_weights: external_exports.record(external_exports.string(), external_exports.number()).optional().meta({
15313
+ description: "Per-domain weight in the surface roll-up, keyed by domain code. A missing weight is 1."
15314
+ })
15315
+ }).catchall(external_exports.unknown()).meta({ id: "QualityProfile", description: "The project's parameterization of the pack (RFC \xA7 6)." });
15316
+ var QualityAssessmentSchema = external_exports.object({
15317
+ criterion_id: external_exports.string(),
15318
+ surface: external_exports.string(),
15319
+ level: external_exports.union([external_exports.literal(0), external_exports.literal(1), external_exports.literal(2), external_exports.literal(3), external_exports.literal(4)]).meta({
15320
+ description: "Maturity 0\u20134. N/A is the absence of the row, never a level."
15321
+ }),
15322
+ evidence: external_exports.string().meta({ description: "file:line / config citations. A score without evidence is an opinion, not an assessment." }),
15323
+ audit_id: external_exports.string(),
15324
+ commit: external_exports.string().optional(),
15325
+ ts: external_exports.string()
15326
+ }).catchall(external_exports.unknown()).meta({ id: "QualityAssessment", description: "One (criterion \xD7 surface) score \u2014 latest wins; history lives in the journal." });
15327
+ var QualityFindingSchema = external_exports.object({
15328
+ id: external_exports.string(),
15329
+ criterion_id: external_exports.string(),
15330
+ surface: external_exports.string(),
15331
+ title: external_exports.string(),
15332
+ detail: external_exports.string(),
15333
+ evidence: external_exports.string(),
15334
+ impact: external_exports.number().meta({ description: "Worst plausible consequence, 1\u20135." }),
15335
+ likelihood: external_exports.number().meta({ description: "Probability it materializes, 1\u20135." }),
15336
+ cost: external_exports.enum(["S", "M", "L", "XL"]),
15337
+ status: external_exports.enum(["open", "resolved", "refuted", "accepted-risk"]),
15338
+ remediation: external_exports.string().optional(),
15339
+ node_ids: external_exports.array(external_exports.string()).optional().meta({ description: "Graph nodes this finding is about \u2014 the tie deliverable.shipped already uses." }),
15340
+ issue_url: external_exports.string().optional(),
15341
+ verification: external_exports.object({ verdict: external_exports.enum(["CONFIRMED", "REFUTED", "DOWNGRADED"]), note: external_exports.string().optional() }).catchall(external_exports.unknown()).optional().meta({ description: "Result of the adversarial refutation pass. Refuted findings are disclosed, not deleted." })
15342
+ }).catchall(external_exports.unknown()).meta({
15343
+ id: "QualityFinding",
15344
+ description: "One concrete defect. Severity and priority are deliberately absent \u2014 they are derived from impact \xD7 likelihood and cost (deriveQualityMatrix), so a finding can never carry a severity its own numbers disagree with."
15345
+ });
15346
+ var QualitySectionSchema = external_exports.object({
15347
+ framework_version: external_exports.string(),
15348
+ library: KritikLibrarySchema.optional(),
15349
+ profile: QualityProfileSchema,
15350
+ assessments: external_exports.array(QualityAssessmentSchema),
15351
+ findings: external_exports.array(QualityFindingSchema)
15352
+ }).catchall(external_exports.unknown()).meta({
15353
+ id: "QualitySection",
15354
+ description: "The project's Kritik state (docs/rfcs/kritik.md \xA7 4.1) \u2014 profile, assessments, findings. Criteria are library content, not graph nodes; the matrix is a projection, never stored."
15355
+ });
15356
+
15154
15357
  // ../schema/src/bundle.ts
15155
15358
  var PlatformStatusMapSchema = external_exports.partialRecord(
15156
15359
  PlatformSchema,
@@ -15292,7 +15495,8 @@ var ProjectMetadataSchema = external_exports.object({
15292
15495
  }),
15293
15496
  products: external_exports.array(ProductDefinitionSchema).optional().meta({
15294
15497
  description: "Product definitions (docs/spec/bundle-format.md \xA7 Products) \u2014 additive; unknown fields preserved."
15295
- })
15498
+ }),
15499
+ pollen: external_exports.object({ plant: external_exports.string().regex(/^[a-z0-9][a-z0-9-]*$/).optional() }).catchall(external_exports.unknown()).optional().meta({ description: "Federation settings \u2014 docs/spec/services.md \xA7 Pollen feed. `plant` enables the feed." })
15296
15500
  }).catchall(external_exports.unknown()).meta({ id: "ProjectMetadata", description: "Optional project-level UI settings." });
15297
15501
  var ProjectSchema = external_exports.object({
15298
15502
  id: external_exports.string().meta({ description: "Unique project identifier." }),
@@ -15320,6 +15524,9 @@ var ProjectBundleSchema = external_exports.object({
15320
15524
  edges: external_exports.array(EdgeSchema).meta({ description: "All edges (relationships) between nodes." }),
15321
15525
  journal: external_exports.array(JournalEventSchema).optional().meta({
15322
15526
  description: "Optional embedded journal events \u2014 the Level 2 interchange projection (docs/spec/journal.md). Canonical storage in repos is the JSONL sidecar; a bundle without a journal is Level 0/1, not an error."
15527
+ }),
15528
+ quality: QualitySectionSchema.optional().meta({
15529
+ description: "Optional Kritik quality state (docs/rfcs/kritik.md \xA7 4.1) \u2014 the pinned pack version, the project's surface profile, its assessments and findings. Additive: a bundle without it is not an error, and the comparative matrix is derived from it (deriveQualityMatrix), never stored."
15323
15530
  })
15324
15531
  }).catchall(external_exports.unknown()).meta({
15325
15532
  id: "ProjectBundle",
@@ -15327,15 +15534,6 @@ var ProjectBundleSchema = external_exports.object({
15327
15534
  description: "The import/export format for an Arkaik product graph. Contains a project, its nodes (flows, views, data models, API endpoints), and edges (relationships between nodes)."
15328
15535
  });
15329
15536
 
15330
- // ../schema/src/maps.ts
15331
- var MAP_FLOW_PLATFORMS_MODES = ["rings", "bars"];
15332
- var MAP_VIEW_PLATFORMS_MODES = ["chips", "rows"];
15333
- var MAP_MINIMAP_COLOR_MODES = ["status", "species"];
15334
- var BUILT_IN_MAP_IDS = ["journey", "system"];
15335
- function isBuiltInMapId(id) {
15336
- return BUILT_IN_MAP_IDS.includes(id);
15337
- }
15338
-
15339
15537
  // ../schema/src/products.ts
15340
15538
  var PRODUCT_MEMBERSHIP_SPECIES = ["flow", "view", "acceptance"];
15341
15539
  function resolveProducts(project) {
@@ -15353,6 +15551,38 @@ function resolveProducts(project) {
15353
15551
  return products;
15354
15552
  }
15355
15553
 
15554
+ // ../schema/src/maps.ts
15555
+ var MAP_FLOW_PLATFORMS_MODES = ["rings", "bars"];
15556
+ var MAP_VIEW_PLATFORMS_MODES = ["chips", "rows"];
15557
+ var MAP_MINIMAP_COLOR_MODES = ["status", "species"];
15558
+ var BUILT_IN_MAP_IDS = ["journey", "system"];
15559
+ function isBuiltInMapId(id) {
15560
+ return BUILT_IN_MAP_IDS.includes(id);
15561
+ }
15562
+
15563
+ // ../schema/src/quality.ts
15564
+ var asArray = (value) => Array.isArray(value) ? value : [];
15565
+ var CROSS_SURFACE_ID = "cross-surface";
15566
+ function mergeKritikLibrary(pack, overlay) {
15567
+ if (!overlay || typeof overlay !== "object") return pack;
15568
+ const domains = [...asArray(pack.domains)];
15569
+ for (const domain2 of asArray(overlay.domains)) {
15570
+ if (typeof domain2?.code !== "string" || domain2.code === "") continue;
15571
+ const at = domains.findIndex((existing) => existing?.code === domain2.code);
15572
+ if (at >= 0) domains[at] = domain2;
15573
+ else domains.push(domain2);
15574
+ }
15575
+ const criteria = [...asArray(pack.criteria)];
15576
+ for (const criterion of asArray(overlay.criteria)) {
15577
+ if (typeof criterion?.id !== "string" || criterion.id === "") continue;
15578
+ const at = criteria.findIndex((existing) => existing?.id === criterion.id);
15579
+ if (at >= 0) criteria[at] = criterion;
15580
+ else criteria.push(criterion);
15581
+ }
15582
+ const scales = pack.scales || overlay.scales ? { ...pack.scales ?? {}, ...overlay.scales ?? {} } : void 0;
15583
+ return { ...pack, domains, criteria, ...scales ? { scales } : {} };
15584
+ }
15585
+
15356
15586
  // ../schema/src/validate.ts
15357
15587
  var VALID_STAGES = ["beta", "monitoring", "deprecated"];
15358
15588
  var VALID_VIEW_CARD_VARIANTS = ["compact", "large"];
@@ -15869,13 +16099,98 @@ function validateBundle(input) {
15869
16099
  composesSet.add(`${sourceId}->${targetId}`);
15870
16100
  }
15871
16101
  });
16102
+ const PLAYLIST_ENTRY_TYPES = ["view", "flow", "condition", "junction"];
16103
+ const checkPlaylistShape = (entries, flowId, path, depth = 0) => {
16104
+ if (depth > 50) return;
16105
+ if (!Array.isArray(entries)) {
16106
+ error51(path, "playlist-entry-shape", `Flow ${flowId}: ${path} must be an array of playlist entries`);
16107
+ return;
16108
+ }
16109
+ entries.forEach((raw, i) => {
16110
+ const p = `${path}[${i}]`;
16111
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
16112
+ error51(p, "playlist-entry-shape", `Flow ${flowId}: ${p} must be a playlist entry object`);
16113
+ return;
16114
+ }
16115
+ const entry = raw;
16116
+ const type = entry.type;
16117
+ if (typeof type !== "string" || !PLAYLIST_ENTRY_TYPES.includes(type)) {
16118
+ error51(
16119
+ `${p}.type`,
16120
+ "playlist-entry-shape",
16121
+ `Flow ${flowId}: ${p}.type must be one of ${PLAYLIST_ENTRY_TYPES.join(", ")} (got ${JSON.stringify(type)})`
16122
+ );
16123
+ return;
16124
+ }
16125
+ if (type === "view") {
16126
+ if (typeof entry.view_id !== "string") {
16127
+ error51(`${p}.view_id`, "playlist-entry-shape", `Flow ${flowId}: ${p} is a view entry with no view_id string`);
16128
+ }
16129
+ } else if (type === "flow") {
16130
+ if (typeof entry.flow_id !== "string") {
16131
+ error51(`${p}.flow_id`, "playlist-entry-shape", `Flow ${flowId}: ${p} is a flow entry with no flow_id string`);
16132
+ }
16133
+ } else if (type === "condition") {
16134
+ if (typeof entry.label !== "string") {
16135
+ error51(`${p}.label`, "playlist-entry-shape", `Flow ${flowId}: ${p} is a condition with no label string`);
16136
+ }
16137
+ for (const branch of ["if_true", "if_false"]) {
16138
+ if (!Array.isArray(entry[branch])) {
16139
+ error51(
16140
+ `${p}.${branch}`,
16141
+ "playlist-entry-shape",
16142
+ `Flow ${flowId}: ${p}.${branch} must be an array of playlist entries (both branches are required, use [] for an empty one)`
16143
+ );
16144
+ } else {
16145
+ checkPlaylistShape(entry[branch], flowId, `${p}.${branch}`, depth + 1);
16146
+ }
16147
+ }
16148
+ } else {
16149
+ if (typeof entry.label !== "string") {
16150
+ error51(`${p}.label`, "playlist-entry-shape", `Flow ${flowId}: ${p} is a junction with no label string`);
16151
+ }
16152
+ if (!Array.isArray(entry.cases)) {
16153
+ error51(`${p}.cases`, "playlist-entry-shape", `Flow ${flowId}: ${p}.cases must be an array of junction cases`);
16154
+ return;
16155
+ }
16156
+ entry.cases.forEach((rawCase, j) => {
16157
+ const cp = `${p}.cases[${j}]`;
16158
+ if (typeof rawCase !== "object" || rawCase === null || Array.isArray(rawCase)) {
16159
+ error51(cp, "playlist-entry-shape", `Flow ${flowId}: ${cp} must be a junction case object`);
16160
+ return;
16161
+ }
16162
+ const branch = rawCase;
16163
+ if (typeof branch.label !== "string") {
16164
+ error51(`${cp}.label`, "playlist-entry-shape", `Flow ${flowId}: ${cp} has no label string`);
16165
+ }
16166
+ if (!Array.isArray(branch.entries)) {
16167
+ error51(
16168
+ `${cp}.entries`,
16169
+ "playlist-entry-shape",
16170
+ `Flow ${flowId}: ${cp}.entries must be an array of playlist entries \u2014 a junction case holds its own entries, not a bare view_id/flow_id`
16171
+ );
16172
+ } else {
16173
+ checkPlaylistShape(branch.entries, flowId, `${cp}.entries`, depth + 1);
16174
+ }
16175
+ });
16176
+ }
16177
+ });
16178
+ };
16179
+ nodes.forEach((node, i) => {
16180
+ const md = node.metadata;
16181
+ if (node.species === "flow" && md?.playlist?.entries) {
16182
+ checkPlaylistShape(md.playlist.entries, node.id, `nodes[${i}].metadata.playlist.entries`);
16183
+ }
16184
+ });
15872
16185
  const collectPlaylistRefs = (entries, flowId, path, depth = 0) => {
15873
16186
  if (depth > 50) {
15874
16187
  error51(path, "playlist-depth", `Flow ${flowId}: playlist nesting too deep (possible cycle)`);
15875
16188
  return [];
15876
16189
  }
15877
16190
  const refs = [];
16191
+ if (!Array.isArray(entries)) return refs;
15878
16192
  for (const entry of entries) {
16193
+ if (typeof entry !== "object" || entry === null) continue;
15879
16194
  if (entry.type === "view") {
15880
16195
  if (!nodeIds.has(entry.view_id)) {
15881
16196
  error51(path, "playlist-ref-exists", `Flow ${flowId}: playlist references non-existent view "${entry.view_id}"`);
@@ -15927,7 +16242,9 @@ function validateBundle(input) {
15927
16242
  const nodeId = node.id;
15928
16243
  const subFlows = [];
15929
16244
  const findSubFlows = (entries) => {
16245
+ if (!Array.isArray(entries)) return;
15930
16246
  for (const e of entries) {
16247
+ if (typeof e !== "object" || e === null) continue;
15931
16248
  if (e.type === "flow") subFlows.push(e.flow_id);
15932
16249
  if (e.type === "condition") {
15933
16250
  if (e.if_true) findSubFlows(e.if_true);
@@ -15962,6 +16279,151 @@ function validateBundle(input) {
15962
16279
  for (const id of flowGraph.keys()) {
15963
16280
  dfs(id);
15964
16281
  }
16282
+ const quality = bundle.quality;
16283
+ if (typeof quality === "object" && quality !== null && !Array.isArray(quality)) {
16284
+ const section = quality;
16285
+ const assessments = Array.isArray(section.assessments) ? section.assessments : [];
16286
+ const qualityFindings = Array.isArray(section.findings) ? section.findings : [];
16287
+ const profile = typeof section.profile === "object" && section.profile !== null ? section.profile : void 0;
16288
+ const declaredSurfaces = /* @__PURE__ */ new Set();
16289
+ if (Array.isArray(profile?.surfaces)) {
16290
+ profile.surfaces.forEach((entry, index) => {
16291
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return;
16292
+ const surface = entry;
16293
+ const surfaceId = typeof surface.id === "string" ? surface.id : void 0;
16294
+ if (surfaceId === void 0 || surfaceId.trim() === "") return;
16295
+ if (declaredSurfaces.has(surfaceId)) {
16296
+ warn(`quality.profile.surfaces[${index}].id`, "quality-duplicate-surface", `Duplicate surface id "${surfaceId}" \u2014 the first wins`);
16297
+ }
16298
+ declaredSurfaces.add(surfaceId);
16299
+ });
16300
+ }
16301
+ if (declaredSurfaces.size === 0 && (assessments.length > 0 || qualityFindings.length > 0)) {
16302
+ warn(
16303
+ "quality.profile.surfaces",
16304
+ "quality-no-surfaces",
16305
+ "Quality data is stored but the profile declares no surfaces \u2014 the matrix falls back to the surfaces the assessments name"
16306
+ );
16307
+ }
16308
+ const library = typeof section.library === "object" && section.library !== null ? section.library : void 0;
16309
+ const knownCriteria = /* @__PURE__ */ new Set();
16310
+ const retiredCriteria = /* @__PURE__ */ new Map();
16311
+ if (Array.isArray(library?.criteria)) {
16312
+ for (const entry of library.criteria) {
16313
+ if (typeof entry !== "object" || entry === null) continue;
16314
+ const criterion = entry;
16315
+ if (typeof criterion.id !== "string") continue;
16316
+ knownCriteria.add(criterion.id);
16317
+ if (typeof criterion.superseded_by === "string" && criterion.superseded_by !== "") {
16318
+ retiredCriteria.set(criterion.id, criterion.superseded_by);
16319
+ }
16320
+ }
16321
+ } else if (assessments.length > 0 || qualityFindings.length > 0) {
16322
+ warn(
16323
+ "quality.library",
16324
+ "quality-library-missing",
16325
+ "No criteria pack is embedded \u2014 criterion ids cannot be resolved to domains or weights; the matrix uses a synthesized library"
16326
+ );
16327
+ }
16328
+ if (typeof section.framework_version !== "string" || section.framework_version === "") {
16329
+ warn("quality.framework_version", "quality-framework-version-missing", "Quality section has no framework_version \u2014 scores are not comparable across audits without one");
16330
+ }
16331
+ const checkRowRefs = (row, path, label) => {
16332
+ const criterionId = typeof row.criterion_id === "string" ? row.criterion_id : void 0;
16333
+ if (criterionId !== void 0 && knownCriteria.size > 0 && !knownCriteria.has(criterionId)) {
16334
+ warn(`${path}.criterion_id`, "quality-unknown-criterion", `${label}: criterion "${criterionId}" is not in the pinned library`);
16335
+ }
16336
+ const supersededBy = criterionId !== void 0 ? retiredCriteria.get(criterionId) : void 0;
16337
+ if (supersededBy !== void 0) {
16338
+ warn(`${path}.criterion_id`, "quality-retired-criterion", `${label}: criterion "${criterionId}" is retired \u2014 superseded by "${supersededBy}"`);
16339
+ }
16340
+ const surface = typeof row.surface === "string" ? row.surface : void 0;
16341
+ if (surface !== void 0 && surface !== CROSS_SURFACE_ID && declaredSurfaces.size > 0 && !declaredSurfaces.has(surface)) {
16342
+ warn(`${path}.surface`, "quality-unknown-surface", `${label}: surface "${surface}" is not declared in the profile`);
16343
+ }
16344
+ };
16345
+ const seenCells = /* @__PURE__ */ new Set();
16346
+ assessments.forEach((entry, index) => {
16347
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return;
16348
+ const assessment = entry;
16349
+ const path = `quality.assessments[${index}]`;
16350
+ const label = `Assessment ${index}`;
16351
+ checkRowRefs(assessment, path, label);
16352
+ const level = assessment.level;
16353
+ if (typeof level !== "number" || !Number.isInteger(level) || level < 0 || level > 4) {
16354
+ warn(`${path}.level`, "quality-level-range", `${label}: level must be an integer 0-4 (N/A is the absence of the row, not a level)`);
16355
+ }
16356
+ if (assessment.surface === CROSS_SURFACE_ID) {
16357
+ warn(`${path}.surface`, "quality-cross-surface-assessment", `${label}: "${CROSS_SURFACE_ID}" is a findings-only lens \u2014 it carries no matrix column, so this score would render nowhere`);
16358
+ }
16359
+ if (typeof assessment.evidence !== "string" || assessment.evidence.trim() === "") {
16360
+ warn(`${path}.evidence`, "quality-assessment-no-evidence", `${label}: scored with no evidence \u2014 a score without a citation is an opinion, not an assessment`);
16361
+ }
16362
+ const criterionId = typeof assessment.criterion_id === "string" ? assessment.criterion_id : void 0;
16363
+ const surface = typeof assessment.surface === "string" ? assessment.surface : void 0;
16364
+ if (criterionId !== void 0 && surface !== void 0) {
16365
+ const cell = `${criterionId}\0${surface}`;
16366
+ if (seenCells.has(cell)) {
16367
+ warn(`${path}`, "quality-duplicate-assessment", `${label}: a second score for (${criterionId} x ${surface}) \u2014 the section stores latest-per-cell, so this cell is ambiguous`);
16368
+ }
16369
+ seenCells.add(cell);
16370
+ }
16371
+ });
16372
+ const seenFindingIds = /* @__PURE__ */ new Set();
16373
+ qualityFindings.forEach((entry, index) => {
16374
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return;
16375
+ const finding = entry;
16376
+ const path = `quality.findings[${index}]`;
16377
+ const label = `Finding ${index}`;
16378
+ checkRowRefs(finding, path, label);
16379
+ const findingId = typeof finding.id === "string" ? finding.id : void 0;
16380
+ if (findingId !== void 0) {
16381
+ if (seenFindingIds.has(findingId)) {
16382
+ warn(`${path}.id`, "quality-duplicate-finding-id", `Duplicate finding id "${findingId}"`);
16383
+ }
16384
+ seenFindingIds.add(findingId);
16385
+ }
16386
+ for (const field of ["impact", "likelihood"]) {
16387
+ const value = finding[field];
16388
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 5) {
16389
+ warn(`${path}.${field}`, "quality-risk-range", `${label}: ${field} must be an integer 1-5 \u2014 severity is derived from impact x likelihood`);
16390
+ }
16391
+ }
16392
+ for (const derived of ["severity", "priority"]) {
16393
+ if (derived in finding) {
16394
+ warn(`${path}.${derived}`, "quality-derived-field-stored", `${label}: ${derived} is derived from impact, likelihood and cost \u2014 storing it lets it drift from the numbers behind it`);
16395
+ }
16396
+ }
16397
+ if (finding.status === "accepted-risk" && (typeof finding.detail !== "string" || finding.detail.trim() === "")) {
16398
+ warn(`${path}.detail`, "quality-accepted-risk-no-note", `${label}: accepted-risk with no note \u2014 an accepted risk is a decision and reads like one`);
16399
+ }
16400
+ });
16401
+ const openedInJournal = /* @__PURE__ */ new Set();
16402
+ const journalEvents = Array.isArray(bundle.journal) ? bundle.journal : [];
16403
+ journalEvents.forEach((entry) => {
16404
+ if (typeof entry !== "object" || entry === null) return;
16405
+ const event = entry;
16406
+ if (event.type === "quality.finding.opened" && typeof event.finding_id === "string") openedInJournal.add(event.finding_id);
16407
+ });
16408
+ journalEvents.forEach((entry, index) => {
16409
+ if (typeof entry !== "object" || entry === null) return;
16410
+ const event = entry;
16411
+ const type = typeof event.type === "string" ? event.type : "";
16412
+ if (!type.startsWith("quality.")) return;
16413
+ if (typeof event.actor !== "string" || event.actor.trim() === "") {
16414
+ warn(`journal[${index}].actor`, "quality-event-no-actor", `Journal event ${index}: ${type} has no actor \u2014 human, agent and CI scores become indistinguishable`);
16415
+ }
16416
+ if (type === "quality.finding.resolved" && typeof event.finding_id === "string") {
16417
+ if (!openedInJournal.has(event.finding_id) && !seenFindingIds.has(event.finding_id)) {
16418
+ warn(
16419
+ `journal[${index}].finding_id`,
16420
+ "quality-resolved-never-opened",
16421
+ `Journal event ${index}: resolves finding "${event.finding_id}", which no quality.finding.opened event or stored finding ever declared`
16422
+ );
16423
+ }
16424
+ }
16425
+ });
16426
+ }
15965
16427
  for (const finding of crossCheckJournal(bundle)) findings.push(finding);
15966
16428
  return result();
15967
16429
  }
@@ -15969,6 +16431,58 @@ function validateBundle(input) {
15969
16431
  // ../schema/src/emit.ts
15970
16432
  var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
15971
16433
  var ENCODING_LEN = ENCODING.length;
16434
+ var TIME_LEN = 10;
16435
+ var RANDOM_LEN = 16;
16436
+ function encodeTime(time3, len) {
16437
+ let out = "";
16438
+ let t = time3;
16439
+ for (let i = len - 1; i >= 0; i -= 1) {
16440
+ const mod = t % ENCODING_LEN;
16441
+ out = ENCODING[mod] + out;
16442
+ t = (t - mod) / ENCODING_LEN;
16443
+ }
16444
+ return out;
16445
+ }
16446
+ function randomSymbols(len) {
16447
+ const bytes = new Uint8Array(len);
16448
+ globalThis.crypto.getRandomValues(bytes);
16449
+ return Array.from(bytes, (b) => b & 31);
16450
+ }
16451
+ function incrementSymbols(symbols) {
16452
+ const out = symbols.slice();
16453
+ for (let i = out.length - 1; i >= 0; i -= 1) {
16454
+ if (out[i] < ENCODING_LEN - 1) {
16455
+ out[i] += 1;
16456
+ return out;
16457
+ }
16458
+ out[i] = 0;
16459
+ }
16460
+ return randomSymbols(RANDOM_LEN);
16461
+ }
16462
+ var lastTime = -1;
16463
+ var lastRandom = [];
16464
+ function ulid3(seedTime = Date.now()) {
16465
+ if (seedTime > lastTime) {
16466
+ lastTime = seedTime;
16467
+ lastRandom = randomSymbols(RANDOM_LEN);
16468
+ } else {
16469
+ lastRandom = incrementSymbols(lastRandom);
16470
+ }
16471
+ return encodeTime(lastTime, TIME_LEN) + lastRandom.map((s) => ENCODING[s]).join("");
16472
+ }
16473
+ function makeEvent(type, payload = {}, options = {}) {
16474
+ const ts = options.ts instanceof Date ? options.ts.toISOString() : options.ts ?? (/* @__PURE__ */ new Date()).toISOString();
16475
+ const id = options.id ?? ulid3();
16476
+ const event = {
16477
+ id,
16478
+ ts,
16479
+ ...options.actor !== void 0 ? { actor: options.actor } : {},
16480
+ type,
16481
+ ...payload
16482
+ };
16483
+ const schema = JOURNAL_EVENT_SCHEMAS[type] ?? JournalEventSchema;
16484
+ return schema.parse(event);
16485
+ }
15972
16486
 
15973
16487
  // src/lib/bundle-io.ts
15974
16488
  function readBundle(filePath) {
@@ -15998,7 +16512,7 @@ function nodesByIdOf(bundle) {
15998
16512
  }
15999
16513
 
16000
16514
  // src/lib/journal-io.ts
16001
- import { appendFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
16515
+ import { appendFileSync, existsSync as existsSync2, mkdirSync, readdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
16002
16516
  import { dirname, join } from "node:path";
16003
16517
  var JOURNAL_SIDECAR = "journal.jsonl";
16004
16518
  function journalPathFor(bundlePath) {
@@ -16007,10 +16521,22 @@ function journalPathFor(bundlePath) {
16007
16521
  function archivePathFor(journalPath, version2) {
16008
16522
  return join(dirname(journalPath), "journal", `archive-${version2}.jsonl`);
16009
16523
  }
16524
+ function archivePathsFor(journalPath) {
16525
+ const dir = join(dirname(journalPath), "journal");
16526
+ if (!existsSync2(dir)) return [];
16527
+ return readdirSync(dir).filter((name) => name.startsWith("archive-") && name.endsWith(".jsonl")).sort().map((name) => join(dir, name));
16528
+ }
16010
16529
  function readJournalEvents(journalPath) {
16011
16530
  if (!existsSync2(journalPath)) return [];
16012
16531
  return parseJournalLines(readFileSync2(journalPath, "utf8")).events;
16013
16532
  }
16533
+ function readFullJournalEvents(journalPath) {
16534
+ const events = readJournalEvents(journalPath);
16535
+ for (const archivePath of archivePathsFor(journalPath)) {
16536
+ events.push(...readJournalEvents(archivePath));
16537
+ }
16538
+ return events;
16539
+ }
16014
16540
  function loadJournalEvents(bundle, bundlePath) {
16015
16541
  if (Array.isArray(bundle.journal)) return bundle.journal;
16016
16542
  return readJournalEvents(journalPathFor(bundlePath));
@@ -16029,6 +16555,14 @@ function appendJournalEvent(journalPath, event) {
16029
16555
  const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
16030
16556
  appendFileSync(journalPath, prefix + line);
16031
16557
  }
16558
+ function ensureJournalBaseline(journalPath, bundle, actor) {
16559
+ const snapshotNodeIds = (Array.isArray(bundle.nodes) ? bundle.nodes : []).map((node) => node?.id).filter((id) => typeof id === "string");
16560
+ const missing = missingProvenanceNodeIds(snapshotNodeIds, readFullJournalEvents(journalPath));
16561
+ if (missing.length === 0) return void 0;
16562
+ const event = makeEvent("journal.baseline", { node_ids: missing }, { actor });
16563
+ appendJournalEvent(journalPath, event);
16564
+ return event;
16565
+ }
16032
16566
  function compactSlice(journalPath, slice, version2) {
16033
16567
  if (slice.length === 0) return;
16034
16568
  const sliceIds = new Set(slice.map((ev) => ev.id));
@@ -16047,10 +16581,72 @@ function compactSlice(journalPath, slice, version2) {
16047
16581
  writeFileSync(journalPath, surviving.map(toLine).join(""));
16048
16582
  }
16049
16583
 
16584
+ // src/lib/kritik-io.ts
16585
+ import { existsSync as existsSync4 } from "node:fs";
16586
+ import { basename, dirname as dirname3, join as join3, resolve as resolve2 } from "node:path";
16587
+ import { fileURLToPath } from "node:url";
16588
+
16589
+ // ../schema/src/cli/kritik-paths.ts
16590
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
16591
+ import { dirname as dirname2, join as join2, resolve } from "node:path";
16592
+ var QUALITY_DIR = "docs/quality";
16593
+ var OVERLAY_FILE = "criteria.custom.json";
16594
+ var PACK_FILE = "library.json";
16595
+ function readJson(path) {
16596
+ const text = readFileSync3(path, "utf8");
16597
+ try {
16598
+ return JSON.parse(text);
16599
+ } catch (e) {
16600
+ throw new Error(`${path}: not valid JSON \u2014 ${e.message}`);
16601
+ }
16602
+ }
16603
+ var overlayPath = (root) => join2(root, QUALITY_DIR, OVERLAY_FILE);
16604
+ function loadOverlay(root) {
16605
+ const path = overlayPath(root);
16606
+ return existsSync3(path) ? readJson(path) : null;
16607
+ }
16608
+
16609
+ // src/lib/kritik-io.ts
16610
+ var KRITIK_ACTOR = "arkaik-cli";
16611
+ var DEFAULT_BUNDLE_PATH = join3("docs", "arkaik", "bundle.json");
16612
+ var VENDORED_PACK = join3(QUALITY_DIR, PACK_FILE);
16613
+ var BUNDLED_PACK = join3(dirname3(fileURLToPath(import.meta.url)), "assets", "kritik", "library.json");
16614
+ function resolvePack(root) {
16615
+ const vendored = join3(root, VENDORED_PACK);
16616
+ if (existsSync4(vendored)) return { library: readJson(vendored), path: vendored, vendored: true };
16617
+ if (!existsSync4(BUNDLED_PACK)) {
16618
+ throw new Error(
16619
+ `no criteria pack found. Looked in:
16620
+ ${vendored}
16621
+ ${BUNDLED_PACK}
16622
+ The second is shipped with this CLI, so its absence means a broken install \u2014 reinstall \`arkaik\`.`
16623
+ );
16624
+ }
16625
+ return { library: readJson(BUNDLED_PACK), path: BUNDLED_PACK, vendored: false };
16626
+ }
16627
+ function loadKritikLibrary(root) {
16628
+ const pack = resolvePack(root);
16629
+ return { library: mergeKritikLibrary(pack.library, loadOverlay(root)), pack };
16630
+ }
16631
+ function resolveJournal(root, bundlePath) {
16632
+ const resolved = bundlePath ?? join3(root, DEFAULT_BUNDLE_PATH);
16633
+ return { bundlePath: resolved, journalPath: journalPathFor(resolved), present: existsSync4(resolved) };
16634
+ }
16635
+ function appendQualityEvents(root, inputs, options = {}) {
16636
+ if (inputs.length === 0) return { events: [] };
16637
+ const actor = options.actor ?? KRITIK_ACTOR;
16638
+ const journal = resolveJournal(root, options.bundlePath);
16639
+ if (!journal.present) return { events: [] };
16640
+ const bundle = readBundle(journal.bundlePath);
16641
+ const baseline = ensureJournalBaseline(journal.journalPath, bundle, actor);
16642
+ const events = inputs.map((input) => makeEvent(input.type, input.payload, { actor }));
16643
+ for (const event of events) appendJournalEvent(journal.journalPath, event);
16644
+ return { journalPath: journal.journalPath, events, ...baseline !== void 0 ? { baseline } : {} };
16645
+ }
16646
+
16050
16647
  // src/lib/bundle-validate.ts
16051
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
16052
- import { dirname as dirname2, join as join2 } from "node:path";
16053
- var JOURNAL_SIDECAR2 = "journal.jsonl";
16648
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
16649
+ import { basename as basename2 } from "node:path";
16054
16650
  function validateBundleAt(filePath) {
16055
16651
  const bundle = readBundle(filePath);
16056
16652
  const loose = bundle;
@@ -16059,31 +16655,69 @@ function validateBundleAt(filePath) {
16059
16655
  }
16060
16656
  let sidecarFindings = [];
16061
16657
  let sidecarLoaded = false;
16658
+ const archiveFindings = [];
16659
+ const archivesLoaded = [];
16062
16660
  if (loose.journal === void 0) {
16063
- const sidecarPath = join2(dirname2(filePath), JOURNAL_SIDECAR2);
16064
- if (existsSync3(sidecarPath)) {
16065
- const { events, findings } = parseJournalLines(readFileSync3(sidecarPath, "utf8"));
16661
+ const sidecarPath = journalPathFor(filePath);
16662
+ const folded = [];
16663
+ if (existsSync5(sidecarPath)) {
16664
+ const { events, findings } = parseJournalLines(readFileSync4(sidecarPath, "utf8"));
16066
16665
  sidecarFindings = findings;
16067
16666
  sidecarLoaded = true;
16068
- loose.journal = events;
16667
+ folded.push(...events);
16668
+ }
16669
+ for (const archivePath of archivePathsFor(sidecarPath)) {
16670
+ const { events, findings } = parseJournalLines(readFileSync4(archivePath, "utf8"));
16671
+ const file2 = basename2(archivePath);
16672
+ for (const finding of findings) archiveFindings.push({ ...finding, file: file2 });
16673
+ archivesLoaded.push(file2);
16674
+ folded.push(...events);
16069
16675
  }
16676
+ if (sidecarLoaded || archivesLoaded.length > 0) loose.journal = folded;
16070
16677
  }
16071
16678
  const nodes = Array.isArray(loose.nodes) ? loose.nodes : [];
16072
16679
  const edges = Array.isArray(loose.edges) ? loose.edges : [];
16073
16680
  const journal = Array.isArray(loose.journal) ? loose.journal : [];
16074
16681
  const result = validateBundle(bundle);
16075
- const valid = sidecarFindings.length === 0 && result.errors.length === 0;
16076
- return { bundle, nodes, edges, journal, sidecarLoaded, sidecarFindings, result, valid };
16682
+ const valid = sidecarFindings.length === 0 && archiveFindings.length === 0 && result.errors.length === 0;
16683
+ return {
16684
+ bundle,
16685
+ nodes,
16686
+ edges,
16687
+ journal,
16688
+ sidecarLoaded,
16689
+ sidecarFindings,
16690
+ archivesLoaded,
16691
+ archiveFindings,
16692
+ result,
16693
+ valid
16694
+ };
16695
+ }
16696
+ function journalLineErrorLines(v) {
16697
+ return [
16698
+ ...v.sidecarFindings.map((f) => `ERROR [${f.rule}] line ${f.line}: ${f.message}`),
16699
+ ...v.archiveFindings.map((f) => `ERROR [${f.rule}] ${f.file} line ${f.line}: ${f.message}`)
16700
+ ];
16077
16701
  }
16078
16702
  export {
16079
16703
  JOURNAL_SIDECAR,
16704
+ KRITIK_ACTOR,
16705
+ VENDORED_PACK,
16080
16706
  appendJournalEvent,
16707
+ appendQualityEvents,
16081
16708
  archivePathFor,
16709
+ archivePathsFor,
16082
16710
  compactSlice,
16711
+ ensureJournalBaseline,
16712
+ journalLineErrorLines,
16083
16713
  journalPathFor,
16084
16714
  loadJournalEvents,
16715
+ loadKritikLibrary,
16085
16716
  nodesByIdOf,
16086
16717
  readBundle,
16718
+ readFullJournalEvents,
16087
16719
  readJournalEvents,
16720
+ resolveJournal,
16721
+ resolvePack,
16088
16722
  validateBundleAt
16089
16723
  };