arkaik 0.1.1 → 0.2.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
@@ -6,33 +6,6 @@ var __export = (target, all) => {
6
6
 
7
7
  // src/lib/bundle-io.ts
8
8
  import { existsSync, readFileSync } from "node:fs";
9
- function readBundle(filePath) {
10
- if (!existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
11
- let parsed;
12
- try {
13
- parsed = JSON.parse(readFileSync(filePath, "utf8"));
14
- } catch (e) {
15
- throw new Error(`Cannot parse JSON \u2014 ${e.message}`);
16
- }
17
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
18
- throw new Error("Bundle must be a JSON object.");
19
- }
20
- return parsed;
21
- }
22
- function nodesByIdOf(bundle) {
23
- const nodes = Array.isArray(bundle.nodes) ? bundle.nodes : [];
24
- const map2 = /* @__PURE__ */ new Map();
25
- for (const n of nodes) {
26
- if (n !== null && typeof n === "object" && typeof n.id === "string") {
27
- map2.set(n.id, n);
28
- }
29
- }
30
- return map2;
31
- }
32
-
33
- // src/lib/journal-io.ts
34
- import { appendFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
35
- import { dirname, join } from "node:path";
36
9
 
37
10
  // ../../node_modules/zod/v4/classic/external.js
38
11
  var external_exports = {};
@@ -540,7 +513,7 @@ __export(core_exports2, {
540
513
  parse: () => parse,
541
514
  parseAsync: () => parseAsync,
542
515
  prettifyError: () => prettifyError,
543
- process: () => process,
516
+ process: () => process2,
544
517
  regexes: () => regexes_exports,
545
518
  registry: () => registry,
546
519
  safeDecode: () => safeDecode,
@@ -11465,7 +11438,7 @@ function initializeContext(params) {
11465
11438
  external: params?.external ?? void 0
11466
11439
  };
11467
11440
  }
11468
- function process(schema, ctx, _params = { path: [], schemaPath: [] }) {
11441
+ function process2(schema, ctx, _params = { path: [], schemaPath: [] }) {
11469
11442
  var _a3;
11470
11443
  const def = schema._zod.def;
11471
11444
  const seen = ctx.seen.get(schema);
@@ -11502,7 +11475,7 @@ function process(schema, ctx, _params = { path: [], schemaPath: [] }) {
11502
11475
  if (parent) {
11503
11476
  if (!result.ref)
11504
11477
  result.ref = parent;
11505
- process(parent, ctx, params);
11478
+ process2(parent, ctx, params);
11506
11479
  ctx.seen.get(parent).isParent = true;
11507
11480
  }
11508
11481
  }
@@ -11790,14 +11763,14 @@ function isTransforming(_schema, _ctx) {
11790
11763
  }
11791
11764
  var createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
11792
11765
  const ctx = initializeContext({ ...params, processors });
11793
- process(schema, ctx);
11766
+ process2(schema, ctx);
11794
11767
  extractDefs(ctx, schema);
11795
11768
  return finalize(ctx, schema);
11796
11769
  };
11797
11770
  var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {
11798
11771
  const { libraryOptions, target } = params ?? {};
11799
11772
  const ctx = initializeContext({ ...libraryOptions ?? {}, target, io, processors });
11800
- process(schema, ctx);
11773
+ process2(schema, ctx);
11801
11774
  extractDefs(ctx, schema);
11802
11775
  return finalize(ctx, schema);
11803
11776
  };
@@ -12043,7 +12016,7 @@ var arrayProcessor = (schema, ctx, _json, params) => {
12043
12016
  if (typeof maximum === "number")
12044
12017
  json2.maxItems = maximum;
12045
12018
  json2.type = "array";
12046
- json2.items = process(def.element, ctx, {
12019
+ json2.items = process2(def.element, ctx, {
12047
12020
  ...params,
12048
12021
  path: [...params.path, "items"]
12049
12022
  });
@@ -12055,7 +12028,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
12055
12028
  json2.properties = {};
12056
12029
  const shape = def.shape;
12057
12030
  for (const key in shape) {
12058
- json2.properties[key] = process(shape[key], ctx, {
12031
+ json2.properties[key] = process2(shape[key], ctx, {
12059
12032
  ...params,
12060
12033
  path: [...params.path, "properties", key]
12061
12034
  });
@@ -12078,7 +12051,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
12078
12051
  if (ctx.io === "output")
12079
12052
  json2.additionalProperties = false;
12080
12053
  } else if (def.catchall) {
12081
- json2.additionalProperties = process(def.catchall, ctx, {
12054
+ json2.additionalProperties = process2(def.catchall, ctx, {
12082
12055
  ...params,
12083
12056
  path: [...params.path, "additionalProperties"]
12084
12057
  });
@@ -12087,7 +12060,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
12087
12060
  var unionProcessor = (schema, ctx, json2, params) => {
12088
12061
  const def = schema._zod.def;
12089
12062
  const isExclusive = def.inclusive === false;
12090
- const options = def.options.map((x, i) => process(x, ctx, {
12063
+ const options = def.options.map((x, i) => process2(x, ctx, {
12091
12064
  ...params,
12092
12065
  path: [...params.path, isExclusive ? "oneOf" : "anyOf", i]
12093
12066
  }));
@@ -12099,11 +12072,11 @@ var unionProcessor = (schema, ctx, json2, params) => {
12099
12072
  };
12100
12073
  var intersectionProcessor = (schema, ctx, json2, params) => {
12101
12074
  const def = schema._zod.def;
12102
- const a = process(def.left, ctx, {
12075
+ const a = process2(def.left, ctx, {
12103
12076
  ...params,
12104
12077
  path: [...params.path, "allOf", 0]
12105
12078
  });
12106
- const b = process(def.right, ctx, {
12079
+ const b = process2(def.right, ctx, {
12107
12080
  ...params,
12108
12081
  path: [...params.path, "allOf", 1]
12109
12082
  });
@@ -12120,11 +12093,11 @@ var tupleProcessor = (schema, ctx, _json, params) => {
12120
12093
  json2.type = "array";
12121
12094
  const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
12122
12095
  const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
12123
- const prefixItems = def.items.map((x, i) => process(x, ctx, {
12096
+ const prefixItems = def.items.map((x, i) => process2(x, ctx, {
12124
12097
  ...params,
12125
12098
  path: [...params.path, prefixPath, i]
12126
12099
  }));
12127
- const rest = def.rest ? process(def.rest, ctx, {
12100
+ const rest = def.rest ? process2(def.rest, ctx, {
12128
12101
  ...params,
12129
12102
  path: [...params.path, restPath, ...ctx.target === "openapi-3.0" ? [def.items.length] : []]
12130
12103
  }) : null;
@@ -12164,7 +12137,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
12164
12137
  const keyBag = keyType._zod.bag;
12165
12138
  const patterns = keyBag?.patterns;
12166
12139
  if (def.mode === "loose" && patterns && patterns.size > 0) {
12167
- const valueSchema = process(def.valueType, ctx, {
12140
+ const valueSchema = process2(def.valueType, ctx, {
12168
12141
  ...params,
12169
12142
  path: [...params.path, "patternProperties", "*"]
12170
12143
  });
@@ -12174,12 +12147,12 @@ var recordProcessor = (schema, ctx, _json, params) => {
12174
12147
  }
12175
12148
  } else {
12176
12149
  if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") {
12177
- json2.propertyNames = process(def.keyType, ctx, {
12150
+ json2.propertyNames = process2(def.keyType, ctx, {
12178
12151
  ...params,
12179
12152
  path: [...params.path, "propertyNames"]
12180
12153
  });
12181
12154
  }
12182
- json2.additionalProperties = process(def.valueType, ctx, {
12155
+ json2.additionalProperties = process2(def.valueType, ctx, {
12183
12156
  ...params,
12184
12157
  path: [...params.path, "additionalProperties"]
12185
12158
  });
@@ -12194,7 +12167,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
12194
12167
  };
12195
12168
  var nullableProcessor = (schema, ctx, json2, params) => {
12196
12169
  const def = schema._zod.def;
12197
- const inner = process(def.innerType, ctx, params);
12170
+ const inner = process2(def.innerType, ctx, params);
12198
12171
  const seen = ctx.seen.get(schema);
12199
12172
  if (ctx.target === "openapi-3.0") {
12200
12173
  seen.ref = def.innerType;
@@ -12205,20 +12178,20 @@ var nullableProcessor = (schema, ctx, json2, params) => {
12205
12178
  };
12206
12179
  var nonoptionalProcessor = (schema, ctx, _json, params) => {
12207
12180
  const def = schema._zod.def;
12208
- process(def.innerType, ctx, params);
12181
+ process2(def.innerType, ctx, params);
12209
12182
  const seen = ctx.seen.get(schema);
12210
12183
  seen.ref = def.innerType;
12211
12184
  };
12212
12185
  var defaultProcessor = (schema, ctx, json2, params) => {
12213
12186
  const def = schema._zod.def;
12214
- process(def.innerType, ctx, params);
12187
+ process2(def.innerType, ctx, params);
12215
12188
  const seen = ctx.seen.get(schema);
12216
12189
  seen.ref = def.innerType;
12217
12190
  json2.default = JSON.parse(JSON.stringify(def.defaultValue));
12218
12191
  };
12219
12192
  var prefaultProcessor = (schema, ctx, json2, params) => {
12220
12193
  const def = schema._zod.def;
12221
- process(def.innerType, ctx, params);
12194
+ process2(def.innerType, ctx, params);
12222
12195
  const seen = ctx.seen.get(schema);
12223
12196
  seen.ref = def.innerType;
12224
12197
  if (ctx.io === "input")
@@ -12226,7 +12199,7 @@ var prefaultProcessor = (schema, ctx, json2, params) => {
12226
12199
  };
12227
12200
  var catchProcessor = (schema, ctx, json2, params) => {
12228
12201
  const def = schema._zod.def;
12229
- process(def.innerType, ctx, params);
12202
+ process2(def.innerType, ctx, params);
12230
12203
  const seen = ctx.seen.get(schema);
12231
12204
  seen.ref = def.innerType;
12232
12205
  let catchValue;
@@ -12241,32 +12214,32 @@ var pipeProcessor = (schema, ctx, _json, params) => {
12241
12214
  const def = schema._zod.def;
12242
12215
  const inIsTransform = def.in._zod.traits.has("$ZodTransform");
12243
12216
  const innerType = ctx.io === "input" ? inIsTransform ? def.out : def.in : def.out;
12244
- process(innerType, ctx, params);
12217
+ process2(innerType, ctx, params);
12245
12218
  const seen = ctx.seen.get(schema);
12246
12219
  seen.ref = innerType;
12247
12220
  };
12248
12221
  var readonlyProcessor = (schema, ctx, json2, params) => {
12249
12222
  const def = schema._zod.def;
12250
- process(def.innerType, ctx, params);
12223
+ process2(def.innerType, ctx, params);
12251
12224
  const seen = ctx.seen.get(schema);
12252
12225
  seen.ref = def.innerType;
12253
12226
  json2.readOnly = true;
12254
12227
  };
12255
12228
  var promiseProcessor = (schema, ctx, _json, params) => {
12256
12229
  const def = schema._zod.def;
12257
- process(def.innerType, ctx, params);
12230
+ process2(def.innerType, ctx, params);
12258
12231
  const seen = ctx.seen.get(schema);
12259
12232
  seen.ref = def.innerType;
12260
12233
  };
12261
12234
  var optionalProcessor = (schema, ctx, _json, params) => {
12262
12235
  const def = schema._zod.def;
12263
- process(def.innerType, ctx, params);
12236
+ process2(def.innerType, ctx, params);
12264
12237
  const seen = ctx.seen.get(schema);
12265
12238
  seen.ref = def.innerType;
12266
12239
  };
12267
12240
  var lazyProcessor = (schema, ctx, _json, params) => {
12268
12241
  const innerType = schema._zod.innerType;
12269
- process(innerType, ctx, params);
12242
+ process2(innerType, ctx, params);
12270
12243
  const seen = ctx.seen.get(schema);
12271
12244
  seen.ref = innerType;
12272
12245
  };
@@ -12318,7 +12291,7 @@ function toJSONSchema(input, params) {
12318
12291
  const defs = {};
12319
12292
  for (const entry of registry2._idmap.entries()) {
12320
12293
  const [_, schema] = entry;
12321
- process(schema, ctx2);
12294
+ process2(schema, ctx2);
12322
12295
  }
12323
12296
  const schemas = {};
12324
12297
  const external = {
@@ -12341,7 +12314,7 @@ function toJSONSchema(input, params) {
12341
12314
  return { schemas };
12342
12315
  }
12343
12316
  const ctx = initializeContext({ ...params, processors: allProcessors });
12344
- process(input, ctx);
12317
+ process2(input, ctx);
12345
12318
  extractDefs(ctx, input);
12346
12319
  return finalize(ctx, input);
12347
12320
  }
@@ -12399,7 +12372,7 @@ var JSONSchemaGenerator = class {
12399
12372
  * This must be called before emit().
12400
12373
  */
12401
12374
  process(schema, _params = { path: [], schemaPath: [] }) {
12402
- return process(schema, this.ctx, _params);
12375
+ return process2(schema, this.ctx, _params);
12403
12376
  }
12404
12377
  /**
12405
12378
  * Emit the final JSON Schema after processing.
@@ -14549,19 +14522,58 @@ function date4(params) {
14549
14522
  config(en_default());
14550
14523
 
14551
14524
  // ../schema/src/ids.ts
14552
- var SPECIES_IDS = ["flow", "view", "data-model", "api-endpoint", "acceptance"];
14525
+ var SPECIES_IDS = ["flow", "view", "data-model", "api-endpoint", "acceptance", "decision"];
14553
14526
  var STATUS_IDS = [
14554
14527
  "idea",
14528
+ "discovery",
14555
14529
  "backlog",
14556
- "prioritized",
14557
14530
  "development",
14558
14531
  "releasing",
14559
14532
  "live",
14560
- "archived",
14561
- "blocked"
14533
+ "archived"
14562
14534
  ];
14563
14535
  var PLATFORM_IDS = ["web", "ios", "android"];
14564
- var EDGE_TYPE_IDS = ["composes", "calls", "displays", "queries", "covers"];
14536
+ var EDGE_TYPE_IDS = ["composes", "calls", "displays", "queries", "covers", "supersedes", "generates", "impacts"];
14537
+ var VALID_EDGE_SEMANTICS = {
14538
+ composes: [
14539
+ ["flow", "view"],
14540
+ ["flow", "flow"],
14541
+ ["view", "flow"],
14542
+ ["view", "view"]
14543
+ ],
14544
+ // `calls` names the initiator, which is why it runs in both directions
14545
+ // between a view and an endpoint: view → api is the outbound/write
14546
+ // affordance, api → view the inbound/read one a server opens itself (a
14547
+ // webhook, an SSE channel, a push). Both project onto the View card
14548
+ // (docs/graph-model.md § Edge Types). Consumers that walk `calls` must
14549
+ // therefore not assume it points down into the system layer — see
14550
+ // `buildProductUsageIndex` in ./products.ts, which restricts hops by
14551
+ // target species for exactly this reason.
14552
+ calls: [
14553
+ ["view", "api-endpoint"],
14554
+ ["flow", "api-endpoint"],
14555
+ ["api-endpoint", "api-endpoint"],
14556
+ ["api-endpoint", "view"]
14557
+ ],
14558
+ displays: [["view", "data-model"]],
14559
+ queries: [["api-endpoint", "data-model"]],
14560
+ covers: [
14561
+ ["acceptance", "view"],
14562
+ ["acceptance", "flow"]
14563
+ ],
14564
+ // Decision edges (cycle 2). `generates` and `impacts` are deliberately
14565
+ // disjoint: an acceptance is *generated* by a decision, never merely
14566
+ // impacted, so `impacts` does not admit an acceptance target
14567
+ // (docs/superpowers/specs/2026-08-03-decisions-species-design.md §3).
14568
+ supersedes: [["decision", "decision"]],
14569
+ generates: [["decision", "acceptance"]],
14570
+ impacts: [
14571
+ ["decision", "flow"],
14572
+ ["decision", "view"],
14573
+ ["decision", "data-model"],
14574
+ ["decision", "api-endpoint"]
14575
+ ]
14576
+ };
14565
14577
  var VALUE_TIER_IDS = ["functional", "emotional", "life-changing", "social-impact"];
14566
14578
  var VALUE_IDS = [
14567
14579
  // functional (14)
@@ -14600,6 +14612,106 @@ var VALUE_IDS = [
14600
14612
  "self-transcendence"
14601
14613
  ];
14602
14614
 
14615
+ // ../schema/src/legacy-status.ts
14616
+ var LEGACY_STATUS_IDS = ["prioritized", "blocked"];
14617
+ var LEGACY_STATUS_ALIASES = {
14618
+ prioritized: "backlog",
14619
+ blocked: "development"
14620
+ };
14621
+ var STATUS_VOCABULARY_VERSION = 3;
14622
+ var BLOCKED_BY_MIGRATION_NOTE = "migrated from legacy blocked status";
14623
+ function normalizeStatus(value) {
14624
+ if (STATUS_IDS.includes(value)) return value;
14625
+ return LEGACY_STATUS_ALIASES[value];
14626
+ }
14627
+ var LEGACY_ALIAS_REMAP = { ...LEGACY_STATUS_ALIASES };
14628
+ var LEGACY_VOCABULARY_REMAP = {
14629
+ backlog: "idea",
14630
+ ...LEGACY_STATUS_ALIASES
14631
+ };
14632
+ function migrateNode(node, remap) {
14633
+ let changed = false;
14634
+ let wasBlocked = node.status === "blocked";
14635
+ const mappedStatus = remap[node.status];
14636
+ const status = mappedStatus ?? node.status;
14637
+ if (mappedStatus !== void 0) changed = true;
14638
+ let metadata = node.metadata;
14639
+ const platformStatuses = node.metadata?.platformStatuses;
14640
+ if (platformStatuses !== void 0) {
14641
+ let anyMapped = false;
14642
+ const next = {};
14643
+ for (const [platform, value] of Object.entries(platformStatuses)) {
14644
+ if (value === "blocked") wasBlocked = true;
14645
+ const mapped = remap[value];
14646
+ next[platform] = mapped ?? value;
14647
+ if (mapped !== void 0) anyMapped = true;
14648
+ }
14649
+ if (anyMapped) {
14650
+ metadata = { ...metadata, platformStatuses: next };
14651
+ changed = true;
14652
+ }
14653
+ }
14654
+ const refs = node.metadata?.refs;
14655
+ if (Array.isArray(refs)) {
14656
+ let anyMapped = false;
14657
+ const nextRefs = refs.map((ref) => {
14658
+ if (ref.status_mapped === void 0) return ref;
14659
+ const mapped = remap[ref.status_mapped];
14660
+ if (mapped === void 0) return ref;
14661
+ anyMapped = true;
14662
+ return { ...ref, status_mapped: mapped };
14663
+ });
14664
+ if (anyMapped) {
14665
+ metadata = { ...metadata, refs: nextRefs };
14666
+ changed = true;
14667
+ }
14668
+ }
14669
+ if (wasBlocked && !metadata?.blocked_by) {
14670
+ metadata = { ...metadata, blocked_by: BLOCKED_BY_MIGRATION_NOTE };
14671
+ changed = true;
14672
+ }
14673
+ if (!changed) return node;
14674
+ return { ...node, status, ...metadata !== void 0 ? { metadata } : {} };
14675
+ }
14676
+ function migrateStatusVocabulary(bundle) {
14677
+ const current = typeof bundle.schema_version === "number" && bundle.schema_version >= STATUS_VOCABULARY_VERSION;
14678
+ const remap = current ? LEGACY_ALIAS_REMAP : LEGACY_VOCABULARY_REMAP;
14679
+ const nodes = bundle.nodes.map((node) => migrateNode(node, remap));
14680
+ if (current) {
14681
+ const untouched = nodes.every((node, i) => node === bundle.nodes[i]);
14682
+ return untouched ? bundle : { ...bundle, nodes };
14683
+ }
14684
+ return { ...bundle, schema_version: STATUS_VOCABULARY_VERSION, nodes };
14685
+ }
14686
+
14687
+ // ../schema/src/decision.ts
14688
+ var DECISION_STATUS_IDS = [
14689
+ "proposed",
14690
+ "approved",
14691
+ "enacted",
14692
+ "rejected",
14693
+ "deprecated",
14694
+ "superseded"
14695
+ ];
14696
+ function lifecycleStatusForDecision(decisionStatus) {
14697
+ switch (decisionStatus) {
14698
+ case "proposed":
14699
+ return "discovery";
14700
+ case "approved":
14701
+ return "backlog";
14702
+ case "enacted":
14703
+ return "live";
14704
+ case "rejected":
14705
+ case "deprecated":
14706
+ case "superseded":
14707
+ return "archived";
14708
+ }
14709
+ }
14710
+ function decisionStatusOf(node) {
14711
+ const raw = node.metadata?.decision_status;
14712
+ return DECISION_STATUS_IDS.includes(raw) ? raw : "proposed";
14713
+ }
14714
+
14603
14715
  // ../schema/src/enums.ts
14604
14716
  var SpeciesSchema = external_exports.enum(SPECIES_IDS).meta({
14605
14717
  id: "Species",
@@ -14609,6 +14721,10 @@ var StatusSchema = external_exports.enum(STATUS_IDS).meta({
14609
14721
  id: "Status",
14610
14722
  description: "Lifecycle status of a node."
14611
14723
  });
14724
+ var AnyStatusSchema = external_exports.enum([...STATUS_IDS, ...LEGACY_STATUS_IDS]).meta({
14725
+ id: "AnyStatus",
14726
+ description: "Lifecycle status as stored: the current vocabulary, or a legacy id (prioritized, blocked) accepted from pre-v3 bundles and migrated on load."
14727
+ });
14612
14728
  var PlatformSchema = external_exports.enum(PLATFORM_IDS).meta({
14613
14729
  id: "Platform",
14614
14730
  description: "Target platform."
@@ -14625,6 +14741,10 @@ var ValueSchema = external_exports.enum(VALUE_IDS).meta({
14625
14741
  id: "Value",
14626
14742
  description: "A Bain B2C Elements-of-Value element served by an acceptance (spec \xA73.2)."
14627
14743
  });
14744
+ var DecisionStatusSchema = external_exports.enum(DECISION_STATUS_IDS).meta({
14745
+ id: "DecisionStatus",
14746
+ description: "Decision nodes only: proposed \u2192 approved (agreed, not yet reality) \u2192 enacted (in effect); terminal: rejected, deprecated, superseded. Not a lifecycle status \u2014 the node's status field is kept in sync (proposed\u2192discovery, approved\u2192backlog, enacted\u2192live, terminals\u2192archived)."
14747
+ });
14628
14748
 
14629
14749
  // ../schema/src/id-gen.ts
14630
14750
  var SPECIES_PREFIXES = {
@@ -14632,7 +14752,8 @@ var SPECIES_PREFIXES = {
14632
14752
  view: "V-",
14633
14753
  "data-model": "DM-",
14634
14754
  "api-endpoint": "API-",
14635
- acceptance: "AC-"
14755
+ acceptance: "AC-",
14756
+ decision: "DEC-"
14636
14757
  };
14637
14758
 
14638
14759
  // ../schema/src/playlist.ts
@@ -14730,6 +14851,7 @@ function parseJournalLines(text) {
14730
14851
  var NODE_REF_FIELDS = {
14731
14852
  "node.updated": ["node_id"],
14732
14853
  "node.status_changed": ["node_id"],
14854
+ "decision.status_changed": ["node_id"],
14733
14855
  "node.deleted": ["node_id"],
14734
14856
  "edge.added": ["source_id", "target_id"],
14735
14857
  "ref.added": ["node_id"],
@@ -14738,6 +14860,12 @@ var NODE_REF_FIELDS = {
14738
14860
  "idea.proposed": ["node_id"],
14739
14861
  "request.filed": ["node_id"]
14740
14862
  };
14863
+ function statusesAgree(last, snapshot) {
14864
+ if (last === snapshot) return true;
14865
+ if (typeof snapshot !== "string") return false;
14866
+ if (last === "backlog" && snapshot === "idea") return true;
14867
+ return (normalizeStatus(last) ?? last) === (normalizeStatus(snapshot) ?? snapshot);
14868
+ }
14741
14869
  function crossCheckJournal(bundle) {
14742
14870
  const findings = [];
14743
14871
  const journalRaw = bundle.journal;
@@ -14756,9 +14884,15 @@ function crossCheckJournal(bundle) {
14756
14884
  const nodesRaw = Array.isArray(bundle.nodes) ? bundle.nodes : [];
14757
14885
  const edgesRaw = Array.isArray(bundle.edges) ? bundle.edges : [];
14758
14886
  const snapshotNodeStatus = /* @__PURE__ */ new Map();
14887
+ const snapshotDecisionStatus = /* @__PURE__ */ new Map();
14759
14888
  for (const n of nodesRaw) {
14760
14889
  const id = str(n?.id);
14761
- if (id !== void 0) snapshotNodeStatus.set(id, n.status);
14890
+ if (id !== void 0) {
14891
+ snapshotNodeStatus.set(id, n.status);
14892
+ const metadata = n.metadata;
14893
+ const decisionStatus = metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata.decision_status : void 0;
14894
+ snapshotDecisionStatus.set(id, decisionStatus ?? "proposed");
14895
+ }
14762
14896
  }
14763
14897
  const snapshotEdgeIds = /* @__PURE__ */ new Set();
14764
14898
  for (const e of edgesRaw) {
@@ -14795,6 +14929,7 @@ function crossCheckJournal(bundle) {
14795
14929
  });
14796
14930
  const everNodes = new Set(snapshotNodeStatus.keys());
14797
14931
  const everEdges = new Set(snapshotEdgeIds);
14932
+ const baselined = /* @__PURE__ */ new Set();
14798
14933
  for (const { ev } of valid) {
14799
14934
  if (ev.type === "node.created") {
14800
14935
  const nid = str(ev.node_id);
@@ -14802,11 +14937,20 @@ function crossCheckJournal(bundle) {
14802
14937
  } else if (ev.type === "edge.added") {
14803
14938
  const eid = str(ev.edge_id);
14804
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
+ }
14805
14948
  }
14806
14949
  }
14807
14950
  const ordered = orderEvents(valid.map((v) => v.ev));
14808
14951
  const created = /* @__PURE__ */ new Set();
14809
14952
  const lastProjectStatus = /* @__PURE__ */ new Map();
14953
+ const lastDecisionStatus = /* @__PURE__ */ new Map();
14810
14954
  for (const ev of ordered) {
14811
14955
  if (ev.type === "node.created") {
14812
14956
  const nid = str(ev.node_id);
@@ -14817,6 +14961,12 @@ function crossCheckJournal(bundle) {
14817
14961
  const to = str(ev.to);
14818
14962
  if (to !== void 0) lastProjectStatus.set(nid, to);
14819
14963
  }
14964
+ } else if (ev.type === "decision.status_changed") {
14965
+ const nid = str(ev.node_id);
14966
+ if (nid) {
14967
+ const to = str(ev.to);
14968
+ if (to !== void 0) lastDecisionStatus.set(nid, to);
14969
+ }
14820
14970
  }
14821
14971
  }
14822
14972
  for (const { ev, index } of valid) {
@@ -14834,6 +14984,19 @@ function crossCheckJournal(bundle) {
14834
14984
  }
14835
14985
  }
14836
14986
  }
14987
+ if (ev.type === "deliverable.shipped" && Array.isArray(ev.node_ids)) {
14988
+ ev.node_ids.forEach((raw, i) => {
14989
+ const ref = str(raw);
14990
+ if (ref !== void 0 && !everNodes.has(ref)) {
14991
+ findings.push({
14992
+ path: `journal[${index}].node_ids[${i}]`,
14993
+ rule: "journal-dangling-node-ref",
14994
+ message: `journal[${index}] (${ev.type}): references node "${ref}" that never existed in the snapshot or journal.`,
14995
+ severity: "error"
14996
+ });
14997
+ }
14998
+ });
14999
+ }
14837
15000
  if (ev.type === "edge.removed") {
14838
15001
  const ref = str(ev.edge_id);
14839
15002
  if (ref !== void 0 && !everEdges.has(ref)) {
@@ -14847,7 +15010,7 @@ function crossCheckJournal(bundle) {
14847
15010
  }
14848
15011
  }
14849
15012
  for (const [nodeId, status] of snapshotNodeStatus) {
14850
- if (!created.has(nodeId)) {
15013
+ if (!created.has(nodeId) && !baselined.has(nodeId)) {
14851
15014
  findings.push({
14852
15015
  path: "journal",
14853
15016
  rule: "journal-missing-node-created",
@@ -14856,7 +15019,7 @@ function crossCheckJournal(bundle) {
14856
15019
  });
14857
15020
  }
14858
15021
  const last = lastProjectStatus.get(nodeId);
14859
- if (last !== void 0 && last !== status) {
15022
+ if (last !== void 0 && !statusesAgree(last, status)) {
14860
15023
  findings.push({
14861
15024
  path: "journal",
14862
15025
  rule: "journal-status-mismatch",
@@ -14865,8 +15028,40 @@ function crossCheckJournal(bundle) {
14865
15028
  });
14866
15029
  }
14867
15030
  }
15031
+ for (const [nodeId, last] of lastDecisionStatus) {
15032
+ if (!snapshotDecisionStatus.has(nodeId)) continue;
15033
+ const current = snapshotDecisionStatus.get(nodeId) ?? "proposed";
15034
+ if (last !== current) {
15035
+ findings.push({
15036
+ path: "journal",
15037
+ rule: "journal-decision-status-mismatch",
15038
+ message: `Node "${nodeId}": journal's last decision.status_changed.to "${last}" disagrees with snapshot decision_status "${String(current)}".`,
15039
+ severity: "error"
15040
+ });
15041
+ }
15042
+ }
14868
15043
  return findings;
14869
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
+ }
14870
15065
 
14871
15066
  // ../schema/src/journal-events.ts
14872
15067
  var envelope = {
@@ -14895,10 +15090,19 @@ var NodeStatusChangedEventSchema = external_exports.object({
14895
15090
  ...envelope,
14896
15091
  type: external_exports.literal("node.status_changed"),
14897
15092
  node_id: external_exports.string(),
14898
- from: StatusSchema,
14899
- to: StatusSchema,
15093
+ // History is never rewritten (docs/spec/journal.md): pre-v3 events keep
15094
+ // their legacy status ids, so strict per-type validation must accept them.
15095
+ from: AnyStatusSchema,
15096
+ to: AnyStatusSchema,
14900
15097
  platform: PlatformSchema.optional()
14901
15098
  }).catchall(external_exports.unknown());
15099
+ var DecisionStatusChangedEventSchema = external_exports.object({
15100
+ ...envelope,
15101
+ type: external_exports.literal("decision.status_changed"),
15102
+ node_id: external_exports.string(),
15103
+ from: DecisionStatusSchema,
15104
+ to: DecisionStatusSchema
15105
+ }).catchall(external_exports.unknown());
14902
15106
  var NodeDeletedEventSchema = external_exports.object({ ...envelope, type: external_exports.literal("node.deleted"), node_id: external_exports.string() }).catchall(external_exports.unknown());
14903
15107
  var EdgeAddedEventSchema = external_exports.object({
14904
15108
  ...envelope,
@@ -14916,6 +15120,21 @@ var ReleaseTaggedEventSchema = external_exports.object({
14916
15120
  notes: external_exports.string().optional(),
14917
15121
  platform: PlatformSchema.optional()
14918
15122
  }).catchall(external_exports.unknown());
15123
+ var DeliverableShippedEventSchema = external_exports.object({
15124
+ ...envelope,
15125
+ type: external_exports.literal("deliverable.shipped"),
15126
+ deliverable_id: external_exports.string(),
15127
+ title: external_exports.string(),
15128
+ summary: external_exports.string().optional(),
15129
+ url: external_exports.string().optional(),
15130
+ node_ids: external_exports.array(external_exports.string()).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." })
15137
+ }).catchall(external_exports.unknown());
14919
15138
  var IdeaProposedEventSchema = external_exports.object({
14920
15139
  ...envelope,
14921
15140
  type: external_exports.literal("idea.proposed"),
@@ -14949,25 +15168,197 @@ var RefStatusChangedEventSchema = external_exports.object({
14949
15168
  to: external_exports.string(),
14950
15169
  synced_at: external_exports.string()
14951
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
+ };
14952
15235
  var KnownJournalEventSchema = external_exports.union([
14953
15236
  NodeCreatedEventSchema,
14954
15237
  NodeUpdatedEventSchema,
14955
15238
  NodeStatusChangedEventSchema,
15239
+ DecisionStatusChangedEventSchema,
14956
15240
  NodeDeletedEventSchema,
14957
15241
  EdgeAddedEventSchema,
14958
15242
  EdgeRemovedEventSchema,
14959
15243
  ReleaseTaggedEventSchema,
15244
+ DeliverableShippedEventSchema,
14960
15245
  IdeaProposedEventSchema,
14961
15246
  RequestFiledEventSchema,
14962
15247
  RefAddedEventSchema,
14963
15248
  RefRemovedEventSchema,
14964
- RefStatusChangedEventSchema
15249
+ RefStatusChangedEventSchema,
15250
+ JournalBaselineEventSchema,
15251
+ QualityAuditCompletedEventSchema,
15252
+ QualityFindingOpenedEventSchema,
15253
+ QualityFindingResolvedEventSchema,
15254
+ QualitySignalTrippedEventSchema
14965
15255
  ]);
14966
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
+
14967
15357
  // ../schema/src/bundle.ts
14968
15358
  var PlatformStatusMapSchema = external_exports.partialRecord(
14969
15359
  PlatformSchema,
14970
- StatusSchema
15360
+ // legacy-tolerant: migrateStatusVocabulary normalizes on load
15361
+ AnyStatusSchema
14971
15362
  ).meta({ id: "PlatformStatusMap", description: "Per-platform status overrides for view nodes." });
14972
15363
  var PlatformNotesMapSchema = external_exports.partialRecord(
14973
15364
  PlatformSchema,
@@ -14990,7 +15381,8 @@ var RefSchema = external_exports.object({
14990
15381
  external_status: external_exports.string().optional().meta({
14991
15382
  description: 'Mirrored external state, verbatim (e.g. "open", "merged", "In Progress").'
14992
15383
  }),
14993
- status_mapped: StatusSchema.optional().meta({
15384
+ // legacy-tolerant: migrateStatusVocabulary normalizes on load
15385
+ status_mapped: AnyStatusSchema.optional().meta({
14994
15386
  description: "Optional mapping of external_status into the arkaik lifecycle. Advisory display data \u2014 never mutates node.status."
14995
15387
  }),
14996
15388
  platform: PlatformSchema.optional().meta({ description: "Optional scoping to one platform variant." }),
@@ -14998,6 +15390,9 @@ var RefSchema = external_exports.object({
14998
15390
  }).meta({ id: "Ref", description: "A typed external reference on a node (docs/spec/bundle-format.md \xA7 References)." });
14999
15391
  var NodeMetadataSchema = external_exports.object({
15000
15392
  stage: external_exports.string().optional(),
15393
+ blocked_by: external_exports.string().optional().meta({
15394
+ description: "Non-empty = blocked at the current status. A node id (rendered as a link) or free text naming the dependency."
15395
+ }),
15001
15396
  playlist: FlowPlaylistSchema.optional(),
15002
15397
  platformNotes: PlatformNotesMapSchema.optional(),
15003
15398
  platformStatuses: PlatformStatusMapSchema.optional(),
@@ -15008,6 +15403,21 @@ var NodeMetadataSchema = external_exports.object({
15008
15403
  }),
15009
15404
  values: external_exports.array(ValueSchema).optional().meta({
15010
15405
  description: "Acceptance nodes only: 1..n Bain value elements served (the Why)."
15406
+ }),
15407
+ product: external_exports.string().optional().meta({
15408
+ description: "Product membership (docs/spec/bundle-format.md \xA7 Products); flow, view, and acceptance only."
15409
+ }),
15410
+ decision_status: DecisionStatusSchema.optional().meta({
15411
+ description: "Decision nodes only: proposed | approved | enacted | rejected | deprecated | superseded. The node's lifecycle status is kept in sync (spec \xA72)."
15412
+ }),
15413
+ context: external_exports.string().optional().meta({
15414
+ description: "Decision nodes only: Context \u2014 the Why (markdown)."
15415
+ }),
15416
+ consequences: external_exports.string().optional().meta({
15417
+ description: "Decision nodes only: Consequences \u2014 the How (markdown)."
15418
+ }),
15419
+ decided_at: external_exports.string().optional().meta({
15420
+ description: "Decision nodes only: ISO 8601 date the decision was made."
15011
15421
  })
15012
15422
  }).catchall(external_exports.unknown()).meta({ id: "NodeMetadata", description: "Optional metadata for a node." });
15013
15423
  var NodeSchema = external_exports.object({
@@ -15016,7 +15426,8 @@ var NodeSchema = external_exports.object({
15016
15426
  species: SpeciesSchema,
15017
15427
  title: external_exports.string().meta({ description: "Human-readable node title." }),
15018
15428
  description: external_exports.string().optional().meta({ description: "Optional description of the node's purpose." }),
15019
- status: StatusSchema,
15429
+ // legacy-tolerant: migrateStatusVocabulary normalizes on load
15430
+ status: AnyStatusSchema,
15020
15431
  platforms: external_exports.array(PlatformSchema).meta({ description: "One or more target platforms." }),
15021
15432
  metadata: NodeMetadataSchema.optional()
15022
15433
  }).meta({ id: "Node" });
@@ -15028,6 +15439,18 @@ var EdgeSchema = external_exports.object({
15028
15439
  edge_type: EdgeTypeSchema,
15029
15440
  metadata: external_exports.record(external_exports.string(), external_exports.unknown()).optional().meta({ description: "Optional edge metadata." })
15030
15441
  }).meta({ id: "Edge" });
15442
+ var MapDisplayOptionsSchema = external_exports.object({
15443
+ images: external_exports.boolean().optional().meta({ description: "Screenshot (or cover) art on view cards." }),
15444
+ flow_platforms: external_exports.string().optional().meta({
15445
+ description: "A flow card's platform delivery: rings (default) | bars."
15446
+ }),
15447
+ view_platforms: external_exports.string().optional().meta({
15448
+ description: "A view card's platform availability: chips (default) | rows."
15449
+ }),
15450
+ minimap_color: external_exports.string().optional().meta({
15451
+ description: "What a minimap node's fill encodes: status (default) | species."
15452
+ })
15453
+ }).catchall(external_exports.unknown()).meta({ id: "MapDisplayOptions", description: "How a map draws its cards (docs/spec/maps.md \xA7 Display Options)." });
15031
15454
  var MapDefinitionSchema = external_exports.object({
15032
15455
  id: external_exports.string().meta({
15033
15456
  description: "Kebab-case, unique within the project; built-in ids (journey, system) are reserved."
@@ -15046,14 +15469,34 @@ var MapDefinitionSchema = external_exports.object({
15046
15469
  root_node_id: external_exports.string().optional().meta({
15047
15470
  description: "Scope anchor: the subgraph is the undirected neighborhood reachable from this node."
15048
15471
  }),
15472
+ product: external_exports.string().optional().meta({ description: "Product scope; absent = every product." }),
15049
15473
  depth: external_exports.number().optional().meta({ description: "Traversal bound from the root; absent = unbounded." }),
15050
- layout: external_exports.object({ direction: external_exports.string().optional() }).catchall(external_exports.unknown()).optional().meta({ description: "Renderer layout hints (e.g. direction: DOWN | RIGHT)." })
15474
+ layout: external_exports.object({ direction: external_exports.string().optional() }).catchall(external_exports.unknown()).optional().meta({ description: "Renderer layout hints (e.g. direction: DOWN | RIGHT)." }),
15475
+ display: MapDisplayOptionsSchema.optional().meta({
15476
+ description: "Card rendering; the human twin is project.metadata.map_display[id]."
15477
+ })
15051
15478
  }).catchall(external_exports.unknown()).meta({ id: "MapDefinition", description: "A stored map definition (docs/spec/maps.md \xA7 MapDefinition)." });
15479
+ var ProductDefinitionSchema = external_exports.object({
15480
+ id: external_exports.string().meta({ description: "Kebab-case, unique within the project." }),
15481
+ title: external_exports.string().meta({ description: "Display title." }),
15482
+ description: external_exports.string().optional().meta({ description: "What this product is." }),
15483
+ platforms: external_exports.array(PlatformSchema).meta({
15484
+ description: "The platforms this product can ship on; empty means availability is not tracked."
15485
+ }),
15486
+ root_node_id: external_exports.string().optional().meta({ description: "This product's journey anchor." })
15487
+ }).catchall(external_exports.unknown()).meta({ id: "ProductDefinition", description: "A product definition (docs/spec/bundle-format.md \xA7 Products)." });
15052
15488
  var ProjectMetadataSchema = external_exports.object({
15053
15489
  view_card_variant: external_exports.enum(["compact", "large"]).optional(),
15054
15490
  maps: external_exports.array(MapDefinitionSchema).optional().meta({
15055
15491
  description: "Stored map definitions (docs/spec/maps.md \xA7 Storage) \u2014 additive; unknown fields preserved."
15056
- })
15492
+ }),
15493
+ map_display: external_exports.record(external_exports.string(), MapDisplayOptionsSchema).optional().meta({
15494
+ description: "Per-map display overrides keyed by map id (docs/spec/maps.md \xA7 Display Options) \u2014 the only path open to the built-in maps."
15495
+ }),
15496
+ products: external_exports.array(ProductDefinitionSchema).optional().meta({
15497
+ description: "Product definitions (docs/spec/bundle-format.md \xA7 Products) \u2014 additive; unknown fields preserved."
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." })
15057
15500
  }).catchall(external_exports.unknown()).meta({ id: "ProjectMetadata", description: "Optional project-level UI settings." });
15058
15501
  var ProjectSchema = external_exports.object({
15059
15502
  id: external_exports.string().meta({ description: "Unique project identifier." }),
@@ -15081,6 +15524,9 @@ var ProjectBundleSchema = external_exports.object({
15081
15524
  edges: external_exports.array(EdgeSchema).meta({ description: "All edges (relationships) between nodes." }),
15082
15525
  journal: external_exports.array(JournalEventSchema).optional().meta({
15083
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."
15084
15530
  })
15085
15531
  }).catchall(external_exports.unknown()).meta({
15086
15532
  id: "ProjectBundle",
@@ -15088,12 +15534,55 @@ var ProjectBundleSchema = external_exports.object({
15088
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)."
15089
15535
  });
15090
15536
 
15537
+ // ../schema/src/products.ts
15538
+ var PRODUCT_MEMBERSHIP_SPECIES = ["flow", "view", "acceptance"];
15539
+ function resolveProducts(project) {
15540
+ const stored = project?.metadata?.products;
15541
+ if (!Array.isArray(stored)) return [];
15542
+ const seen = /* @__PURE__ */ new Set();
15543
+ const products = [];
15544
+ for (const entry of stored) {
15545
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
15546
+ const candidate = entry;
15547
+ if (typeof candidate.id !== "string" || candidate.id.trim() === "" || seen.has(candidate.id)) continue;
15548
+ seen.add(candidate.id);
15549
+ products.push(candidate);
15550
+ }
15551
+ return products;
15552
+ }
15553
+
15091
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"];
15092
15558
  var BUILT_IN_MAP_IDS = ["journey", "system"];
15093
15559
  function isBuiltInMapId(id) {
15094
15560
  return BUILT_IN_MAP_IDS.includes(id);
15095
15561
  }
15096
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
+
15097
15586
  // ../schema/src/validate.ts
15098
15587
  var VALID_STAGES = ["beta", "monitoring", "deprecated"];
15099
15588
  var VALID_VIEW_CARD_VARIANTS = ["compact", "large"];
@@ -15105,25 +15594,6 @@ function estimateDataUriBytes(dataUri) {
15105
15594
  const padding = payload.endsWith("==") ? 2 : payload.endsWith("=") ? 1 : 0;
15106
15595
  return Math.floor(payload.length * 3 / 4) - padding;
15107
15596
  }
15108
- var VALID_EDGE_SEMANTICS = {
15109
- composes: [
15110
- ["flow", "view"],
15111
- ["flow", "flow"],
15112
- ["view", "flow"],
15113
- ["view", "view"]
15114
- ],
15115
- calls: [
15116
- ["view", "api-endpoint"],
15117
- ["flow", "api-endpoint"],
15118
- ["api-endpoint", "api-endpoint"]
15119
- ],
15120
- displays: [["view", "data-model"]],
15121
- queries: [["api-endpoint", "data-model"]],
15122
- covers: [
15123
- ["acceptance", "view"],
15124
- ["acceptance", "flow"]
15125
- ]
15126
- };
15127
15597
  function isIsoDate(value) {
15128
15598
  return typeof value === "string" && !Number.isNaN(Date.parse(value));
15129
15599
  }
@@ -15219,7 +15689,9 @@ function validateBundle(input) {
15219
15689
  }
15220
15690
  const platforms = node.platforms;
15221
15691
  if (!platforms || platforms.length === 0) {
15222
- error51(`${base}.platforms`, "platforms-non-empty", `Node ${nodeId}: platforms array is empty or missing`);
15692
+ if (species !== "decision") {
15693
+ error51(`${base}.platforms`, "platforms-non-empty", `Node ${nodeId}: platforms array is empty or missing`);
15694
+ }
15223
15695
  } else {
15224
15696
  for (const p of platforms) {
15225
15697
  if (!PLATFORM_IDS.includes(p)) {
@@ -15341,6 +15813,25 @@ function validateBundle(input) {
15341
15813
  }
15342
15814
  });
15343
15815
  }
15816
+ const decisionStatus = md.decision_status;
15817
+ if (decisionStatus !== void 0 && species !== "decision") {
15818
+ warn(
15819
+ `${base}.metadata.decision_status`,
15820
+ "decision-status-wrong-species",
15821
+ `decision_status is meaningful on decision nodes only; "${nodeId}" is a ${species}.`
15822
+ );
15823
+ }
15824
+ if (species === "decision") {
15825
+ const effective = decisionStatusOf({ metadata: node.metadata });
15826
+ const expected = lifecycleStatusForDecision(effective);
15827
+ if (node.status !== expected) {
15828
+ warn(
15829
+ `${base}.status`,
15830
+ "decision-lifecycle-mismatch",
15831
+ `Decision "${nodeId}" is ${effective}, whose lifecycle status should be "${expected}", but status is "${node.status}" (spec \xA72).`
15832
+ );
15833
+ }
15834
+ }
15344
15835
  if (species === "flow") {
15345
15836
  const playlist = md.playlist;
15346
15837
  if (!node.metadata || !playlist || !playlist.entries) {
@@ -15358,6 +15849,38 @@ function validateBundle(input) {
15358
15849
  `project.root_node_id "${rootNodeId}" does not reference an existing node`
15359
15850
  );
15360
15851
  }
15852
+ const checkMapDisplay = (display, path, subject) => {
15853
+ if (typeof display !== "object" || display === null || Array.isArray(display)) return;
15854
+ const options = display;
15855
+ const modes = [
15856
+ ["flow_platforms", MAP_FLOW_PLATFORMS_MODES],
15857
+ ["view_platforms", MAP_VIEW_PLATFORMS_MODES],
15858
+ ["minimap_color", MAP_MINIMAP_COLOR_MODES]
15859
+ ];
15860
+ for (const [key, allowed] of modes) {
15861
+ const value = options[key];
15862
+ if (value !== void 0 && (typeof value !== "string" || !allowed.includes(value))) {
15863
+ warn(
15864
+ `${path}.${key}`,
15865
+ "map-unknown-display",
15866
+ `${subject} sets ${key} to "${String(value)}"; expected one of ${allowed.join(", ")} (renderers fall back to the default)`
15867
+ );
15868
+ }
15869
+ }
15870
+ if (options.images !== void 0 && typeof options.images !== "boolean") {
15871
+ warn(
15872
+ `${path}.images`,
15873
+ "map-unknown-display",
15874
+ `${subject} sets images to a non-boolean value (renderers fall back to the default)`
15875
+ );
15876
+ }
15877
+ };
15878
+ const mapDisplayOverrides = projectMetadata?.map_display;
15879
+ if (typeof mapDisplayOverrides === "object" && mapDisplayOverrides !== null && !Array.isArray(mapDisplayOverrides)) {
15880
+ for (const [mapId, display] of Object.entries(mapDisplayOverrides)) {
15881
+ checkMapDisplay(display, `project.metadata.map_display.${mapId}`, `Map "${mapId}"`);
15882
+ }
15883
+ }
15361
15884
  const storedMaps = projectMetadata?.maps;
15362
15885
  if (Array.isArray(storedMaps)) {
15363
15886
  const seenMapIds = /* @__PURE__ */ new Set();
@@ -15405,8 +15928,122 @@ function validateBundle(input) {
15405
15928
  }
15406
15929
  });
15407
15930
  }
15931
+ checkMapDisplay(map2.display, `${path}.display`, `Map "${mapId ?? index}"`);
15932
+ });
15933
+ }
15934
+ const storedProducts = projectMetadata?.products;
15935
+ const declaredProductIds = /* @__PURE__ */ new Set();
15936
+ if (Array.isArray(storedProducts)) {
15937
+ const seenProductIds = /* @__PURE__ */ new Set();
15938
+ storedProducts.forEach((definition, index) => {
15939
+ if (typeof definition !== "object" || definition === null || Array.isArray(definition)) return;
15940
+ const product = definition;
15941
+ const path = `project.metadata.products[${index}]`;
15942
+ const productId = typeof product.id === "string" ? product.id : void 0;
15943
+ if (productId === void 0) return;
15944
+ if (seenProductIds.has(productId)) {
15945
+ warn(`${path}.id`, "product-duplicate-id", `Duplicate product id "${productId}" \u2014 the first wins`);
15946
+ } else {
15947
+ seenProductIds.add(productId);
15948
+ if (productId.trim() !== "") declaredProductIds.add(productId);
15949
+ }
15950
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(productId)) {
15951
+ warn(`${path}.id`, "product-invalid-id", `Product id "${productId}" is not kebab-case`);
15952
+ }
15408
15953
  });
15409
15954
  }
15955
+ const hasProducts = declaredProductIds.size > 0;
15956
+ const anchorsByAcceptance = /* @__PURE__ */ new Map();
15957
+ for (const edge of edges) {
15958
+ if (edge.edge_type !== "covers") continue;
15959
+ const source = typeof edge.source_id === "string" ? edge.source_id : void 0;
15960
+ const target = typeof edge.target_id === "string" ? edge.target_id : void 0;
15961
+ if (source === void 0 || target === void 0) continue;
15962
+ const list = anchorsByAcceptance.get(source) ?? [];
15963
+ list.push(target);
15964
+ anchorsByAcceptance.set(source, list);
15965
+ }
15966
+ const menuByProduct = /* @__PURE__ */ new Map();
15967
+ if (hasProducts) {
15968
+ for (const definition of resolveProducts({ metadata: projectMetadata })) {
15969
+ menuByProduct.set(
15970
+ definition.id,
15971
+ new Set(Array.isArray(definition.platforms) ? definition.platforms : [])
15972
+ );
15973
+ }
15974
+ }
15975
+ const productByNodeId = /* @__PURE__ */ new Map();
15976
+ const indexByNodeId = /* @__PURE__ */ new Map();
15977
+ nodes.forEach((node, index) => {
15978
+ const nodeId = typeof node.id === "string" ? node.id : `#${index}`;
15979
+ const species = node.species;
15980
+ const base = `nodes[${index}]`;
15981
+ indexByNodeId.set(nodeId, index);
15982
+ const metadata = node.metadata ?? {};
15983
+ const membership = typeof metadata.product === "string" ? metadata.product : void 0;
15984
+ const storesMembership = species !== void 0 && PRODUCT_MEMBERSHIP_SPECIES.includes(species);
15985
+ if (membership !== void 0 && !storesMembership) {
15986
+ const detail = SPECIES_IDS.includes(species) ? `${species} membership is derived from consumers and must not be stored` : "metadata.product is only meaningful on flow, view, and acceptance nodes";
15987
+ warn(`${base}.metadata.product`, "product-membership-wrong-species", `Node ${nodeId}: ${detail}`);
15988
+ return;
15989
+ }
15990
+ if (!storesMembership) return;
15991
+ if (membership === void 0) {
15992
+ if (!hasProducts) return;
15993
+ if (species === "acceptance") {
15994
+ if (!anchorsByAcceptance.has(nodeId)) {
15995
+ warn(
15996
+ `${base}.metadata.product`,
15997
+ "acceptance-product-unassigned",
15998
+ `Acceptance ${nodeId} covers nothing and names no product \u2014 it will show only under "All products"`
15999
+ );
16000
+ }
16001
+ } else {
16002
+ warn(
16003
+ `${base}.metadata.product`,
16004
+ "unassigned-membership",
16005
+ `Node ${nodeId}: no product membership \u2014 it will show only under "All products"`
16006
+ );
16007
+ }
16008
+ return;
16009
+ }
16010
+ productByNodeId.set(nodeId, membership);
16011
+ if (!declaredProductIds.has(membership)) {
16012
+ warn(
16013
+ `${base}.metadata.product`,
16014
+ "product-unknown-reference",
16015
+ `Node ${nodeId}: product "${membership}" is not declared on the project`
16016
+ );
16017
+ return;
16018
+ }
16019
+ const menu = menuByProduct.get(membership);
16020
+ const nodePlatforms = Array.isArray(node.platforms) ? node.platforms : [];
16021
+ if (menu) {
16022
+ for (const platform of nodePlatforms) {
16023
+ if (typeof platform === "string" && !menu.has(platform)) {
16024
+ warn(
16025
+ `${base}.platforms`,
16026
+ "product-platform-not-in-menu",
16027
+ `Node ${nodeId}: platform "${platform}" is not in product "${membership}"'s menu`
16028
+ );
16029
+ }
16030
+ }
16031
+ }
16032
+ });
16033
+ if (hasProducts) {
16034
+ for (const [acceptanceId, anchors] of anchorsByAcceptance) {
16035
+ const acceptanceIndex = indexByNodeId.get(acceptanceId);
16036
+ if (acceptanceIndex === void 0) continue;
16037
+ const spanned = new Set(anchors.map((id) => productByNodeId.get(id)).filter((id) => Boolean(id)));
16038
+ if (spanned.size > 1) {
16039
+ warn(
16040
+ `nodes[${acceptanceIndex}].metadata.product`,
16041
+ "acceptance-covers-span-products",
16042
+ `Acceptance ${acceptanceId} covers anchors in ${[...spanned].sort().join(" and ")} \u2014 statuses may conflate products`
16043
+ );
16044
+ }
16045
+ }
16046
+ }
15410
16047
  const edgeIds = /* @__PURE__ */ new Set();
15411
16048
  const edgeSignatures = /* @__PURE__ */ new Set();
15412
16049
  const composesSet = /* @__PURE__ */ new Set();
@@ -15462,13 +16099,98 @@ function validateBundle(input) {
15462
16099
  composesSet.add(`${sourceId}->${targetId}`);
15463
16100
  }
15464
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
+ });
15465
16185
  const collectPlaylistRefs = (entries, flowId, path, depth = 0) => {
15466
16186
  if (depth > 50) {
15467
16187
  error51(path, "playlist-depth", `Flow ${flowId}: playlist nesting too deep (possible cycle)`);
15468
16188
  return [];
15469
16189
  }
15470
16190
  const refs = [];
16191
+ if (!Array.isArray(entries)) return refs;
15471
16192
  for (const entry of entries) {
16193
+ if (typeof entry !== "object" || entry === null) continue;
15472
16194
  if (entry.type === "view") {
15473
16195
  if (!nodeIds.has(entry.view_id)) {
15474
16196
  error51(path, "playlist-ref-exists", `Flow ${flowId}: playlist references non-existent view "${entry.view_id}"`);
@@ -15520,7 +16242,9 @@ function validateBundle(input) {
15520
16242
  const nodeId = node.id;
15521
16243
  const subFlows = [];
15522
16244
  const findSubFlows = (entries) => {
16245
+ if (!Array.isArray(entries)) return;
15523
16246
  for (const e of entries) {
16247
+ if (typeof e !== "object" || e === null) continue;
15524
16248
  if (e.type === "flow") subFlows.push(e.flow_id);
15525
16249
  if (e.type === "condition") {
15526
16250
  if (e.if_true) findSubFlows(e.if_true);
@@ -15555,6 +16279,151 @@ function validateBundle(input) {
15555
16279
  for (const id of flowGraph.keys()) {
15556
16280
  dfs(id);
15557
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
+ }
15558
16427
  for (const finding of crossCheckJournal(bundle)) findings.push(finding);
15559
16428
  return result();
15560
16429
  }
@@ -15562,8 +16431,89 @@ function validateBundle(input) {
15562
16431
  // ../schema/src/emit.ts
15563
16432
  var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
15564
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
+ }
16486
+
16487
+ // src/lib/bundle-io.ts
16488
+ function readBundle(filePath) {
16489
+ if (!existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
16490
+ let parsed;
16491
+ try {
16492
+ parsed = JSON.parse(readFileSync(filePath, "utf8"));
16493
+ } catch (e) {
16494
+ throw new Error(`Cannot parse JSON \u2014 ${e.message}`);
16495
+ }
16496
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
16497
+ throw new Error("Bundle must be a JSON object.");
16498
+ }
16499
+ const record2 = parsed;
16500
+ if (!Array.isArray(record2.nodes)) return record2;
16501
+ return migrateStatusVocabulary(record2);
16502
+ }
16503
+ function nodesByIdOf(bundle) {
16504
+ const nodes = Array.isArray(bundle.nodes) ? bundle.nodes : [];
16505
+ const map2 = /* @__PURE__ */ new Map();
16506
+ for (const n of nodes) {
16507
+ if (n !== null && typeof n === "object" && typeof n.id === "string") {
16508
+ map2.set(n.id, n);
16509
+ }
16510
+ }
16511
+ return map2;
16512
+ }
15565
16513
 
15566
16514
  // src/lib/journal-io.ts
16515
+ import { appendFileSync, existsSync as existsSync2, mkdirSync, readdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
16516
+ import { dirname, join } from "node:path";
15567
16517
  var JOURNAL_SIDECAR = "journal.jsonl";
15568
16518
  function journalPathFor(bundlePath) {
15569
16519
  return join(dirname(bundlePath), JOURNAL_SIDECAR);
@@ -15571,10 +16521,22 @@ function journalPathFor(bundlePath) {
15571
16521
  function archivePathFor(journalPath, version2) {
15572
16522
  return join(dirname(journalPath), "journal", `archive-${version2}.jsonl`);
15573
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
+ }
15574
16529
  function readJournalEvents(journalPath) {
15575
16530
  if (!existsSync2(journalPath)) return [];
15576
16531
  return parseJournalLines(readFileSync2(journalPath, "utf8")).events;
15577
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
+ }
15578
16540
  function loadJournalEvents(bundle, bundlePath) {
15579
16541
  if (Array.isArray(bundle.journal)) return bundle.journal;
15580
16542
  return readJournalEvents(journalPathFor(bundlePath));
@@ -15593,6 +16555,14 @@ function appendJournalEvent(journalPath, event) {
15593
16555
  const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
15594
16556
  appendFileSync(journalPath, prefix + line);
15595
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
+ }
15596
16566
  function compactSlice(journalPath, slice, version2) {
15597
16567
  if (slice.length === 0) return;
15598
16568
  const sliceIds = new Set(slice.map((ev) => ev.id));
@@ -15611,10 +16581,67 @@ function compactSlice(journalPath, slice, version2) {
15611
16581
  writeFileSync(journalPath, surviving.map(toLine).join(""));
15612
16582
  }
15613
16583
 
16584
+ // src/lib/kritik-io.ts
16585
+ import { existsSync as existsSync4 } from "node:fs";
16586
+ import { dirname as dirname3, join as join3 } 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
+ return JSON.parse(readFileSync3(path, "utf8"));
16597
+ }
16598
+ var overlayPath = (root) => join2(root, QUALITY_DIR, OVERLAY_FILE);
16599
+ function loadOverlay(root) {
16600
+ const path = overlayPath(root);
16601
+ return existsSync3(path) ? readJson(path) : null;
16602
+ }
16603
+
16604
+ // src/lib/kritik-io.ts
16605
+ var KRITIK_ACTOR = "arkaik-cli";
16606
+ var DEFAULT_BUNDLE_PATH = join3("docs", "arkaik", "bundle.json");
16607
+ var VENDORED_PACK = join3(QUALITY_DIR, PACK_FILE);
16608
+ var BUNDLED_PACK = join3(dirname3(fileURLToPath(import.meta.url)), "assets", "kritik", "library.json");
16609
+ function resolvePack(root) {
16610
+ const vendored = join3(root, VENDORED_PACK);
16611
+ if (existsSync4(vendored)) return { library: readJson(vendored), path: vendored, vendored: true };
16612
+ if (!existsSync4(BUNDLED_PACK)) {
16613
+ throw new Error(
16614
+ `no criteria pack found. Looked in:
16615
+ ${vendored}
16616
+ ${BUNDLED_PACK}
16617
+ The second is shipped with this CLI, so its absence means a broken install \u2014 reinstall \`arkaik\`.`
16618
+ );
16619
+ }
16620
+ return { library: readJson(BUNDLED_PACK), path: BUNDLED_PACK, vendored: false };
16621
+ }
16622
+ function loadKritikLibrary(root) {
16623
+ const pack = resolvePack(root);
16624
+ return { library: mergeKritikLibrary(pack.library, loadOverlay(root)), pack };
16625
+ }
16626
+ function resolveJournal(root, bundlePath) {
16627
+ const resolved = bundlePath ?? join3(root, DEFAULT_BUNDLE_PATH);
16628
+ return { bundlePath: resolved, journalPath: journalPathFor(resolved), present: existsSync4(resolved) };
16629
+ }
16630
+ function appendQualityEvents(root, inputs, options = {}) {
16631
+ if (inputs.length === 0) return { events: [] };
16632
+ const actor = options.actor ?? KRITIK_ACTOR;
16633
+ const journal = resolveJournal(root, options.bundlePath);
16634
+ if (!journal.present) return { events: [] };
16635
+ const bundle = readBundle(journal.bundlePath);
16636
+ const baseline = ensureJournalBaseline(journal.journalPath, bundle, actor);
16637
+ const events = inputs.map((input) => makeEvent(input.type, input.payload, { actor }));
16638
+ for (const event of events) appendJournalEvent(journal.journalPath, event);
16639
+ return { journalPath: journal.journalPath, events, ...baseline !== void 0 ? { baseline } : {} };
16640
+ }
16641
+
15614
16642
  // src/lib/bundle-validate.ts
15615
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
15616
- import { dirname as dirname2, join as join2 } from "node:path";
15617
- var JOURNAL_SIDECAR2 = "journal.jsonl";
16643
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
16644
+ import { basename } from "node:path";
15618
16645
  function validateBundleAt(filePath) {
15619
16646
  const bundle = readBundle(filePath);
15620
16647
  const loose = bundle;
@@ -15623,31 +16650,69 @@ function validateBundleAt(filePath) {
15623
16650
  }
15624
16651
  let sidecarFindings = [];
15625
16652
  let sidecarLoaded = false;
16653
+ const archiveFindings = [];
16654
+ const archivesLoaded = [];
15626
16655
  if (loose.journal === void 0) {
15627
- const sidecarPath = join2(dirname2(filePath), JOURNAL_SIDECAR2);
15628
- if (existsSync3(sidecarPath)) {
15629
- const { events, findings } = parseJournalLines(readFileSync3(sidecarPath, "utf8"));
16656
+ const sidecarPath = journalPathFor(filePath);
16657
+ const folded = [];
16658
+ if (existsSync5(sidecarPath)) {
16659
+ const { events, findings } = parseJournalLines(readFileSync4(sidecarPath, "utf8"));
15630
16660
  sidecarFindings = findings;
15631
16661
  sidecarLoaded = true;
15632
- loose.journal = events;
16662
+ folded.push(...events);
16663
+ }
16664
+ for (const archivePath of archivePathsFor(sidecarPath)) {
16665
+ const { events, findings } = parseJournalLines(readFileSync4(archivePath, "utf8"));
16666
+ const file2 = basename(archivePath);
16667
+ for (const finding of findings) archiveFindings.push({ ...finding, file: file2 });
16668
+ archivesLoaded.push(file2);
16669
+ folded.push(...events);
15633
16670
  }
16671
+ if (sidecarLoaded || archivesLoaded.length > 0) loose.journal = folded;
15634
16672
  }
15635
16673
  const nodes = Array.isArray(loose.nodes) ? loose.nodes : [];
15636
16674
  const edges = Array.isArray(loose.edges) ? loose.edges : [];
15637
16675
  const journal = Array.isArray(loose.journal) ? loose.journal : [];
15638
16676
  const result = validateBundle(bundle);
15639
- const valid = sidecarFindings.length === 0 && result.errors.length === 0;
15640
- return { bundle, nodes, edges, journal, sidecarLoaded, sidecarFindings, result, valid };
16677
+ const valid = sidecarFindings.length === 0 && archiveFindings.length === 0 && result.errors.length === 0;
16678
+ return {
16679
+ bundle,
16680
+ nodes,
16681
+ edges,
16682
+ journal,
16683
+ sidecarLoaded,
16684
+ sidecarFindings,
16685
+ archivesLoaded,
16686
+ archiveFindings,
16687
+ result,
16688
+ valid
16689
+ };
16690
+ }
16691
+ function journalLineErrorLines(v) {
16692
+ return [
16693
+ ...v.sidecarFindings.map((f) => `ERROR [${f.rule}] line ${f.line}: ${f.message}`),
16694
+ ...v.archiveFindings.map((f) => `ERROR [${f.rule}] ${f.file} line ${f.line}: ${f.message}`)
16695
+ ];
15641
16696
  }
15642
16697
  export {
15643
16698
  JOURNAL_SIDECAR,
16699
+ KRITIK_ACTOR,
16700
+ VENDORED_PACK,
15644
16701
  appendJournalEvent,
16702
+ appendQualityEvents,
15645
16703
  archivePathFor,
16704
+ archivePathsFor,
15646
16705
  compactSlice,
16706
+ ensureJournalBaseline,
16707
+ journalLineErrorLines,
15647
16708
  journalPathFor,
15648
16709
  loadJournalEvents,
16710
+ loadKritikLibrary,
15649
16711
  nodesByIdOf,
15650
16712
  readBundle,
16713
+ readFullJournalEvents,
15651
16714
  readJournalEvents,
16715
+ resolveJournal,
16716
+ resolvePack,
15652
16717
  validateBundleAt
15653
16718
  };