@plures/praxis 1.4.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/browser/{chunk-N63K4KWS.js → chunk-4IRUGWR3.js} +1 -1
  2. package/dist/browser/chunk-6MVRT7CK.js +363 -0
  3. package/dist/browser/chunk-6SJ44Q64.js +473 -0
  4. package/dist/browser/chunk-BQOYZBWA.js +282 -0
  5. package/dist/browser/chunk-IG5BJ2MT.js +91 -0
  6. package/dist/browser/{chunk-MJK3IYTJ.js → chunk-JZDJU2DO.js} +4 -84
  7. package/dist/browser/chunk-ZEW4LJAJ.js +353 -0
  8. package/dist/browser/{engine-YIEGSX7U.js → engine-3B5WJPGT.js} +2 -1
  9. package/dist/browser/expectations/index.d.ts +180 -0
  10. package/dist/browser/expectations/index.js +14 -0
  11. package/dist/browser/factory/index.d.ts +150 -0
  12. package/dist/browser/factory/index.js +15 -0
  13. package/dist/browser/index.d.ts +277 -3
  14. package/dist/browser/index.js +425 -60
  15. package/dist/browser/integrations/svelte.d.ts +4 -2
  16. package/dist/browser/integrations/svelte.js +3 -2
  17. package/dist/browser/project/index.d.ts +177 -0
  18. package/dist/browser/project/index.js +19 -0
  19. package/dist/browser/reactive-engine.svelte-BwWadvAW.d.ts +224 -0
  20. package/dist/browser/rule-result-DcXWe9tn.d.ts +206 -0
  21. package/dist/browser/rules-BaWMqxuG.d.ts +277 -0
  22. package/dist/browser/unified/index.d.ts +239 -0
  23. package/dist/browser/unified/index.js +20 -0
  24. package/dist/node/chunk-6MVRT7CK.js +363 -0
  25. package/dist/node/chunk-AZLNISFI.js +1690 -0
  26. package/dist/node/chunk-IG5BJ2MT.js +91 -0
  27. package/dist/node/{chunk-KMJWAFZV.js → chunk-JZDJU2DO.js} +4 -89
  28. package/dist/node/{chunk-7M3HV4XR.js → chunk-WFRHXZBP.js} +3 -3
  29. package/dist/node/cli/index.cjs +48 -0
  30. package/dist/node/cli/index.js +2 -2
  31. package/dist/node/{engine-FEN5IYZ5.js → engine-VFHCIEM4.js} +2 -1
  32. package/dist/node/index.cjs +2114 -0
  33. package/dist/node/index.d.cts +964 -280
  34. package/dist/node/index.d.ts +964 -280
  35. package/dist/node/index.js +575 -10
  36. package/dist/node/integrations/svelte.d.cts +3 -2
  37. package/dist/node/integrations/svelte.d.ts +3 -2
  38. package/dist/node/integrations/svelte.js +3 -2
  39. package/dist/node/{reactive-engine.svelte-DekxqFu0.d.ts → reactive-engine.svelte-BBZLMzus.d.ts} +3 -79
  40. package/dist/node/{reactive-engine.svelte-Cg0Yc2Hs.d.cts → reactive-engine.svelte-Cbq_V20o.d.cts} +3 -79
  41. package/dist/node/rule-result-B9GMivAn.d.cts +80 -0
  42. package/dist/node/rule-result-Bo3sFMmN.d.ts +80 -0
  43. package/dist/node/{server-SYZPDULV.js → server-FKLVY57V.js} +4 -2
  44. package/dist/node/unified/index.cjs +484 -0
  45. package/dist/node/unified/index.d.cts +240 -0
  46. package/dist/node/unified/index.d.ts +240 -0
  47. package/dist/node/unified/index.js +21 -0
  48. package/dist/node/{validate-TQGVIG7G.js → validate-BY7JNY7H.js} +2 -1
  49. package/package.json +38 -11
  50. package/src/__tests__/chronos-project.test.ts +799 -0
  51. package/src/__tests__/decision-ledger.test.ts +857 -402
  52. package/src/chronos/diff.ts +336 -0
  53. package/src/chronos/hooks.ts +227 -0
  54. package/src/chronos/index.ts +83 -0
  55. package/src/chronos/project-chronicle.ts +198 -0
  56. package/src/chronos/timeline.ts +152 -0
  57. package/src/decision-ledger/analyzer-types.ts +280 -0
  58. package/src/decision-ledger/analyzer.ts +518 -0
  59. package/src/decision-ledger/contract-verification.ts +456 -0
  60. package/src/decision-ledger/derivation.ts +158 -0
  61. package/src/decision-ledger/index.ts +59 -0
  62. package/src/decision-ledger/report.ts +378 -0
  63. package/src/decision-ledger/suggestions.ts +287 -0
  64. package/src/index.browser.ts +103 -0
  65. package/src/index.ts +98 -0
  66. package/src/unified/__tests__/unified.test.ts +396 -0
  67. package/src/unified/core.ts +517 -0
  68. package/src/unified/index.ts +32 -0
  69. package/src/unified/rules.ts +66 -0
  70. package/src/unified/types.ts +148 -0
  71. package/dist/browser/reactive-engine.svelte-DjynI82A.d.ts +0 -688
  72. package/dist/node/chunk-FWOXU4MM.js +0 -487
