@neuroverseos/governance 0.1.0 → 0.1.2

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.
Files changed (94) hide show
  1. package/LICENSE.md +190 -0
  2. package/README.md +472 -115
  3. package/dist/adapters/express.cjs +874 -0
  4. package/dist/adapters/express.d.cts +66 -0
  5. package/dist/adapters/express.d.ts +66 -0
  6. package/dist/adapters/express.js +10 -0
  7. package/dist/adapters/index.cjs +1166 -0
  8. package/dist/adapters/index.d.cts +5 -0
  9. package/dist/adapters/index.d.ts +5 -0
  10. package/dist/adapters/index.js +40 -0
  11. package/dist/adapters/langchain.cjs +861 -0
  12. package/dist/adapters/langchain.d.cts +82 -0
  13. package/dist/adapters/langchain.d.ts +82 -0
  14. package/dist/adapters/langchain.js +14 -0
  15. package/dist/adapters/openai.cjs +891 -0
  16. package/dist/adapters/openai.d.cts +92 -0
  17. package/dist/adapters/openai.d.ts +92 -0
  18. package/dist/adapters/openai.js +14 -0
  19. package/dist/adapters/openclaw.cjs +883 -0
  20. package/dist/adapters/openclaw.d.cts +92 -0
  21. package/dist/adapters/openclaw.d.ts +92 -0
  22. package/dist/adapters/openclaw.js +14 -0
  23. package/dist/bootstrap-H4HHKQ5G.js +113 -0
  24. package/dist/build-73KAVHEY.js +308 -0
  25. package/dist/chunk-4NGDRRQH.js +10 -0
  26. package/dist/chunk-B4NF3OLW.js +673 -0
  27. package/dist/chunk-BUWWN2NX.js +192 -0
  28. package/dist/chunk-CROPZ75A.js +107 -0
  29. package/dist/chunk-D7BGWV2J.js +620 -0
  30. package/dist/chunk-EIUHJXBB.js +340 -0
  31. package/dist/chunk-EQXFOKH2.js +85 -0
  32. package/dist/chunk-FYPYZFV5.js +187 -0
  33. package/dist/chunk-FYS2CBUW.js +304 -0
  34. package/dist/chunk-FZQCRGUU.js +115 -0
  35. package/dist/chunk-I3RRAYK2.js +11 -0
  36. package/dist/chunk-ITJ3LCPG.js +225 -0
  37. package/dist/chunk-M3TZFGHO.js +79 -0
  38. package/dist/chunk-MWDQ4MJB.js +11 -0
  39. package/dist/chunk-O5OMJMIE.js +590 -0
  40. package/dist/chunk-T4X42QXC.js +121 -0
  41. package/dist/chunk-XPDMYECO.js +642 -0
  42. package/dist/chunk-Z2S2HIV5.js +100 -0
  43. package/dist/cli/neuroverse.cjs +5864 -0
  44. package/dist/cli/neuroverse.d.cts +1 -0
  45. package/dist/cli/neuroverse.d.ts +1 -0
  46. package/dist/cli/neuroverse.js +128 -0
  47. package/dist/configure-ai-46JVG56I.js +129 -0
  48. package/dist/derive-6NAEWLM5.js +150 -0
  49. package/dist/explain-3B3VB6TL.js +69 -0
  50. package/dist/guard-67Y66P3I.js +82 -0
  51. package/dist/guard-contract-D_RQz9kt.d.cts +529 -0
  52. package/dist/guard-contract-D_RQz9kt.d.ts +529 -0
  53. package/dist/impact-CHERK3O6.js +57 -0
  54. package/dist/improve-YG6I6ERG.js +84 -0
  55. package/dist/index.cjs +4227 -0
  56. package/dist/index.d.cts +1147 -0
  57. package/dist/index.d.ts +1139 -25
  58. package/dist/index.js +318 -1159
  59. package/dist/init-Z66T6TDI.js +142 -0
  60. package/dist/simulate-ETHHINZ4.js +107 -0
  61. package/dist/trace-3YODSSIP.js +164 -0
  62. package/dist/validate-UVE6GKQU.js +72 -0
  63. package/dist/validate-engine-657D75OG.js +6 -0
  64. package/dist/world-WLNHL5XC.js +298 -0
  65. package/dist/worlds/derivation-world.nv-world.md +278 -0
  66. package/package.json +74 -26
  67. package/LICENSE +0 -21
  68. package/dist/audit-logger.d.ts +0 -38
  69. package/dist/audit-logger.d.ts.map +0 -1
  70. package/dist/audit-logger.js +0 -100
  71. package/dist/audit-logger.js.map +0 -1
  72. package/dist/condition-engine.d.ts +0 -16
  73. package/dist/condition-engine.d.ts.map +0 -1
  74. package/dist/condition-engine.js +0 -186
  75. package/dist/condition-engine.js.map +0 -1
  76. package/dist/drift-monitor.d.ts +0 -81
  77. package/dist/drift-monitor.d.ts.map +0 -1
  78. package/dist/drift-monitor.js +0 -228
  79. package/dist/drift-monitor.js.map +0 -1
  80. package/dist/governance-engine.d.ts +0 -131
  81. package/dist/governance-engine.d.ts.map +0 -1
  82. package/dist/governance-engine.js +0 -660
  83. package/dist/governance-engine.js.map +0 -1
  84. package/dist/index.d.ts.map +0 -1
  85. package/dist/index.js.map +0 -1
  86. package/dist/types.d.ts +0 -196
  87. package/dist/types.d.ts.map +0 -1
  88. package/dist/types.js +0 -9
  89. package/dist/types.js.map +0 -1
  90. package/dist/world-bootstrap.d.ts +0 -31
  91. package/dist/world-bootstrap.d.ts.map +0 -1
  92. package/dist/world-bootstrap.js +0 -415
  93. package/dist/world-bootstrap.js.map +0 -1
  94. package/openclaw.plugin.json +0 -59