@@ -2,12 +2,46 @@ import {
2
2
  PraxisRegistry,
3
3
  ReactiveLogicEngine,
4
4
  createReactiveEngine
5
- } from "./chunk-N63K4KWS.js";
5
+ } from "./chunk-4IRUGWR3.js";
6
+ import {
7
+ Expectation,
8
+ ExpectationSet,
9
+ expectBehavior,
10
+ formatVerificationReport,
11
+ verify
12
+ } from "./chunk-ZEW4LJAJ.js";
13
+ import {
14
+ dataRules,
15
+ formRules,
16
+ inputRules,
17
+ navigationRules,
18
+ toastRules
19
+ } from "./chunk-6SJ44Q64.js";
20
+ import {
21
+ branchRules,
22
+ commitFromState,
23
+ defineGate,
24
+ expectationGate,
25
+ formatGate,
26
+ lintGate,
27
+ semverContract
28
+ } from "./chunk-BQOYZBWA.js";
29
+ import {
30
+ createApp,
31
+ defineConstraint,
32
+ defineModule,
33
+ definePath,
34
+ defineRule
35
+ } from "./chunk-6MVRT7CK.js";
6
36
  import {
7
37
  LogicEngine,
8
38
  PRAXIS_PROTOCOL_VERSION,
9
39
  createPraxisEngine
10
- } from "./chunk-MJK3IYTJ.js";
40
+ } from "./chunk-JZDJU2DO.js";
41
+ import {
42
+ RuleResult,
43
+ fact
44
+ } from "./chunk-IG5BJ2MT.js";
11
45
  import {
12
46
  InMemoryPraxisDB,
13
47
  PluresDBPraxisAdapter,
@@ -563,8 +597,8 @@ function defineFact(tag) {
563
597
  create(payload) {
564
598
  return { tag, payload };
565
599
  },
566
- is(fact) {
567
- return fact.tag === tag;
600
+ is(fact2) {
601
+ return fact2.tag === tag;
568
602
  }
569
603
  };
570
604
  }
@@ -579,7 +613,7 @@ function defineEvent(tag) {
579
613
  }
580
614
  };
581
615
  }
582
- function defineRule(options) {
616
+ function defineRule2(options) {
583
617
  const contract = options.contract ?? options.meta?.contract;
584
618
  const meta = contract ? { ...options.meta ?? {}, contract } : options.meta;
585
619
  return {
@@ -591,7 +625,7 @@ function defineRule(options) {
591
625
  meta
592
626
  };
593
627
  }
594
- function defineConstraint(options) {
628
+ function defineConstraint2(options) {
595
629
  const contract = options.contract ?? options.meta?.contract;
596
630
  const meta = contract ? { ...options.meta ?? {}, contract } : options.meta;
597
631
  return {
@@ -602,7 +636,7 @@ function defineConstraint(options) {
602
636
  meta
603
637
  };
604
638
  }
605
- function defineModule(options) {
639
+ function defineModule2(options) {
606
640
  return {
607
641
  rules: options.rules ?? [],
608
642
  constraints: options.constraints ?? [],
@@ -666,16 +700,16 @@ function validateSchema(schema) {
666
700
  if (schema.logic) {
667
701
  schema.logic.forEach((logic, logicIndex) => {
668
702
  if (logic.facts) {
669
- logic.facts.forEach((fact, factIndex) => {
670
- if (!fact.tag) {
703
+ logic.facts.forEach((fact2, factIndex) => {
704
+ if (!fact2.tag) {
671
705
  errors.push({
672
706
  path: `logic[${logicIndex}].facts[${factIndex}].tag`,
673
707
  message: "Fact tag is required"
674
708
  });
675
- } else if (!isValidIdentifier(fact.tag)) {
709
+ } else if (!isValidIdentifier(fact2.tag)) {
676
710
  errors.push({
677
711
  path: `logic[${logicIndex}].facts[${factIndex}].tag`,
678
- message: `Fact tag "${fact.tag}" is not a valid JavaScript identifier. Use only letters, numbers, underscores, and dollar signs, and do not start with a number.`
712
+ message: `Fact tag "${fact2.tag}" is not a valid JavaScript identifier. Use only letters, numbers, underscores, and dollar signs, and do not start with a number.`
679
713
  });
680
714
  }
681
715
  });
@@ -1002,37 +1036,37 @@ var PraxisDBStore = class {
1002
1036
  * @param fact The fact to store
1003
1037
  * @returns Promise that resolves when the fact is stored
1004
1038
  */
1005
- async storeFact(fact) {
1006
- const constraintResult = await this.checkConstraints([fact]);
1039
+ async storeFact(fact2) {
1040
+ const constraintResult = await this.checkConstraints([fact2]);
1007
1041
  if (!constraintResult.valid) {
1008
1042
  throw new Error(`Constraint violation: ${constraintResult.errors.join(", ")}`);
1009
1043
  }
1010
1044
  let before;
1011
1045
  if (this.chronicle) {
1012
- const payload = fact.payload;
1046
+ const payload = fact2.payload;
1013
1047
  const id = payload?.id;
1014
1048
  if (id) {
1015
- before = await this.getFact(fact.tag, id);
1049
+ before = await this.getFact(fact2.tag, id);
1016
1050
  }
1017
1051
  }
1018
- await this.persistFact(fact);
1052
+ await this.persistFact(fact2);
1019
1053
  if (this.chronicle) {
1020
- const payload = fact.payload;
1054
+ const payload = fact2.payload;
1021
1055
  const id = payload?.id ?? "";
1022
1056
  const span = ChronicleContext.current;
1023
1057
  try {
1024
1058
  await this.chronicle.record({
1025
- path: getFactPath(fact.tag, id),
1059
+ path: getFactPath(fact2.tag, id),
1026
1060
  before,
1027
- after: fact,
1061
+ after: fact2,
1028
1062
  cause: span?.spanId,
1029
1063
  context: span?.contextId,
1030
- metadata: { factTag: fact.tag, operation: "storeFact" }
1064
+ metadata: { factTag: fact2.tag, operation: "storeFact" }
1031
1065
  });
1032
1066
  } catch {
1033
1067
  }
1034
1068
  }
1035
- await this.triggerRules([fact]);
1069
+ await this.triggerRules([fact2]);
1036
1070
  }
1037
1071
  /**
1038
1072
  * Store multiple facts in PluresDB
@@ -1044,28 +1078,28 @@ var PraxisDBStore = class {
1044
1078
  if (!constraintResult.valid) {
1045
1079
  throw new Error(`Constraint violation: ${constraintResult.errors.join(", ")}`);
1046
1080
  }
1047
- for (const fact of facts) {
1081
+ for (const fact2 of facts) {
1048
1082
  let before;
1049
1083
  if (this.chronicle) {
1050
- const payload = fact.payload;
1084
+ const payload = fact2.payload;
1051
1085
  const id = payload?.id;
1052
1086
  if (id) {
1053
- before = await this.getFact(fact.tag, id);
1087
+ before = await this.getFact(fact2.tag, id);
1054
1088
  }
1055
1089
  }
1056
- await this.persistFact(fact);
1090
+ await this.persistFact(fact2);
1057
1091
  if (this.chronicle) {
1058
- const payload = fact.payload;
1092
+ const payload = fact2.payload;
1059
1093
  const id = payload?.id ?? "";
1060
1094
  const span = ChronicleContext.current;
1061
1095
  try {
1062
1096
  await this.chronicle.record({
1063
- path: getFactPath(fact.tag, id),
1097
+ path: getFactPath(fact2.tag, id),
1064
1098
  before,
1065
- after: fact,
1099
+ after: fact2,
1066
1100
  cause: span?.spanId,
1067
1101
  context: span?.contextId,
1068
- metadata: { factTag: fact.tag, operation: "storeFacts" }
1102
+ metadata: { factTag: fact2.tag, operation: "storeFacts" }
1069
1103
  });
1070
1104
  } catch {
1071
1105
  }
@@ -1077,11 +1111,11 @@ var PraxisDBStore = class {
1077
1111
  * Internal method to persist a fact without constraint checking
1078
1112
  * Used by both storeFact and derived fact storage
1079
1113
  */
1080
- async persistFact(fact) {
1081
- const payload = fact.payload;
1114
+ async persistFact(fact2) {
1115
+ const payload = fact2.payload;
1082
1116
  const id = payload?.id ?? generateId();
1083
- const path = getFactPath(fact.tag, id);
1084
- await this.db.set(path, fact);
1117
+ const path = getFactPath(fact2.tag, id);
1118
+ await this.db.set(path, fact2);
1085
1119
  }
1086
1120
  /**
1087
1121
  * Get a fact by tag and id
@@ -1218,8 +1252,8 @@ var PraxisDBStore = class {
1218
1252
  if (watchers) {
1219
1253
  watchers.add(callback);
1220
1254
  }
1221
- const unsubscribe = this.db.watch(path, (fact) => {
1222
- callback([fact]);
1255
+ const unsubscribe = this.db.watch(path, (fact2) => {
1256
+ callback([fact2]);
1223
1257
  });
1224
1258
  this.subscriptions.push(unsubscribe);
1225
1259
  return () => {
@@ -1296,19 +1330,19 @@ var PraxisDBStore = class {
1296
1330
  if (derivedFacts.length > 0) {
1297
1331
  const constraintResult = await this.checkConstraints(derivedFacts);
1298
1332
  if (constraintResult.valid) {
1299
- for (const fact of derivedFacts) {
1300
- await this.persistFact(fact);
1333
+ for (const fact2 of derivedFacts) {
1334
+ await this.persistFact(fact2);
1301
1335
  if (this.chronicle) {
1302
- const payload = fact.payload;
1336
+ const payload = fact2.payload;
1303
1337
  const id = payload?.id ?? "";
1304
1338
  const span = ChronicleContext.current;
1305
1339
  try {
1306
1340
  await this.chronicle.record({
1307
- path: getFactPath(fact.tag, id),
1308
- after: fact,
1341
+ path: getFactPath(fact2.tag, id),
1342
+ after: fact2,
1309
1343
  cause: span?.spanId,
1310
1344
  context: span?.contextId,
1311
- metadata: { factTag: fact.tag, operation: "derivedFact" }
1345
+ metadata: { factTag: fact2.tag, operation: "derivedFact" }
1312
1346
  });
1313
1347
  } catch {
1314
1348
  }
@@ -1768,7 +1802,7 @@ async function createUnumAdapter(config) {
1768
1802
  type: "event"
1769
1803
  });
1770
1804
  }
1771
- async function broadcastFact(channelId, fact) {
1805
+ async function broadcastFact(channelId, fact2) {
1772
1806
  const channel = channels.get(channelId);
1773
1807
  if (!channel) {
1774
1808
  throw new Error(`Not joined to channel ${channelId}`);
@@ -1776,7 +1810,7 @@ async function createUnumAdapter(config) {
1776
1810
  await channel.publish({
1777
1811
  id: generateId2(),
1778
1812
  sender: currentIdentity || { id: "anonymous", createdAt: Date.now() },
1779
- content: fact,
1813
+ content: fact2,
1780
1814
  type: "fact"
1781
1815
  });
1782
1816
  }
@@ -1919,17 +1953,17 @@ function schemaToCanvas(schema, _options = {}) {
1919
1953
  yOffset += schema.events.length * ySpacing + 30;
1920
1954
  }
1921
1955
  if (schema.facts) {
1922
- schema.facts.forEach((fact, index) => {
1923
- const pos = fact.position && (fact.position.x !== 0 || fact.position.y !== 0) ? fact.position : { x: 50 + xSpacing * 3, y: yOffset + index * ySpacing };
1956
+ schema.facts.forEach((fact2, index) => {
1957
+ const pos = fact2.position && (fact2.position.x !== 0 || fact2.position.y !== 0) ? fact2.position : { x: 50 + xSpacing * 3, y: yOffset + index * ySpacing };
1924
1958
  const node = {
1925
- id: fact.id || `fact-${nodeId++}`,
1959
+ id: fact2.id || `fact-${nodeId++}`,
1926
1960
  type: "fact",
1927
- label: fact.tag,
1961
+ label: fact2.tag,
1928
1962
  x: pos.x,
1929
1963
  y: pos.y,
1930
1964
  width: 150,
1931
1965
  height: 50,
1932
- data: fact,
1966
+ data: fact2,
1933
1967
  style: {
1934
1968
  backgroundColor: "#fce4ec",
1935
1969
  borderColor: "#c2185b"
@@ -2384,8 +2418,8 @@ var StateDocsGenerator = class {
2384
2418
  if (logic.facts && logic.facts.length > 0) {
2385
2419
  lines.push("**Facts:**");
2386
2420
  lines.push("");
2387
- for (const fact of logic.facts) {
2388
- lines.push(`- \`${fact.tag}\`: ${fact.description || ""}`);
2421
+ for (const fact2 of logic.facts) {
2422
+ lines.push(`- \`${fact2.tag}\`: ${fact2.description || ""}`);
2389
2423
  }
2390
2424
  lines.push("");
2391
2425
  }
@@ -2517,9 +2551,9 @@ var StateDocsGenerator = class {
2517
2551
  lines.push("");
2518
2552
  lines.push("| Fact | Description | Payload |");
2519
2553
  lines.push("|------|-------------|---------|");
2520
- for (const fact of logic.facts) {
2521
- const payload = fact.payload ? Object.entries(fact.payload).map(([k, v]) => `${k}: ${v}`).join(", ") : "-";
2522
- lines.push(`| \`${fact.tag}\` | ${fact.description || "-"} | ${payload} |`);
2554
+ for (const fact2 of logic.facts) {
2555
+ const payload = fact2.payload ? Object.entries(fact2.payload).map(([k, v]) => `${k}: ${v}`).join(", ") : "-";
2556
+ lines.push(`| \`${fact2.tag}\` | ${fact2.description || "-"} | ${payload} |`);
2523
2557
  }
2524
2558
  lines.push("");
2525
2559
  }
@@ -2571,8 +2605,8 @@ var StateDocsGenerator = class {
2571
2605
  for (const event of logic.events) {
2572
2606
  lines.push(` Processing --> ${event.tag.replace(/[^a-zA-Z0-9]/g, "")}: ${event.tag}`);
2573
2607
  }
2574
- for (const fact of logic.facts) {
2575
- lines.push(` ${fact.tag.replace(/[^a-zA-Z0-9]/g, "")} --> [*]`);
2608
+ for (const fact2 of logic.facts) {
2609
+ lines.push(` ${fact2.tag.replace(/[^a-zA-Z0-9]/g, "")} --> [*]`);
2576
2610
  }
2577
2611
  }
2578
2612
  return {
@@ -2894,7 +2928,7 @@ function generateTauriConfig(config) {
2894
2928
 
2895
2929
  // src/integrations/unified.ts
2896
2930
  async function createUnifiedApp(config) {
2897
- const { createPraxisEngine: createPraxisEngine2 } = await import("./engine-YIEGSX7U.js");
2931
+ const { createPraxisEngine: createPraxisEngine2 } = await import("./engine-3B5WJPGT.js");
2898
2932
  const { createInMemoryDB: createInMemoryDB2 } = await import("./adapter-CIMBGDC7.js");
2899
2933
  const db = config.db || createInMemoryDB2();
2900
2934
  const pluresdb = createPluresDBAdapter({
@@ -3023,8 +3057,302 @@ async function attachAllIntegrations(engine, registry, options = {}) {
3023
3057
  }
3024
3058
  };
3025
3059
  }
3060
+
3061
+ // src/core/ui-rules.ts
3062
+ var loadingGateRule = {
3063
+ id: "ui/loading-gate",
3064
+ description: "Signals when the app is in a loading state",
3065
+ eventTypes: ["ui.state-change", "app.init"],
3066
+ impl: (state) => {
3067
+ const ctx = state.context;
3068
+ if (ctx.loading) {
3069
+ return RuleResult.emit([fact("ui.loading-gate", { active: true })]);
3070
+ }
3071
+ return RuleResult.retract(["ui.loading-gate"], "Not loading");
3072
+ },
3073
+ contract: {
3074
+ ruleId: "RULE_ID_PLACEHOLDER",
3075
+ behavior: "Emits ui.loading-gate when context.loading is true, retracts when false",
3076
+ examples: [
3077
+ { given: "loading is true", when: "ui state changes", then: "ui.loading-gate emitted" },
3078
+ { given: "loading is false", when: "ui state changes", then: "ui.loading-gate retracted" }
3079
+ ],
3080
+ invariants: ["Loading gate must reflect context.loading exactly"]
3081
+ }
3082
+ };
3083
+ var errorDisplayRule = {
3084
+ id: "ui/error-display",
3085
+ description: "Signals when an error should be displayed to the user",
3086
+ eventTypes: ["ui.state-change", "app.error"],
3087
+ impl: (state) => {
3088
+ const ctx = state.context;
3089
+ if (ctx.error) {
3090
+ return RuleResult.emit([fact("ui.error-display", { message: ctx.error, severity: "error" })]);
3091
+ }
3092
+ return RuleResult.retract(["ui.error-display"], "Error cleared");
3093
+ },
3094
+ contract: {
3095
+ ruleId: "RULE_ID_PLACEHOLDER",
3096
+ behavior: "Emits ui.error-display when context.error is non-null, retracts when cleared",
3097
+ examples: [
3098
+ { given: "error is set", when: "ui state changes", then: "ui.error-display emitted with message" },
3099
+ { given: "error is null", when: "ui state changes", then: "ui.error-display retracted" }
3100
+ ],
3101
+ invariants: ["Error display must clear when error is null"]
3102
+ }
3103
+ };
3104
+ var offlineIndicatorRule = {
3105
+ id: "ui/offline-indicator",
3106
+ description: "Signals when the app is offline",
3107
+ eventTypes: ["ui.state-change", "network.change"],
3108
+ impl: (state) => {
3109
+ if (state.context.offline) {
3110
+ return RuleResult.emit([fact("ui.offline", { message: "You are offline. Changes will sync when reconnected." })]);
3111
+ }
3112
+ return RuleResult.retract(["ui.offline"], "Back online");
3113
+ },
3114
+ contract: {
3115
+ ruleId: "RULE_ID_PLACEHOLDER",
3116
+ behavior: "Emits ui.offline when context.offline is true, retracts when back online",
3117
+ examples: [
3118
+ { given: "offline is true", when: "network changes", then: "ui.offline emitted" },
3119
+ { given: "offline is false", when: "network changes", then: "ui.offline retracted" }
3120
+ ],
3121
+ invariants: ["Offline indicator must match actual connectivity"]
3122
+ }
3123
+ };
3124
+ var dirtyGuardRule = {
3125
+ id: "ui/dirty-guard",
3126
+ description: "Warns when there are unsaved changes",
3127
+ eventTypes: ["ui.state-change", "navigation.request"],
3128
+ impl: (state) => {
3129
+ if (state.context.dirty) {
3130
+ return RuleResult.emit([fact("ui.unsaved-warning", {
3131
+ message: "You have unsaved changes",
3132
+ blocking: true
3133
+ })]);
3134
+ }
3135
+ return RuleResult.retract(["ui.unsaved-warning"], "No unsaved changes");
3136
+ },
3137
+ contract: {
3138
+ ruleId: "RULE_ID_PLACEHOLDER",
3139
+ behavior: "Emits ui.unsaved-warning when context.dirty is true, retracts when saved",
3140
+ examples: [
3141
+ { given: "dirty is true", when: "ui state changes", then: "ui.unsaved-warning emitted with blocking=true" },
3142
+ { given: "dirty is false", when: "ui state changes", then: "ui.unsaved-warning retracted" }
3143
+ ],
3144
+ invariants: ["Dirty guard must clear after save"]
3145
+ }
3146
+ };
3147
+ var initGateRule = {
3148
+ id: "ui/init-gate",
3149
+ description: "Signals whether the app has completed initialization",
3150
+ eventTypes: ["ui.state-change", "app.init"],
3151
+ impl: (state) => {
3152
+ if (!state.context.initialized) {
3153
+ return RuleResult.emit([fact("ui.init-pending", {
3154
+ message: "App is initializing..."
3155
+ })]);
3156
+ }
3157
+ return RuleResult.retract(["ui.init-pending"], "App initialized");
3158
+ },
3159
+ contract: {
3160
+ ruleId: "RULE_ID_PLACEHOLDER",
3161
+ behavior: "Emits ui.init-pending until context.initialized is true",
3162
+ examples: [
3163
+ { given: "initialized is false", when: "app starts", then: "ui.init-pending emitted" },
3164
+ { given: "initialized is true", when: "init completes", then: "ui.init-pending retracted" }
3165
+ ],
3166
+ invariants: ["Init gate must clear exactly once, when initialization completes"]
3167
+ }
3168
+ };
3169
+ var viewportRule = {
3170
+ id: "ui/viewport-class",
3171
+ description: "Classifies viewport size for responsive layout decisions",
3172
+ eventTypes: ["ui.state-change", "ui.resize"],
3173
+ impl: (state) => {
3174
+ const vp = state.context.viewport;
3175
+ if (!vp) return RuleResult.skip("No viewport data");
3176
+ return RuleResult.emit([fact("ui.viewport-class", {
3177
+ viewport: vp,
3178
+ compact: vp === "mobile",
3179
+ showSidebar: vp !== "mobile"
3180
+ })]);
3181
+ },
3182
+ contract: {
3183
+ ruleId: "RULE_ID_PLACEHOLDER",
3184
+ behavior: "Classifies viewport into responsive layout hints",
3185
+ examples: [
3186
+ { given: "viewport is mobile", when: "resize event", then: "compact=true, showSidebar=false" },
3187
+ { given: "viewport is desktop", when: "resize event", then: "compact=false, showSidebar=true" }
3188
+ ],
3189
+ invariants: ["Viewport class must update on every resize event"]
3190
+ }
3191
+ };
3192
+ var noInteractionWhileLoadingConstraint = {
3193
+ id: "ui/no-interaction-while-loading",
3194
+ description: "Prevents data mutations while a load operation is in progress",
3195
+ impl: (state) => {
3196
+ if (state.context.loading) {
3197
+ return "Cannot perform action while data is loading";
3198
+ }
3199
+ return true;
3200
+ },
3201
+ contract: {
3202
+ ruleId: "RULE_ID_PLACEHOLDER",
3203
+ behavior: "Fails when context.loading is true",
3204
+ examples: [
3205
+ { given: "loading is true", when: "action attempted", then: "violation" },
3206
+ { given: "loading is false", when: "action attempted", then: "pass" }
3207
+ ],
3208
+ invariants: ["Must always fail during loading"]
3209
+ }
3210
+ };
3211
+ var mustBeInitializedConstraint = {
3212
+ id: "ui/must-be-initialized",
3213
+ description: "Requires app initialization before user interactions",
3214
+ impl: (state) => {
3215
+ if (!state.context.initialized) {
3216
+ return "App must be initialized before performing this action";
3217
+ }
3218
+ return true;
3219
+ },
3220
+ contract: {
3221
+ ruleId: "RULE_ID_PLACEHOLDER",
3222
+ behavior: "Fails when context.initialized is false",
3223
+ examples: [
3224
+ { given: "initialized is false", when: "action attempted", then: "violation" },
3225
+ { given: "initialized is true", when: "action attempted", then: "pass" }
3226
+ ],
3227
+ invariants: ["Must always fail before init completes"]
3228
+ }
3229
+ };
3230
+ var uiModule = {
3231
+ rules: [
3232
+ loadingGateRule,
3233
+ errorDisplayRule,
3234
+ offlineIndicatorRule,
3235
+ dirtyGuardRule,
3236
+ initGateRule,
3237
+ viewportRule
3238
+ ],
3239
+ constraints: [
3240
+ noInteractionWhileLoadingConstraint,
3241
+ mustBeInitializedConstraint
3242
+ ],
3243
+ meta: {
3244
+ name: "praxis-ui",
3245
+ version: "1.0.0",
3246
+ description: "Predefined UI rules and constraints \u2014 separate from business logic"
3247
+ }
3248
+ };
3249
+ function createUIModule(options) {
3250
+ const allRules = uiModule.rules;
3251
+ const allConstraints = uiModule.constraints;
3252
+ const selectedRules = options.rules ? allRules.filter((r) => options.rules.includes(r.id)) : allRules;
3253
+ const selectedConstraints = options.constraints ? allConstraints.filter((c) => options.constraints.includes(c.id)) : allConstraints;
3254
+ return {
3255
+ rules: [...selectedRules, ...options.extraRules ?? []],
3256
+ constraints: [...selectedConstraints, ...options.extraConstraints ?? []],
3257
+ meta: { ...uiModule.meta, customized: true }
3258
+ };
3259
+ }
3260
+ function uiStateChanged(changes) {
3261
+ return { tag: "ui.state-change", payload: changes ?? {} };
3262
+ }
3263
+ function navigationRequest(to) {
3264
+ return { tag: "navigation.request", payload: { to } };
3265
+ }
3266
+ function resizeEvent(width, height) {
3267
+ return { tag: "ui.resize", payload: { width, height } };
3268
+ }
3269
+
3270
+ // src/core/completeness.ts
3271
+ function auditCompleteness(manifest, registryRuleIds, registryConstraintIds, rulesWithContracts, config) {
3272
+ const threshold = config?.threshold ?? 90;
3273
+ const domainBranches = manifest.branches.filter((b) => b.kind === "domain");
3274
+ const coveredDomain = domainBranches.filter((b) => b.coveredBy && registryRuleIds.includes(b.coveredBy));
3275
+ const uncoveredDomain = domainBranches.filter((b) => !b.coveredBy || !registryRuleIds.includes(b.coveredBy));
3276
+ const invariantBranches = manifest.branches.filter((b) => b.kind === "invariant");
3277
+ const coveredInvariants = invariantBranches.filter((b) => b.coveredBy && registryConstraintIds.includes(b.coveredBy));
3278
+ const uncoveredInvariants = invariantBranches.filter((b) => !b.coveredBy || !registryConstraintIds.includes(b.coveredBy));
3279
+ const needContracts = manifest.rulesNeedingContracts;
3280
+ const haveContracts = needContracts.filter((id) => rulesWithContracts.includes(id));
3281
+ const missingContracts = needContracts.filter((id) => !rulesWithContracts.includes(id));
3282
+ const neededFields = manifest.stateFields.filter((f) => f.usedByRule);
3283
+ const coveredFields = neededFields.filter((f) => f.inContext);
3284
+ const missingFields = neededFields.filter((f) => !f.inContext);
3285
+ const coveredTransitions = manifest.transitions.filter((t) => t.eventTag);
3286
+ const missingTransitions = manifest.transitions.filter((t) => !t.eventTag);
3287
+ const ruleScore = domainBranches.length > 0 ? coveredDomain.length / domainBranches.length * 40 : 40;
3288
+ const constraintScore = invariantBranches.length > 0 ? coveredInvariants.length / invariantBranches.length * 20 : 20;
3289
+ const contractScore = needContracts.length > 0 ? haveContracts.length / needContracts.length * 15 : 15;
3290
+ const contextScore = neededFields.length > 0 ? coveredFields.length / neededFields.length * 15 : 15;
3291
+ const eventScore = manifest.transitions.length > 0 ? coveredTransitions.length / manifest.transitions.length * 10 : 10;
3292
+ const score = Math.round(ruleScore + constraintScore + contractScore + contextScore + eventScore);
3293
+ const rating = score >= 90 ? "complete" : score >= 70 ? "good" : score >= 50 ? "partial" : "incomplete";
3294
+ const report = {
3295
+ score,
3296
+ rating,
3297
+ rules: { total: domainBranches.length, covered: coveredDomain.length, uncovered: uncoveredDomain },
3298
+ constraints: { total: invariantBranches.length, covered: coveredInvariants.length, uncovered: uncoveredInvariants },
3299
+ contracts: { total: needContracts.length, withContracts: haveContracts.length, missing: missingContracts },
3300
+ context: { total: neededFields.length, covered: coveredFields.length, missing: missingFields },
3301
+ events: { total: manifest.transitions.length, covered: coveredTransitions.length, missing: missingTransitions }
3302
+ };
3303
+ if (config?.strict && score < threshold) {
3304
+ throw new Error(`Praxis completeness ${score}/100 (${rating}) \u2014 below threshold ${threshold}. ${uncoveredDomain.length} uncovered rules, ${uncoveredInvariants.length} uncovered invariants, ${missingContracts.length} missing contracts.`);
3305
+ }
3306
+ return report;
3307
+ }
3308
+ function formatReport(report) {
3309
+ const lines = [];
3310
+ const icon = report.rating === "complete" ? "\u2705" : report.rating === "good" ? "\u{1F7E2}" : report.rating === "partial" ? "\u{1F7E1}" : "\u{1F534}";
3311
+ lines.push(`${icon} Praxis Completeness: ${report.score}/100 (${report.rating})`);
3312
+ lines.push("");
3313
+ lines.push(`Rules: ${report.rules.covered}/${report.rules.total} domain branches covered (${pct(report.rules.covered, report.rules.total)})`);
3314
+ lines.push(`Constraints: ${report.constraints.covered}/${report.constraints.total} invariants covered (${pct(report.constraints.covered, report.constraints.total)})`);
3315
+ lines.push(`Contracts: ${report.contracts.withContracts}/${report.contracts.total} rules have contracts (${pct(report.contracts.withContracts, report.contracts.total)})`);
3316
+ lines.push(`Context: ${report.context.covered}/${report.context.total} state fields in context (${pct(report.context.covered, report.context.total)})`);
3317
+ lines.push(`Events: ${report.events.covered}/${report.events.total} transitions have events (${pct(report.events.covered, report.events.total)})`);
3318
+ if (report.rules.uncovered.length > 0) {
3319
+ lines.push("");
3320
+ lines.push("Uncovered domain logic:");
3321
+ for (const b of report.rules.uncovered) {
3322
+ lines.push(` \u274C ${b.location}: ${b.condition}${b.note ? ` \u2014 ${b.note}` : ""}`);
3323
+ }
3324
+ }
3325
+ if (report.constraints.uncovered.length > 0) {
3326
+ lines.push("");
3327
+ lines.push("Uncovered invariants:");
3328
+ for (const b of report.constraints.uncovered) {
3329
+ lines.push(` \u274C ${b.location}: ${b.condition}${b.note ? ` \u2014 ${b.note}` : ""}`);
3330
+ }
3331
+ }
3332
+ if (report.contracts.missing.length > 0) {
3333
+ lines.push("");
3334
+ lines.push("Rules missing contracts:");
3335
+ for (const id of report.contracts.missing) {
3336
+ lines.push(` \u{1F4DD} ${id}`);
3337
+ }
3338
+ }
3339
+ if (report.events.missing.length > 0) {
3340
+ lines.push("");
3341
+ lines.push("State transitions without events:");
3342
+ for (const t of report.events.missing) {
3343
+ lines.push(` \u26A1 ${t.location}: ${t.description}`);
3344
+ }
3345
+ }
3346
+ return lines.join("\n");
3347
+ }
3348
+ function pct(a, b) {
3349
+ if (b === 0) return "100%";
3350
+ return Math.round(a / b * 100) + "%";
3351
+ }
3026
3352
  export {
3027
3353
  ActorManager,
3354
+ Expectation,
3355
+ ExpectationSet,
3028
3356
  ReactiveLogicEngine2 as FrameworkAgnosticReactiveEngine,
3029
3357
  InMemoryPraxisDB,
3030
3358
  LogicEngine,
@@ -3037,14 +3365,19 @@ export {
3037
3365
  PraxisSchemaRegistry,
3038
3366
  ReactiveLogicEngine,
3039
3367
  RegistryIntrospector,
3368
+ RuleResult,
3040
3369
  StateDocsGenerator,
3041
3370
  attachAllIntegrations,
3042
3371
  attachTauriToEngine,
3043
3372
  attachToEngine,
3044
3373
  attachUnumToEngine,
3374
+ auditCompleteness,
3375
+ branchRules,
3045
3376
  canvasToMermaid,
3046
3377
  canvasToSchema,
3047
3378
  canvasToYaml,
3379
+ commitFromState,
3380
+ createApp,
3048
3381
  createCanvasEditor,
3049
3382
  createReactiveEngine2 as createFrameworkAgnosticReactiveEngine,
3050
3383
  createInMemoryDB,
@@ -3063,28 +3396,60 @@ export {
3063
3396
  createStateDocsGenerator,
3064
3397
  createTauriPraxisAdapter,
3065
3398
  createTimerActor,
3399
+ createUIModule,
3066
3400
  createUnifiedApp,
3067
3401
  createUnumAdapter,
3068
- defineConstraint,
3402
+ dataRules,
3403
+ defineConstraint2 as defineConstraint,
3069
3404
  defineEvent,
3070
3405
  defineFact,
3071
- defineModule,
3072
- defineRule,
3406
+ defineGate,
3407
+ defineModule2 as defineModule,
3408
+ definePath,
3409
+ defineRule2 as defineRule,
3410
+ defineConstraint as defineUnifiedConstraint,
3411
+ defineModule as defineUnifiedModule,
3412
+ defineRule as defineUnifiedRule,
3413
+ dirtyGuardRule,
3414
+ errorDisplayRule,
3415
+ expectBehavior,
3416
+ expectationGate,
3417
+ fact,
3073
3418
  filterEvents,
3074
3419
  filterFacts,
3075
3420
  findEvent,
3076
3421
  findFact,
3422
+ formRules,
3423
+ formatGate,
3424
+ formatReport,
3425
+ formatVerificationReport,
3077
3426
  generateDocs,
3078
3427
  generateId,
3079
3428
  generateTauriConfig,
3080
3429
  getEventPath,
3081
3430
  getFactPath,
3082
3431
  getSchemaPath,
3432
+ initGateRule,
3433
+ inputRules,
3434
+ lintGate,
3083
3435
  loadSchemaFromJson,
3084
3436
  loadSchemaFromYaml,
3437
+ loadingGateRule,
3438
+ mustBeInitializedConstraint,
3439
+ navigationRequest,
3440
+ navigationRules,
3441
+ noInteractionWhileLoadingConstraint,
3442
+ offlineIndicatorRule,
3085
3443
  registerSchema,
3444
+ resizeEvent,
3086
3445
  schemaToCanvas,
3446
+ semverContract,
3447
+ toastRules,
3448
+ uiModule,
3449
+ uiStateChanged,
3087
3450
  validateForGeneration,
3088
3451
  validateSchema,
3089
- validateWithGuardian
3452
+ validateWithGuardian,
3453
+ verify,
3454
+ viewportRule
3090
3455
  };