@@ -0,0 +1,79 @@
1
+ // src/loader/world-loader.ts
2
+ async function loadWorldFromDirectory(dirPath) {
3
+ const { readFile } = await import("fs/promises");
4
+ const { join } = await import("path");
5
+ const { readdirSync } = await import("fs");
6
+ async function readJson(filename) {
7
+ try {
8
+ const content = await readFile(join(dirPath, filename), "utf-8");
9
+ return JSON.parse(content);
10
+ } catch {
11
+ return void 0;
12
+ }
13
+ }
14
+ const worldJson = await readJson("world.json");
15
+ if (!worldJson) {
16
+ throw new Error(`Cannot read world.json in ${dirPath}`);
17
+ }
18
+ const invariantsJson = await readJson("invariants.json");
19
+ const assumptionsJson = await readJson("assumptions.json");
20
+ const stateSchemaJson = await readJson("state-schema.json");
21
+ const gatesJson = await readJson("gates.json");
22
+ const outcomesJson = await readJson("outcomes.json");
23
+ const guardsJson = await readJson("guards.json");
24
+ const rolesJson = await readJson("roles.json");
25
+ const kernelJson = await readJson("kernel.json");
26
+ const metadataJson = await readJson("metadata.json");
27
+ const rules = [];
28
+ try {
29
+ const rulesDir = join(dirPath, "rules");
30
+ const ruleFiles = readdirSync(rulesDir).filter((f) => f.endsWith(".json")).sort();
31
+ for (const file of ruleFiles) {
32
+ const content = await readFile(join(rulesDir, file), "utf-8");
33
+ rules.push(JSON.parse(content));
34
+ }
35
+ } catch {
36
+ }
37
+ return {
38
+ world: worldJson,
39
+ invariants: invariantsJson?.invariants ?? [],
40
+ assumptions: assumptionsJson ?? { profiles: {}, parameter_definitions: {} },
41
+ stateSchema: stateSchemaJson ?? { variables: {}, presets: {} },
42
+ rules,
43
+ gates: gatesJson ?? {
44
+ viability_classification: [],
45
+ structural_override: { description: "", enforcement: "mandatory" },
46
+ sustainability_threshold: 0,
47
+ collapse_visual: { background: "", text: "", border: "", label: "" }
48
+ },
49
+ outcomes: outcomesJson ?? {
50
+ computed_outcomes: [],
51
+ comparison_layout: { primary_card: "", status_badge: "", structural_indicators: [] }
52
+ },
53
+ guards: guardsJson,
54
+ roles: rolesJson,
55
+ kernel: kernelJson,
56
+ metadata: metadataJson ?? {
57
+ format_version: "1.0.0",
58
+ created_at: "",
59
+ last_modified: "",
60
+ authoring_method: "manual-authoring"
61
+ }
62
+ };
63
+ }
64
+ async function loadWorld(worldPath) {
65
+ const { stat } = await import("fs/promises");
66
+ const info = await stat(worldPath);
67
+ if (info.isDirectory()) {
68
+ return loadWorldFromDirectory(worldPath);
69
+ }
70
+ if (worldPath.endsWith(".nv-world.zip")) {
71
+ throw new Error(".nv-world.zip loading not yet implemented \u2014 use a world directory");
72
+ }
73
+ throw new Error(`Cannot load world from: ${worldPath} \u2014 expected a directory or .nv-world.zip`);
74
+ }
75
+
76
+ export {
77
+ loadWorldFromDirectory,
78
+ loadWorld
79
+ };
@@ -0,0 +1,11 @@
1
+ // src/contracts/guard-contract.ts
2
+ var GUARD_EXIT_CODES = {
3
+ ALLOW: 0,
4
+ BLOCK: 1,
5
+ PAUSE: 2,
6
+ ERROR: 3
7
+ };
8
+
9
+ export {
10
+ GUARD_EXIT_CODES
11
+ };
@@ -0,0 +1,590 @@
1
+ // src/engine/validate-engine.ts
2
+ function validateWorld(world) {
3
+ const startTime = performance.now();
4
+ const findings = [];
5
+ checkCompleteness(world, findings);
6
+ checkReferentialIntegrity(world, findings);
7
+ checkGuardCoverage(world, findings);
8
+ checkContradictions(world, findings);
9
+ checkOrphans(world, findings);
10
+ checkSchemaViolations(world, findings);
11
+ const severityOrder = { error: 0, warning: 1, info: 2 };
12
+ findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
13
+ const errors = findings.filter((f) => f.severity === "error").length;
14
+ const warnings = findings.filter((f) => f.severity === "warning").length;
15
+ const info = findings.filter((f) => f.severity === "info").length;
16
+ const completenessScore = computeCompletenessScore(world);
17
+ const invariantCoverage = computeInvariantCoverage(world);
18
+ const summary = {
19
+ errors,
20
+ warnings,
21
+ info,
22
+ completenessScore,
23
+ invariantCoverage,
24
+ canRun: errors === 0,
25
+ isHealthy: errors === 0 && warnings === 0
26
+ };
27
+ return {
28
+ worldId: world.world.world_id,
29
+ worldName: world.world.name,
30
+ worldVersion: world.world.version,
31
+ validatedAt: Date.now(),
32
+ durationMs: performance.now() - startTime,
33
+ summary,
34
+ findings
35
+ };
36
+ }
37
+ function checkCompleteness(world, findings) {
38
+ if (!world.world?.world_id) {
39
+ findings.push(finding("missing-world-id", "World identity is missing world_id", "error", "completeness", ["world.json"]));
40
+ }
41
+ if (!world.world?.name) {
42
+ findings.push(finding("missing-world-name", "World identity is missing name", "error", "completeness", ["world.json"]));
43
+ }
44
+ if (!world.world?.thesis) {
45
+ findings.push(finding("missing-thesis", "World has no thesis \u2014 there is nothing to simulate", "error", "completeness", ["world.json"]));
46
+ }
47
+ if (!world.invariants || world.invariants.length === 0) {
48
+ findings.push(finding("missing-invariants", "No invariants declared \u2014 world has no structural constraints", "error", "completeness", ["invariants.json"]));
49
+ }
50
+ if (!world.stateSchema?.variables || Object.keys(world.stateSchema.variables).length === 0) {
51
+ findings.push(finding("missing-state-schema", "No state variables declared \u2014 nothing to simulate", "error", "completeness", ["state-schema.json"]));
52
+ }
53
+ if (!world.rules || world.rules.length === 0) {
54
+ findings.push(finding("missing-rules", "No rules declared \u2014 world has no causal mechanics", "error", "completeness", ["rules/"]));
55
+ }
56
+ if (!world.gates?.viability_classification || world.gates.viability_classification.length === 0) {
57
+ findings.push(finding("missing-gates", "No viability gates declared \u2014 cannot classify outcomes", "error", "completeness", ["gates.json"]));
58
+ }
59
+ if (!world.outcomes?.computed_outcomes || world.outcomes.computed_outcomes.length === 0) {
60
+ findings.push(finding("missing-outcomes", "No computed outcomes declared \u2014 nothing to display", "warning", "completeness", ["outcomes.json"]));
61
+ }
62
+ if (!world.assumptions?.profiles || Object.keys(world.assumptions.profiles).length === 0) {
63
+ findings.push(finding("missing-assumptions", "No assumption profiles declared \u2014 cannot compare scenarios", "warning", "completeness", ["assumptions.json"]));
64
+ }
65
+ if (!world.metadata) {
66
+ findings.push(finding("missing-metadata", "No metadata block \u2014 world has no version history", "warning", "completeness", ["metadata.json"]));
67
+ }
68
+ if (!world.guards) {
69
+ findings.push(finding("no-guards", "No guards declared \u2014 Action Space enforcement unavailable", "info", "completeness", ["guards.json"]));
70
+ }
71
+ if (!world.roles) {
72
+ findings.push(finding("no-roles", "No roles declared \u2014 multi-agent governance unavailable", "info", "completeness", ["roles.json"]));
73
+ }
74
+ if (!world.kernel) {
75
+ findings.push(finding("no-kernel", "No kernel config \u2014 Thinking Space governance unavailable", "info", "completeness", ["kernel.json"]));
76
+ }
77
+ }
78
+ function checkReferentialIntegrity(world, findings) {
79
+ if (!world.rules || !world.stateSchema?.variables) return;
80
+ const declaredVars = new Set(Object.keys(world.stateSchema.variables));
81
+ const declaredOutcomes = new Set(
82
+ (world.outcomes?.computed_outcomes ?? []).map((o) => o.id)
83
+ );
84
+ const declaredAssumptionParams = new Set(
85
+ Object.keys(world.assumptions?.parameter_definitions ?? {})
86
+ );
87
+ const allDeclared = /* @__PURE__ */ new Set([...declaredVars, ...declaredOutcomes]);
88
+ for (const rule of world.rules) {
89
+ for (const trigger of rule.triggers) {
90
+ if (trigger.source === "state" && !allDeclared.has(trigger.field)) {
91
+ findings.push(finding(
92
+ `undeclared-trigger-${rule.id}-${trigger.field}`,
93
+ `Rule "${rule.id}" trigger references undeclared state variable "${trigger.field}"`,
94
+ "error",
95
+ "referential-integrity",
96
+ ["rules/", "state-schema.json"],
97
+ rule.id,
98
+ `Add "${trigger.field}" to state-schema.json variables`
99
+ ));
100
+ }
101
+ if (trigger.source === "assumption" && !declaredAssumptionParams.has(trigger.field)) {
102
+ findings.push(finding(
103
+ `undeclared-assumption-trigger-${rule.id}-${trigger.field}`,
104
+ `Rule "${rule.id}" trigger references undeclared assumption parameter "${trigger.field}"`,
105
+ "error",
106
+ "referential-integrity",
107
+ ["rules/", "assumptions.json"],
108
+ rule.id,
109
+ `Add "${trigger.field}" to assumptions.json parameter_definitions`
110
+ ));
111
+ }
112
+ }
113
+ for (const effect of rule.effects ?? []) {
114
+ if (!allDeclared.has(effect.target)) {
115
+ findings.push(finding(
116
+ `undeclared-effect-${rule.id}-${effect.target}`,
117
+ `Rule "${rule.id}" effect targets undeclared variable "${effect.target}"`,
118
+ "error",
119
+ "referential-integrity",
120
+ ["rules/", "state-schema.json", "outcomes.json"],
121
+ rule.id,
122
+ `Add "${effect.target}" to state-schema.json or outcomes.json`
123
+ ));
124
+ }
125
+ }
126
+ if (rule.exclusive_with) {
127
+ const refExists = world.rules.some((r) => r.id === rule.exclusive_with);
128
+ if (!refExists) {
129
+ findings.push(finding(
130
+ `broken-exclusive-${rule.id}`,
131
+ `Rule "${rule.id}" has exclusive_with="${rule.exclusive_with}" but that rule does not exist`,
132
+ "error",
133
+ "referential-integrity",
134
+ ["rules/"],
135
+ rule.id
136
+ ));
137
+ }
138
+ }
139
+ if (rule.collapse_check && !allDeclared.has(rule.collapse_check.field)) {
140
+ findings.push(finding(
141
+ `undeclared-collapse-${rule.id}`,
142
+ `Rule "${rule.id}" collapse_check references undeclared field "${rule.collapse_check.field}"`,
143
+ "error",
144
+ "referential-integrity",
145
+ ["rules/", "state-schema.json"],
146
+ rule.id
147
+ ));
148
+ }
149
+ }
150
+ for (const gate of world.gates?.viability_classification ?? []) {
151
+ if (!allDeclared.has(gate.field)) {
152
+ findings.push(finding(
153
+ `undeclared-gate-field-${gate.status}`,
154
+ `Gate "${gate.status}" references undeclared field "${gate.field}"`,
155
+ "error",
156
+ "referential-integrity",
157
+ ["gates.json", "state-schema.json", "outcomes.json"]
158
+ ));
159
+ }
160
+ }
161
+ }
162
+ function checkGuardCoverage(world, findings) {
163
+ if (!world.invariants || world.invariants.length === 0) return;
164
+ const guards = world.guards?.guards ?? [];
165
+ for (const invariant of world.invariants) {
166
+ if (invariant.enforcement === "prompt") continue;
167
+ const coveringGuard = guards.find(
168
+ (g) => g.invariant_ref === invariant.id && g.immutable
169
+ );
170
+ if (!coveringGuard) {
171
+ findings.push(finding(
172
+ `uncovered-invariant-${invariant.id}`,
173
+ `Invariant "${invariant.id}" has no backing structural guard \u2014 it is declared but not enforced at runtime`,
174
+ "warning",
175
+ "guard-coverage",
176
+ ["invariants.json", "guards.json"],
177
+ invariant.id,
178
+ `Add a structural guard with invariant_ref="${invariant.id}" to guards.json`
179
+ ));
180
+ }
181
+ }
182
+ for (const guard of guards) {
183
+ if (guard.invariant_ref) {
184
+ const invariantExists = world.invariants.some((i) => i.id === guard.invariant_ref);
185
+ if (!invariantExists) {
186
+ findings.push(finding(
187
+ `broken-guard-invariant-ref-${guard.id}`,
188
+ `Guard "${guard.id}" references invariant "${guard.invariant_ref}" which does not exist`,
189
+ "error",
190
+ "referential-integrity",
191
+ ["guards.json", "invariants.json"],
192
+ guard.id
193
+ ));
194
+ }
195
+ }
196
+ }
197
+ if (world.guards) {
198
+ const vocabKeys = new Set(Object.keys(world.guards.intent_vocabulary));
199
+ for (const guard of guards) {
200
+ for (const patternKey of guard.intent_patterns) {
201
+ if (!vocabKeys.has(patternKey)) {
202
+ findings.push(finding(
203
+ `broken-guard-pattern-${guard.id}-${patternKey}`,
204
+ `Guard "${guard.id}" references intent pattern "${patternKey}" which is not in intent_vocabulary`,
205
+ "error",
206
+ "referential-integrity",
207
+ ["guards.json"],
208
+ guard.id
209
+ ));
210
+ }
211
+ }
212
+ }
213
+ }
214
+ }
215
+ function checkContradictions(world, findings) {
216
+ if (!world.rules || world.rules.length < 2) return;
217
+ checkCircularExclusiveWith(world.rules, findings);
218
+ const tensions = detectSemanticTensions(world.rules);
219
+ for (let i = 0; i < tensions.length; i++) {
220
+ const tension = tensions[i];
221
+ findings.push(finding(
222
+ `semantic-tension-${i}`,
223
+ tension.message,
224
+ tension.severity,
225
+ "semantic-tension",
226
+ tension.affectedBlocks
227
+ ));
228
+ }
229
+ }
230
+ function checkCircularExclusiveWith(rules, findings) {
231
+ const exclusiveMap = /* @__PURE__ */ new Map();
232
+ for (const rule of rules) {
233
+ if (rule.exclusive_with) {
234
+ exclusiveMap.set(rule.id, rule.exclusive_with);
235
+ }
236
+ }
237
+ const reportedPairs = /* @__PURE__ */ new Set();
238
+ for (const [ruleA, ruleB] of exclusiveMap) {
239
+ if (exclusiveMap.get(ruleB) === ruleA) {
240
+ const pairKey = [ruleA, ruleB].sort().join("::");
241
+ if (!reportedPairs.has(pairKey)) {
242
+ reportedPairs.add(pairKey);
243
+ findings.push(finding(
244
+ `mutual-exclusion-${pairKey.replace("::", "-")}`,
245
+ `Rules "${ruleA}" and "${ruleB}" are mutually exclusive \u2014 only one can fire per evaluation`,
246
+ "info",
247
+ "semantic-tension",
248
+ ["rules/"],
249
+ `${ruleA}, ${ruleB}`
250
+ ));
251
+ }
252
+ }
253
+ }
254
+ const reportedChains = /* @__PURE__ */ new Set();
255
+ for (const startRule of exclusiveMap.keys()) {
256
+ let current = startRule;
257
+ const visited = /* @__PURE__ */ new Set();
258
+ const chain = [];
259
+ while (current && !visited.has(current)) {
260
+ visited.add(current);
261
+ chain.push(current);
262
+ current = exclusiveMap.get(current);
263
+ }
264
+ if (current === startRule && visited.size > 2) {
265
+ const chainKey = [...chain].sort().join("::");
266
+ if (!reportedChains.has(chainKey)) {
267
+ reportedChains.add(chainKey);
268
+ findings.push(finding(
269
+ `circular-exclusive-chain-${startRule}`,
270
+ `Circular exclusive_with chain: ${chain.join(" \u2192 ")} \u2192 ${startRule} (${chain.length} rules)`,
271
+ "warning",
272
+ "contradiction",
273
+ ["rules/"],
274
+ chain.join(", ")
275
+ ));
276
+ }
277
+ }
278
+ }
279
+ }
280
+ function detectSemanticTensions(rules) {
281
+ const results = [];
282
+ if (rules.length < 2) return results;
283
+ const exclusivePairs = /* @__PURE__ */ new Set();
284
+ for (const rule of rules) {
285
+ if (rule.exclusive_with) {
286
+ exclusivePairs.add(`${rule.id}:${rule.exclusive_with}`);
287
+ exclusivePairs.add(`${rule.exclusive_with}:${rule.id}`);
288
+ }
289
+ }
290
+ const isExclusive = (a, b) => exclusivePairs.has(`${a}:${b}`);
291
+ const targetEffects = /* @__PURE__ */ new Map();
292
+ for (const rule of rules) {
293
+ const allEffects = [
294
+ ...rule.effects || [],
295
+ ...(rule.effects_conditional || []).flatMap((c) => c.effects)
296
+ ];
297
+ for (const effect of allEffects) {
298
+ const direction = classifyEffectDirection(effect);
299
+ if (direction === "neutral") continue;
300
+ const entries = targetEffects.get(effect.target) ?? [];
301
+ entries.push({ ruleId: rule.id, ruleLabel: rule.label, direction, effect });
302
+ targetEffects.set(effect.target, entries);
303
+ }
304
+ }
305
+ for (const [target, effects] of targetEffects) {
306
+ const increasing = effects.filter((e) => e.direction === "increase");
307
+ const decreasing = effects.filter((e) => e.direction === "decrease");
308
+ if (increasing.length === 0 || decreasing.length === 0) continue;
309
+ const incRuleIds = /* @__PURE__ */ new Set();
310
+ const decRuleIds = /* @__PURE__ */ new Set();
311
+ for (const inc of increasing) {
312
+ for (const dec of decreasing) {
313
+ if (inc.ruleId === dec.ruleId) continue;
314
+ if (isExclusive(inc.ruleId, dec.ruleId)) continue;
315
+ incRuleIds.add(inc.ruleId);
316
+ decRuleIds.add(dec.ruleId);
317
+ }
318
+ }
319
+ if (incRuleIds.size === 0 || decRuleIds.size === 0) continue;
320
+ const incLabels = [...incRuleIds].map((id) => {
321
+ const e = increasing.find((e2) => e2.ruleId === id);
322
+ return `"${e.ruleLabel}" (${describeEffect(e.effect)})`;
323
+ });
324
+ const decLabels = [...decRuleIds].map((id) => {
325
+ const e = decreasing.find((e2) => e2.ruleId === id);
326
+ return `"${e.ruleLabel}" (${describeEffect(e.effect)})`;
327
+ });
328
+ results.push({
329
+ type: "semantic_tension",
330
+ severity: "warning",
331
+ message: `Opposing effects on "${target}": ${incLabels.join(", ")} increase it while ${decLabels.join(", ")} decrease it`,
332
+ affectedBlocks: ["rules"]
333
+ });
334
+ }
335
+ const setEffects = /* @__PURE__ */ new Map();
336
+ for (const rule of rules) {
337
+ const allEffects = [
338
+ ...rule.effects || [],
339
+ ...(rule.effects_conditional || []).flatMap((c) => c.effects)
340
+ ];
341
+ for (const effect of allEffects) {
342
+ if (effect.operation !== "set" && effect.operation !== "set_boolean" && effect.operation !== "set_dynamic") continue;
343
+ const entries = setEffects.get(effect.target) ?? [];
344
+ entries.push({ ruleId: rule.id, ruleLabel: rule.label, value: effect.value });
345
+ setEffects.set(effect.target, entries);
346
+ }
347
+ }
348
+ for (const [target, effects] of setEffects) {
349
+ if (effects.length < 2) continue;
350
+ const byValue = /* @__PURE__ */ new Map();
351
+ for (const e of effects) {
352
+ const key = String(e.value);
353
+ const existing = byValue.get(key) ?? [];
354
+ existing.push(e);
355
+ byValue.set(key, existing);
356
+ }
357
+ if (byValue.size < 2) continue;
358
+ const valueGroups = [...byValue.entries()];
359
+ const involvedRules = [];
360
+ for (let i = 0; i < valueGroups.length; i++) {
361
+ for (let j = i + 1; j < valueGroups.length; j++) {
362
+ for (const a of valueGroups[i][1]) {
363
+ for (const b of valueGroups[j][1]) {
364
+ if (a.ruleId === b.ruleId) continue;
365
+ if (isExclusive(a.ruleId, b.ruleId)) continue;
366
+ if (!involvedRules.includes(a.ruleId)) involvedRules.push(a.ruleId);
367
+ if (!involvedRules.includes(b.ruleId)) involvedRules.push(b.ruleId);
368
+ }
369
+ }
370
+ }
371
+ }
372
+ if (involvedRules.length === 0) continue;
373
+ const descriptions = involvedRules.map((id) => {
374
+ const e = effects.find((e2) => e2.ruleId === id);
375
+ return `"${e.ruleLabel}" sets to ${e.value}`;
376
+ });
377
+ results.push({
378
+ type: "semantic_tension",
379
+ severity: "warning",
380
+ message: `Conflicting set operations on "${target}": ${descriptions.join(", ")}`,
381
+ affectedBlocks: ["rules"]
382
+ });
383
+ }
384
+ return results;
385
+ }
386
+ function classifyEffectDirection(effect) {
387
+ if (effect.operation === "multiply" || effect.operation === "multiply_dynamic") {
388
+ const val = typeof effect.value === "number" ? effect.value : 1;
389
+ return val > 1 ? "increase" : val < 1 ? "decrease" : "neutral";
390
+ }
391
+ if (effect.operation === "add" || effect.operation === "add_dynamic") {
392
+ const val = typeof effect.value === "number" ? effect.value : 0;
393
+ return val > 0 ? "increase" : val < 0 ? "decrease" : "neutral";
394
+ }
395
+ if (effect.operation === "subtract" || effect.operation === "subtract_dynamic") {
396
+ const val = typeof effect.value === "number" ? effect.value : 0;
397
+ return val > 0 ? "decrease" : val < 0 ? "increase" : "neutral";
398
+ }
399
+ return "neutral";
400
+ }
401
+ function describeEffect(effect) {
402
+ switch (effect.operation) {
403
+ case "multiply":
404
+ case "multiply_dynamic":
405
+ return `multiplies by ${effect.value}`;
406
+ case "add":
407
+ case "add_dynamic":
408
+ return `adds ${effect.value}`;
409
+ case "subtract":
410
+ case "subtract_dynamic":
411
+ return `subtracts ${effect.value}`;
412
+ case "set":
413
+ case "set_dynamic":
414
+ case "set_boolean":
415
+ return `sets to ${effect.value}`;
416
+ default:
417
+ return `${effect.operation} ${effect.value}`;
418
+ }
419
+ }
420
+ function checkOrphans(world, findings) {
421
+ if (!world.stateSchema?.variables || !world.rules) return;
422
+ const referencedVars = /* @__PURE__ */ new Set();
423
+ for (const rule of world.rules) {
424
+ for (const trigger of rule.triggers) {
425
+ referencedVars.add(trigger.field);
426
+ }
427
+ for (const effect of rule.effects ?? []) {
428
+ referencedVars.add(effect.target);
429
+ }
430
+ if (rule.collapse_check) {
431
+ referencedVars.add(rule.collapse_check.field);
432
+ }
433
+ }
434
+ for (const gate of world.gates?.viability_classification ?? []) {
435
+ referencedVars.add(gate.field);
436
+ }
437
+ for (const varId of Object.keys(world.stateSchema.variables)) {
438
+ if (!referencedVars.has(varId)) {
439
+ findings.push(finding(
440
+ `orphan-variable-${varId}`,
441
+ `State variable "${varId}" is declared but never referenced by any rule or gate`,
442
+ "warning",
443
+ "orphan",
444
+ ["state-schema.json"],
445
+ varId,
446
+ "Remove this variable or add rules that reference it"
447
+ ));
448
+ }
449
+ }
450
+ const effectTargets = /* @__PURE__ */ new Set();
451
+ for (const rule of world.rules) {
452
+ for (const effect of rule.effects ?? []) {
453
+ effectTargets.add(effect.target);
454
+ }
455
+ }
456
+ for (const outcome of world.outcomes?.computed_outcomes ?? []) {
457
+ if (outcome.assignment === "external") continue;
458
+ if (!effectTargets.has(outcome.id) && !outcome.derived_from) {
459
+ findings.push(finding(
460
+ `orphan-outcome-${outcome.id}`,
461
+ `Outcome "${outcome.id}" is declared but no rule produces it`,
462
+ "warning",
463
+ "orphan",
464
+ ["outcomes.json", "rules/"],
465
+ outcome.id
466
+ ));
467
+ }
468
+ }
469
+ }
470
+ function checkSchemaViolations(world, findings) {
471
+ if (!world.stateSchema?.variables) return;
472
+ for (const [varId, variable] of Object.entries(world.stateSchema.variables)) {
473
+ if (variable.type === "number") {
474
+ const def = variable.default;
475
+ if (variable.min !== void 0 && def < variable.min) {
476
+ findings.push(finding(
477
+ `default-below-min-${varId}`,
478
+ `Variable "${varId}" default (${def}) is below declared min (${variable.min})`,
479
+ "error",
480
+ "schema-violation",
481
+ ["state-schema.json"],
482
+ varId
483
+ ));
484
+ }
485
+ if (variable.max !== void 0 && def > variable.max) {
486
+ findings.push(finding(
487
+ `default-above-max-${varId}`,
488
+ `Variable "${varId}" default (${def}) is above declared max (${variable.max})`,
489
+ "error",
490
+ "schema-violation",
491
+ ["state-schema.json"],
492
+ varId
493
+ ));
494
+ }
495
+ }
496
+ if (variable.type === "enum") {
497
+ if (!variable.options || variable.options.length === 0) {
498
+ findings.push(finding(
499
+ `enum-no-options-${varId}`,
500
+ `Enum variable "${varId}" has no options declared`,
501
+ "error",
502
+ "schema-violation",
503
+ ["state-schema.json"],
504
+ varId
505
+ ));
506
+ } else if (!variable.options.includes(variable.default)) {
507
+ findings.push(finding(
508
+ `enum-default-invalid-${varId}`,
509
+ `Enum variable "${varId}" default "${variable.default}" is not in declared options`,
510
+ "error",
511
+ "schema-violation",
512
+ ["state-schema.json"],
513
+ varId
514
+ ));
515
+ }
516
+ }
517
+ }
518
+ for (const [presetName, preset] of Object.entries(world.stateSchema.presets ?? {})) {
519
+ for (const [varId, value] of Object.entries(preset.values)) {
520
+ const variable = world.stateSchema.variables[varId];
521
+ if (!variable) {
522
+ findings.push(finding(
523
+ `preset-undeclared-var-${presetName}-${varId}`,
524
+ `Preset "${presetName}" sets undeclared variable "${varId}"`,
525
+ "error",
526
+ "referential-integrity",
527
+ ["state-schema.json"],
528
+ presetName
529
+ ));
530
+ continue;
531
+ }
532
+ if (variable.type === "number" && typeof value === "number") {
533
+ if (variable.min !== void 0 && value < variable.min) {
534
+ findings.push(finding(
535
+ `preset-below-min-${presetName}-${varId}`,
536
+ `Preset "${presetName}" sets "${varId}" to ${value}, below min ${variable.min}`,
537
+ "warning",
538
+ "schema-violation",
539
+ ["state-schema.json"],
540
+ presetName
541
+ ));
542
+ }
543
+ if (variable.max !== void 0 && value > variable.max) {
544
+ findings.push(finding(
545
+ `preset-above-max-${presetName}-${varId}`,
546
+ `Preset "${presetName}" sets "${varId}" to ${value}, above max ${variable.max}`,
547
+ "warning",
548
+ "schema-violation",
549
+ ["state-schema.json"],
550
+ presetName
551
+ ));
552
+ }
553
+ }
554
+ }
555
+ }
556
+ }
557
+ function computeCompletenessScore(world) {
558
+ let score = 0;
559
+ const total = 9;
560
+ if (world.world?.world_id && world.world?.name && world.world?.thesis) score++;
561
+ if (world.invariants && world.invariants.length > 0) score++;
562
+ if (world.assumptions?.profiles && Object.keys(world.assumptions.profiles).length > 0) score++;
563
+ if (world.stateSchema?.variables && Object.keys(world.stateSchema.variables).length > 0) score++;
564
+ if (world.rules && world.rules.length > 0) score++;
565
+ if (world.gates?.viability_classification && world.gates.viability_classification.length > 0) score++;
566
+ if (world.outcomes?.computed_outcomes && world.outcomes.computed_outcomes.length > 0) score++;
567
+ if (world.guards?.guards && world.guards.guards.length > 0) score++;
568
+ if (world.metadata) score++;
569
+ return Math.round(score / total * 100);
570
+ }
571
+ function computeInvariantCoverage(world) {
572
+ if (!world.invariants || world.invariants.length === 0) return 0;
573
+ const guards = world.guards?.guards ?? [];
574
+ let covered = 0;
575
+ for (const invariant of world.invariants) {
576
+ const hasGuard = guards.some((g) => g.invariant_ref === invariant.id && g.immutable);
577
+ if (hasGuard) covered++;
578
+ }
579
+ return Math.round(covered / world.invariants.length * 100);
580
+ }
581
+ function finding(id, message, severity, category, affectedBlocks, source, suggestion) {
582
+ const f = { id, message, severity, category, affectedBlocks };
583
+ if (source) f.source = source;
584
+ if (suggestion) f.suggestion = suggestion;
585
+ return f;
586
+ }
587
+
588
+ export {
589
+ validateWorld
590
+ };