@plures/praxis 1.4.0 → 1.4.4
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/browser/{chunk-N63K4KWS.js → chunk-4IRUGWR3.js} +1 -1
- package/dist/browser/chunk-6SJ44Q64.js +473 -0
- package/dist/browser/chunk-BQOYZBWA.js +282 -0
- package/dist/browser/chunk-IG5BJ2MT.js +91 -0
- package/dist/browser/{chunk-MJK3IYTJ.js → chunk-JZDJU2DO.js} +4 -84
- package/dist/browser/chunk-ZEW4LJAJ.js +353 -0
- package/dist/browser/{engine-YIEGSX7U.js → engine-3B5WJPGT.js} +2 -1
- package/dist/browser/expectations/index.d.ts +180 -0
- package/dist/browser/expectations/index.js +14 -0
- package/dist/browser/factory/index.d.ts +149 -0
- package/dist/browser/factory/index.js +15 -0
- package/dist/browser/index.d.ts +274 -3
- package/dist/browser/index.js +407 -54
- package/dist/browser/integrations/svelte.d.ts +3 -2
- package/dist/browser/integrations/svelte.js +3 -2
- package/dist/browser/project/index.d.ts +176 -0
- package/dist/browser/project/index.js +19 -0
- package/dist/browser/reactive-engine.svelte-DgVTqHLc.d.ts +223 -0
- package/dist/browser/{reactive-engine.svelte-DjynI82A.d.ts → rules-i1LHpnGd.d.ts} +13 -221
- package/dist/node/chunk-AZLNISFI.js +1690 -0
- package/dist/node/chunk-IG5BJ2MT.js +91 -0
- package/dist/node/{chunk-KMJWAFZV.js → chunk-JZDJU2DO.js} +4 -89
- package/dist/node/{chunk-7M3HV4XR.js → chunk-ZO2LU4G4.js} +1 -1
- package/dist/node/cli/index.cjs +48 -0
- package/dist/node/cli/index.js +2 -2
- package/dist/node/{engine-FEN5IYZ5.js → engine-VFHCIEM4.js} +2 -1
- package/dist/node/index.cjs +1747 -0
- package/dist/node/index.d.cts +960 -278
- package/dist/node/index.d.ts +960 -278
- package/dist/node/index.js +559 -6
- package/dist/node/integrations/svelte.js +3 -2
- package/dist/node/{server-SYZPDULV.js → server-FKLVY57V.js} +4 -2
- package/dist/node/{validate-TQGVIG7G.js → validate-5PSWJTIC.js} +2 -1
- package/package.json +32 -11
- package/src/__tests__/chronos-project.test.ts +799 -0
- package/src/__tests__/decision-ledger.test.ts +857 -402
- package/src/chronos/diff.ts +336 -0
- package/src/chronos/hooks.ts +227 -0
- package/src/chronos/index.ts +83 -0
- package/src/chronos/project-chronicle.ts +198 -0
- package/src/chronos/timeline.ts +152 -0
- package/src/decision-ledger/analyzer-types.ts +280 -0
- package/src/decision-ledger/analyzer.ts +518 -0
- package/src/decision-ledger/contract-verification.ts +456 -0
- package/src/decision-ledger/derivation.ts +158 -0
- package/src/decision-ledger/index.ts +59 -0
- package/src/decision-ledger/report.ts +378 -0
- package/src/decision-ledger/suggestions.ts +287 -0
- package/src/index.browser.ts +83 -0
- package/src/index.ts +77 -0
- package/dist/node/chunk-FWOXU4MM.js +0 -487
package/dist/browser/index.js
CHANGED
|
@@ -2,12 +2,39 @@ import {
|
|
|
2
2
|
PraxisRegistry,
|
|
3
3
|
ReactiveLogicEngine,
|
|
4
4
|
createReactiveEngine
|
|
5
|
-
} from "./chunk-
|
|
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";
|
|
6
29
|
import {
|
|
7
30
|
LogicEngine,
|
|
8
31
|
PRAXIS_PROTOCOL_VERSION,
|
|
9
32
|
createPraxisEngine
|
|
10
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-JZDJU2DO.js";
|
|
34
|
+
import {
|
|
35
|
+
RuleResult,
|
|
36
|
+
fact
|
|
37
|
+
} from "./chunk-IG5BJ2MT.js";
|
|
11
38
|
import {
|
|
12
39
|
InMemoryPraxisDB,
|
|
13
40
|
PluresDBPraxisAdapter,
|
|
@@ -563,8 +590,8 @@ function defineFact(tag) {
|
|
|
563
590
|
create(payload) {
|
|
564
591
|
return { tag, payload };
|
|
565
592
|
},
|
|
566
|
-
is(
|
|
567
|
-
return
|
|
593
|
+
is(fact2) {
|
|
594
|
+
return fact2.tag === tag;
|
|
568
595
|
}
|
|
569
596
|
};
|
|
570
597
|
}
|
|
@@ -666,16 +693,16 @@ function validateSchema(schema) {
|
|
|
666
693
|
if (schema.logic) {
|
|
667
694
|
schema.logic.forEach((logic, logicIndex) => {
|
|
668
695
|
if (logic.facts) {
|
|
669
|
-
logic.facts.forEach((
|
|
670
|
-
if (!
|
|
696
|
+
logic.facts.forEach((fact2, factIndex) => {
|
|
697
|
+
if (!fact2.tag) {
|
|
671
698
|
errors.push({
|
|
672
699
|
path: `logic[${logicIndex}].facts[${factIndex}].tag`,
|
|
673
700
|
message: "Fact tag is required"
|
|
674
701
|
});
|
|
675
|
-
} else if (!isValidIdentifier(
|
|
702
|
+
} else if (!isValidIdentifier(fact2.tag)) {
|
|
676
703
|
errors.push({
|
|
677
704
|
path: `logic[${logicIndex}].facts[${factIndex}].tag`,
|
|
678
|
-
message: `Fact tag "${
|
|
705
|
+
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
706
|
});
|
|
680
707
|
}
|
|
681
708
|
});
|
|
@@ -1002,37 +1029,37 @@ var PraxisDBStore = class {
|
|
|
1002
1029
|
* @param fact The fact to store
|
|
1003
1030
|
* @returns Promise that resolves when the fact is stored
|
|
1004
1031
|
*/
|
|
1005
|
-
async storeFact(
|
|
1006
|
-
const constraintResult = await this.checkConstraints([
|
|
1032
|
+
async storeFact(fact2) {
|
|
1033
|
+
const constraintResult = await this.checkConstraints([fact2]);
|
|
1007
1034
|
if (!constraintResult.valid) {
|
|
1008
1035
|
throw new Error(`Constraint violation: ${constraintResult.errors.join(", ")}`);
|
|
1009
1036
|
}
|
|
1010
1037
|
let before;
|
|
1011
1038
|
if (this.chronicle) {
|
|
1012
|
-
const payload =
|
|
1039
|
+
const payload = fact2.payload;
|
|
1013
1040
|
const id = payload?.id;
|
|
1014
1041
|
if (id) {
|
|
1015
|
-
before = await this.getFact(
|
|
1042
|
+
before = await this.getFact(fact2.tag, id);
|
|
1016
1043
|
}
|
|
1017
1044
|
}
|
|
1018
|
-
await this.persistFact(
|
|
1045
|
+
await this.persistFact(fact2);
|
|
1019
1046
|
if (this.chronicle) {
|
|
1020
|
-
const payload =
|
|
1047
|
+
const payload = fact2.payload;
|
|
1021
1048
|
const id = payload?.id ?? "";
|
|
1022
1049
|
const span = ChronicleContext.current;
|
|
1023
1050
|
try {
|
|
1024
1051
|
await this.chronicle.record({
|
|
1025
|
-
path: getFactPath(
|
|
1052
|
+
path: getFactPath(fact2.tag, id),
|
|
1026
1053
|
before,
|
|
1027
|
-
after:
|
|
1054
|
+
after: fact2,
|
|
1028
1055
|
cause: span?.spanId,
|
|
1029
1056
|
context: span?.contextId,
|
|
1030
|
-
metadata: { factTag:
|
|
1057
|
+
metadata: { factTag: fact2.tag, operation: "storeFact" }
|
|
1031
1058
|
});
|
|
1032
1059
|
} catch {
|
|
1033
1060
|
}
|
|
1034
1061
|
}
|
|
1035
|
-
await this.triggerRules([
|
|
1062
|
+
await this.triggerRules([fact2]);
|
|
1036
1063
|
}
|
|
1037
1064
|
/**
|
|
1038
1065
|
* Store multiple facts in PluresDB
|
|
@@ -1044,28 +1071,28 @@ var PraxisDBStore = class {
|
|
|
1044
1071
|
if (!constraintResult.valid) {
|
|
1045
1072
|
throw new Error(`Constraint violation: ${constraintResult.errors.join(", ")}`);
|
|
1046
1073
|
}
|
|
1047
|
-
for (const
|
|
1074
|
+
for (const fact2 of facts) {
|
|
1048
1075
|
let before;
|
|
1049
1076
|
if (this.chronicle) {
|
|
1050
|
-
const payload =
|
|
1077
|
+
const payload = fact2.payload;
|
|
1051
1078
|
const id = payload?.id;
|
|
1052
1079
|
if (id) {
|
|
1053
|
-
before = await this.getFact(
|
|
1080
|
+
before = await this.getFact(fact2.tag, id);
|
|
1054
1081
|
}
|
|
1055
1082
|
}
|
|
1056
|
-
await this.persistFact(
|
|
1083
|
+
await this.persistFact(fact2);
|
|
1057
1084
|
if (this.chronicle) {
|
|
1058
|
-
const payload =
|
|
1085
|
+
const payload = fact2.payload;
|
|
1059
1086
|
const id = payload?.id ?? "";
|
|
1060
1087
|
const span = ChronicleContext.current;
|
|
1061
1088
|
try {
|
|
1062
1089
|
await this.chronicle.record({
|
|
1063
|
-
path: getFactPath(
|
|
1090
|
+
path: getFactPath(fact2.tag, id),
|
|
1064
1091
|
before,
|
|
1065
|
-
after:
|
|
1092
|
+
after: fact2,
|
|
1066
1093
|
cause: span?.spanId,
|
|
1067
1094
|
context: span?.contextId,
|
|
1068
|
-
metadata: { factTag:
|
|
1095
|
+
metadata: { factTag: fact2.tag, operation: "storeFacts" }
|
|
1069
1096
|
});
|
|
1070
1097
|
} catch {
|
|
1071
1098
|
}
|
|
@@ -1077,11 +1104,11 @@ var PraxisDBStore = class {
|
|
|
1077
1104
|
* Internal method to persist a fact without constraint checking
|
|
1078
1105
|
* Used by both storeFact and derived fact storage
|
|
1079
1106
|
*/
|
|
1080
|
-
async persistFact(
|
|
1081
|
-
const payload =
|
|
1107
|
+
async persistFact(fact2) {
|
|
1108
|
+
const payload = fact2.payload;
|
|
1082
1109
|
const id = payload?.id ?? generateId();
|
|
1083
|
-
const path = getFactPath(
|
|
1084
|
-
await this.db.set(path,
|
|
1110
|
+
const path = getFactPath(fact2.tag, id);
|
|
1111
|
+
await this.db.set(path, fact2);
|
|
1085
1112
|
}
|
|
1086
1113
|
/**
|
|
1087
1114
|
* Get a fact by tag and id
|
|
@@ -1218,8 +1245,8 @@ var PraxisDBStore = class {
|
|
|
1218
1245
|
if (watchers) {
|
|
1219
1246
|
watchers.add(callback);
|
|
1220
1247
|
}
|
|
1221
|
-
const unsubscribe = this.db.watch(path, (
|
|
1222
|
-
callback([
|
|
1248
|
+
const unsubscribe = this.db.watch(path, (fact2) => {
|
|
1249
|
+
callback([fact2]);
|
|
1223
1250
|
});
|
|
1224
1251
|
this.subscriptions.push(unsubscribe);
|
|
1225
1252
|
return () => {
|
|
@@ -1296,19 +1323,19 @@ var PraxisDBStore = class {
|
|
|
1296
1323
|
if (derivedFacts.length > 0) {
|
|
1297
1324
|
const constraintResult = await this.checkConstraints(derivedFacts);
|
|
1298
1325
|
if (constraintResult.valid) {
|
|
1299
|
-
for (const
|
|
1300
|
-
await this.persistFact(
|
|
1326
|
+
for (const fact2 of derivedFacts) {
|
|
1327
|
+
await this.persistFact(fact2);
|
|
1301
1328
|
if (this.chronicle) {
|
|
1302
|
-
const payload =
|
|
1329
|
+
const payload = fact2.payload;
|
|
1303
1330
|
const id = payload?.id ?? "";
|
|
1304
1331
|
const span = ChronicleContext.current;
|
|
1305
1332
|
try {
|
|
1306
1333
|
await this.chronicle.record({
|
|
1307
|
-
path: getFactPath(
|
|
1308
|
-
after:
|
|
1334
|
+
path: getFactPath(fact2.tag, id),
|
|
1335
|
+
after: fact2,
|
|
1309
1336
|
cause: span?.spanId,
|
|
1310
1337
|
context: span?.contextId,
|
|
1311
|
-
metadata: { factTag:
|
|
1338
|
+
metadata: { factTag: fact2.tag, operation: "derivedFact" }
|
|
1312
1339
|
});
|
|
1313
1340
|
} catch {
|
|
1314
1341
|
}
|
|
@@ -1768,7 +1795,7 @@ async function createUnumAdapter(config) {
|
|
|
1768
1795
|
type: "event"
|
|
1769
1796
|
});
|
|
1770
1797
|
}
|
|
1771
|
-
async function broadcastFact(channelId,
|
|
1798
|
+
async function broadcastFact(channelId, fact2) {
|
|
1772
1799
|
const channel = channels.get(channelId);
|
|
1773
1800
|
if (!channel) {
|
|
1774
1801
|
throw new Error(`Not joined to channel ${channelId}`);
|
|
@@ -1776,7 +1803,7 @@ async function createUnumAdapter(config) {
|
|
|
1776
1803
|
await channel.publish({
|
|
1777
1804
|
id: generateId2(),
|
|
1778
1805
|
sender: currentIdentity || { id: "anonymous", createdAt: Date.now() },
|
|
1779
|
-
content:
|
|
1806
|
+
content: fact2,
|
|
1780
1807
|
type: "fact"
|
|
1781
1808
|
});
|
|
1782
1809
|
}
|
|
@@ -1919,17 +1946,17 @@ function schemaToCanvas(schema, _options = {}) {
|
|
|
1919
1946
|
yOffset += schema.events.length * ySpacing + 30;
|
|
1920
1947
|
}
|
|
1921
1948
|
if (schema.facts) {
|
|
1922
|
-
schema.facts.forEach((
|
|
1923
|
-
const pos =
|
|
1949
|
+
schema.facts.forEach((fact2, index) => {
|
|
1950
|
+
const pos = fact2.position && (fact2.position.x !== 0 || fact2.position.y !== 0) ? fact2.position : { x: 50 + xSpacing * 3, y: yOffset + index * ySpacing };
|
|
1924
1951
|
const node = {
|
|
1925
|
-
id:
|
|
1952
|
+
id: fact2.id || `fact-${nodeId++}`,
|
|
1926
1953
|
type: "fact",
|
|
1927
|
-
label:
|
|
1954
|
+
label: fact2.tag,
|
|
1928
1955
|
x: pos.x,
|
|
1929
1956
|
y: pos.y,
|
|
1930
1957
|
width: 150,
|
|
1931
1958
|
height: 50,
|
|
1932
|
-
data:
|
|
1959
|
+
data: fact2,
|
|
1933
1960
|
style: {
|
|
1934
1961
|
backgroundColor: "#fce4ec",
|
|
1935
1962
|
borderColor: "#c2185b"
|
|
@@ -2384,8 +2411,8 @@ var StateDocsGenerator = class {
|
|
|
2384
2411
|
if (logic.facts && logic.facts.length > 0) {
|
|
2385
2412
|
lines.push("**Facts:**");
|
|
2386
2413
|
lines.push("");
|
|
2387
|
-
for (const
|
|
2388
|
-
lines.push(`- \`${
|
|
2414
|
+
for (const fact2 of logic.facts) {
|
|
2415
|
+
lines.push(`- \`${fact2.tag}\`: ${fact2.description || ""}`);
|
|
2389
2416
|
}
|
|
2390
2417
|
lines.push("");
|
|
2391
2418
|
}
|
|
@@ -2517,9 +2544,9 @@ var StateDocsGenerator = class {
|
|
|
2517
2544
|
lines.push("");
|
|
2518
2545
|
lines.push("| Fact | Description | Payload |");
|
|
2519
2546
|
lines.push("|------|-------------|---------|");
|
|
2520
|
-
for (const
|
|
2521
|
-
const payload =
|
|
2522
|
-
lines.push(`| \`${
|
|
2547
|
+
for (const fact2 of logic.facts) {
|
|
2548
|
+
const payload = fact2.payload ? Object.entries(fact2.payload).map(([k, v]) => `${k}: ${v}`).join(", ") : "-";
|
|
2549
|
+
lines.push(`| \`${fact2.tag}\` | ${fact2.description || "-"} | ${payload} |`);
|
|
2523
2550
|
}
|
|
2524
2551
|
lines.push("");
|
|
2525
2552
|
}
|
|
@@ -2571,8 +2598,8 @@ var StateDocsGenerator = class {
|
|
|
2571
2598
|
for (const event of logic.events) {
|
|
2572
2599
|
lines.push(` Processing --> ${event.tag.replace(/[^a-zA-Z0-9]/g, "")}: ${event.tag}`);
|
|
2573
2600
|
}
|
|
2574
|
-
for (const
|
|
2575
|
-
lines.push(` ${
|
|
2601
|
+
for (const fact2 of logic.facts) {
|
|
2602
|
+
lines.push(` ${fact2.tag.replace(/[^a-zA-Z0-9]/g, "")} --> [*]`);
|
|
2576
2603
|
}
|
|
2577
2604
|
}
|
|
2578
2605
|
return {
|
|
@@ -2894,7 +2921,7 @@ function generateTauriConfig(config) {
|
|
|
2894
2921
|
|
|
2895
2922
|
// src/integrations/unified.ts
|
|
2896
2923
|
async function createUnifiedApp(config) {
|
|
2897
|
-
const { createPraxisEngine: createPraxisEngine2 } = await import("./engine-
|
|
2924
|
+
const { createPraxisEngine: createPraxisEngine2 } = await import("./engine-3B5WJPGT.js");
|
|
2898
2925
|
const { createInMemoryDB: createInMemoryDB2 } = await import("./adapter-CIMBGDC7.js");
|
|
2899
2926
|
const db = config.db || createInMemoryDB2();
|
|
2900
2927
|
const pluresdb = createPluresDBAdapter({
|
|
@@ -3023,8 +3050,302 @@ async function attachAllIntegrations(engine, registry, options = {}) {
|
|
|
3023
3050
|
}
|
|
3024
3051
|
};
|
|
3025
3052
|
}
|
|
3053
|
+
|
|
3054
|
+
// src/core/ui-rules.ts
|
|
3055
|
+
var loadingGateRule = {
|
|
3056
|
+
id: "ui/loading-gate",
|
|
3057
|
+
description: "Signals when the app is in a loading state",
|
|
3058
|
+
eventTypes: ["ui.state-change", "app.init"],
|
|
3059
|
+
impl: (state) => {
|
|
3060
|
+
const ctx = state.context;
|
|
3061
|
+
if (ctx.loading) {
|
|
3062
|
+
return RuleResult.emit([fact("ui.loading-gate", { active: true })]);
|
|
3063
|
+
}
|
|
3064
|
+
return RuleResult.retract(["ui.loading-gate"], "Not loading");
|
|
3065
|
+
},
|
|
3066
|
+
contract: {
|
|
3067
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
3068
|
+
behavior: "Emits ui.loading-gate when context.loading is true, retracts when false",
|
|
3069
|
+
examples: [
|
|
3070
|
+
{ given: "loading is true", when: "ui state changes", then: "ui.loading-gate emitted" },
|
|
3071
|
+
{ given: "loading is false", when: "ui state changes", then: "ui.loading-gate retracted" }
|
|
3072
|
+
],
|
|
3073
|
+
invariants: ["Loading gate must reflect context.loading exactly"]
|
|
3074
|
+
}
|
|
3075
|
+
};
|
|
3076
|
+
var errorDisplayRule = {
|
|
3077
|
+
id: "ui/error-display",
|
|
3078
|
+
description: "Signals when an error should be displayed to the user",
|
|
3079
|
+
eventTypes: ["ui.state-change", "app.error"],
|
|
3080
|
+
impl: (state) => {
|
|
3081
|
+
const ctx = state.context;
|
|
3082
|
+
if (ctx.error) {
|
|
3083
|
+
return RuleResult.emit([fact("ui.error-display", { message: ctx.error, severity: "error" })]);
|
|
3084
|
+
}
|
|
3085
|
+
return RuleResult.retract(["ui.error-display"], "Error cleared");
|
|
3086
|
+
},
|
|
3087
|
+
contract: {
|
|
3088
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
3089
|
+
behavior: "Emits ui.error-display when context.error is non-null, retracts when cleared",
|
|
3090
|
+
examples: [
|
|
3091
|
+
{ given: "error is set", when: "ui state changes", then: "ui.error-display emitted with message" },
|
|
3092
|
+
{ given: "error is null", when: "ui state changes", then: "ui.error-display retracted" }
|
|
3093
|
+
],
|
|
3094
|
+
invariants: ["Error display must clear when error is null"]
|
|
3095
|
+
}
|
|
3096
|
+
};
|
|
3097
|
+
var offlineIndicatorRule = {
|
|
3098
|
+
id: "ui/offline-indicator",
|
|
3099
|
+
description: "Signals when the app is offline",
|
|
3100
|
+
eventTypes: ["ui.state-change", "network.change"],
|
|
3101
|
+
impl: (state) => {
|
|
3102
|
+
if (state.context.offline) {
|
|
3103
|
+
return RuleResult.emit([fact("ui.offline", { message: "You are offline. Changes will sync when reconnected." })]);
|
|
3104
|
+
}
|
|
3105
|
+
return RuleResult.retract(["ui.offline"], "Back online");
|
|
3106
|
+
},
|
|
3107
|
+
contract: {
|
|
3108
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
3109
|
+
behavior: "Emits ui.offline when context.offline is true, retracts when back online",
|
|
3110
|
+
examples: [
|
|
3111
|
+
{ given: "offline is true", when: "network changes", then: "ui.offline emitted" },
|
|
3112
|
+
{ given: "offline is false", when: "network changes", then: "ui.offline retracted" }
|
|
3113
|
+
],
|
|
3114
|
+
invariants: ["Offline indicator must match actual connectivity"]
|
|
3115
|
+
}
|
|
3116
|
+
};
|
|
3117
|
+
var dirtyGuardRule = {
|
|
3118
|
+
id: "ui/dirty-guard",
|
|
3119
|
+
description: "Warns when there are unsaved changes",
|
|
3120
|
+
eventTypes: ["ui.state-change", "navigation.request"],
|
|
3121
|
+
impl: (state) => {
|
|
3122
|
+
if (state.context.dirty) {
|
|
3123
|
+
return RuleResult.emit([fact("ui.unsaved-warning", {
|
|
3124
|
+
message: "You have unsaved changes",
|
|
3125
|
+
blocking: true
|
|
3126
|
+
})]);
|
|
3127
|
+
}
|
|
3128
|
+
return RuleResult.retract(["ui.unsaved-warning"], "No unsaved changes");
|
|
3129
|
+
},
|
|
3130
|
+
contract: {
|
|
3131
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
3132
|
+
behavior: "Emits ui.unsaved-warning when context.dirty is true, retracts when saved",
|
|
3133
|
+
examples: [
|
|
3134
|
+
{ given: "dirty is true", when: "ui state changes", then: "ui.unsaved-warning emitted with blocking=true" },
|
|
3135
|
+
{ given: "dirty is false", when: "ui state changes", then: "ui.unsaved-warning retracted" }
|
|
3136
|
+
],
|
|
3137
|
+
invariants: ["Dirty guard must clear after save"]
|
|
3138
|
+
}
|
|
3139
|
+
};
|
|
3140
|
+
var initGateRule = {
|
|
3141
|
+
id: "ui/init-gate",
|
|
3142
|
+
description: "Signals whether the app has completed initialization",
|
|
3143
|
+
eventTypes: ["ui.state-change", "app.init"],
|
|
3144
|
+
impl: (state) => {
|
|
3145
|
+
if (!state.context.initialized) {
|
|
3146
|
+
return RuleResult.emit([fact("ui.init-pending", {
|
|
3147
|
+
message: "App is initializing..."
|
|
3148
|
+
})]);
|
|
3149
|
+
}
|
|
3150
|
+
return RuleResult.retract(["ui.init-pending"], "App initialized");
|
|
3151
|
+
},
|
|
3152
|
+
contract: {
|
|
3153
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
3154
|
+
behavior: "Emits ui.init-pending until context.initialized is true",
|
|
3155
|
+
examples: [
|
|
3156
|
+
{ given: "initialized is false", when: "app starts", then: "ui.init-pending emitted" },
|
|
3157
|
+
{ given: "initialized is true", when: "init completes", then: "ui.init-pending retracted" }
|
|
3158
|
+
],
|
|
3159
|
+
invariants: ["Init gate must clear exactly once, when initialization completes"]
|
|
3160
|
+
}
|
|
3161
|
+
};
|
|
3162
|
+
var viewportRule = {
|
|
3163
|
+
id: "ui/viewport-class",
|
|
3164
|
+
description: "Classifies viewport size for responsive layout decisions",
|
|
3165
|
+
eventTypes: ["ui.state-change", "ui.resize"],
|
|
3166
|
+
impl: (state) => {
|
|
3167
|
+
const vp = state.context.viewport;
|
|
3168
|
+
if (!vp) return RuleResult.skip("No viewport data");
|
|
3169
|
+
return RuleResult.emit([fact("ui.viewport-class", {
|
|
3170
|
+
viewport: vp,
|
|
3171
|
+
compact: vp === "mobile",
|
|
3172
|
+
showSidebar: vp !== "mobile"
|
|
3173
|
+
})]);
|
|
3174
|
+
},
|
|
3175
|
+
contract: {
|
|
3176
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
3177
|
+
behavior: "Classifies viewport into responsive layout hints",
|
|
3178
|
+
examples: [
|
|
3179
|
+
{ given: "viewport is mobile", when: "resize event", then: "compact=true, showSidebar=false" },
|
|
3180
|
+
{ given: "viewport is desktop", when: "resize event", then: "compact=false, showSidebar=true" }
|
|
3181
|
+
],
|
|
3182
|
+
invariants: ["Viewport class must update on every resize event"]
|
|
3183
|
+
}
|
|
3184
|
+
};
|
|
3185
|
+
var noInteractionWhileLoadingConstraint = {
|
|
3186
|
+
id: "ui/no-interaction-while-loading",
|
|
3187
|
+
description: "Prevents data mutations while a load operation is in progress",
|
|
3188
|
+
impl: (state) => {
|
|
3189
|
+
if (state.context.loading) {
|
|
3190
|
+
return "Cannot perform action while data is loading";
|
|
3191
|
+
}
|
|
3192
|
+
return true;
|
|
3193
|
+
},
|
|
3194
|
+
contract: {
|
|
3195
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
3196
|
+
behavior: "Fails when context.loading is true",
|
|
3197
|
+
examples: [
|
|
3198
|
+
{ given: "loading is true", when: "action attempted", then: "violation" },
|
|
3199
|
+
{ given: "loading is false", when: "action attempted", then: "pass" }
|
|
3200
|
+
],
|
|
3201
|
+
invariants: ["Must always fail during loading"]
|
|
3202
|
+
}
|
|
3203
|
+
};
|
|
3204
|
+
var mustBeInitializedConstraint = {
|
|
3205
|
+
id: "ui/must-be-initialized",
|
|
3206
|
+
description: "Requires app initialization before user interactions",
|
|
3207
|
+
impl: (state) => {
|
|
3208
|
+
if (!state.context.initialized) {
|
|
3209
|
+
return "App must be initialized before performing this action";
|
|
3210
|
+
}
|
|
3211
|
+
return true;
|
|
3212
|
+
},
|
|
3213
|
+
contract: {
|
|
3214
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
3215
|
+
behavior: "Fails when context.initialized is false",
|
|
3216
|
+
examples: [
|
|
3217
|
+
{ given: "initialized is false", when: "action attempted", then: "violation" },
|
|
3218
|
+
{ given: "initialized is true", when: "action attempted", then: "pass" }
|
|
3219
|
+
],
|
|
3220
|
+
invariants: ["Must always fail before init completes"]
|
|
3221
|
+
}
|
|
3222
|
+
};
|
|
3223
|
+
var uiModule = {
|
|
3224
|
+
rules: [
|
|
3225
|
+
loadingGateRule,
|
|
3226
|
+
errorDisplayRule,
|
|
3227
|
+
offlineIndicatorRule,
|
|
3228
|
+
dirtyGuardRule,
|
|
3229
|
+
initGateRule,
|
|
3230
|
+
viewportRule
|
|
3231
|
+
],
|
|
3232
|
+
constraints: [
|
|
3233
|
+
noInteractionWhileLoadingConstraint,
|
|
3234
|
+
mustBeInitializedConstraint
|
|
3235
|
+
],
|
|
3236
|
+
meta: {
|
|
3237
|
+
name: "praxis-ui",
|
|
3238
|
+
version: "1.0.0",
|
|
3239
|
+
description: "Predefined UI rules and constraints \u2014 separate from business logic"
|
|
3240
|
+
}
|
|
3241
|
+
};
|
|
3242
|
+
function createUIModule(options) {
|
|
3243
|
+
const allRules = uiModule.rules;
|
|
3244
|
+
const allConstraints = uiModule.constraints;
|
|
3245
|
+
const selectedRules = options.rules ? allRules.filter((r) => options.rules.includes(r.id)) : allRules;
|
|
3246
|
+
const selectedConstraints = options.constraints ? allConstraints.filter((c) => options.constraints.includes(c.id)) : allConstraints;
|
|
3247
|
+
return {
|
|
3248
|
+
rules: [...selectedRules, ...options.extraRules ?? []],
|
|
3249
|
+
constraints: [...selectedConstraints, ...options.extraConstraints ?? []],
|
|
3250
|
+
meta: { ...uiModule.meta, customized: true }
|
|
3251
|
+
};
|
|
3252
|
+
}
|
|
3253
|
+
function uiStateChanged(changes) {
|
|
3254
|
+
return { tag: "ui.state-change", payload: changes ?? {} };
|
|
3255
|
+
}
|
|
3256
|
+
function navigationRequest(to) {
|
|
3257
|
+
return { tag: "navigation.request", payload: { to } };
|
|
3258
|
+
}
|
|
3259
|
+
function resizeEvent(width, height) {
|
|
3260
|
+
return { tag: "ui.resize", payload: { width, height } };
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
// src/core/completeness.ts
|
|
3264
|
+
function auditCompleteness(manifest, registryRuleIds, registryConstraintIds, rulesWithContracts, config) {
|
|
3265
|
+
const threshold = config?.threshold ?? 90;
|
|
3266
|
+
const domainBranches = manifest.branches.filter((b) => b.kind === "domain");
|
|
3267
|
+
const coveredDomain = domainBranches.filter((b) => b.coveredBy && registryRuleIds.includes(b.coveredBy));
|
|
3268
|
+
const uncoveredDomain = domainBranches.filter((b) => !b.coveredBy || !registryRuleIds.includes(b.coveredBy));
|
|
3269
|
+
const invariantBranches = manifest.branches.filter((b) => b.kind === "invariant");
|
|
3270
|
+
const coveredInvariants = invariantBranches.filter((b) => b.coveredBy && registryConstraintIds.includes(b.coveredBy));
|
|
3271
|
+
const uncoveredInvariants = invariantBranches.filter((b) => !b.coveredBy || !registryConstraintIds.includes(b.coveredBy));
|
|
3272
|
+
const needContracts = manifest.rulesNeedingContracts;
|
|
3273
|
+
const haveContracts = needContracts.filter((id) => rulesWithContracts.includes(id));
|
|
3274
|
+
const missingContracts = needContracts.filter((id) => !rulesWithContracts.includes(id));
|
|
3275
|
+
const neededFields = manifest.stateFields.filter((f) => f.usedByRule);
|
|
3276
|
+
const coveredFields = neededFields.filter((f) => f.inContext);
|
|
3277
|
+
const missingFields = neededFields.filter((f) => !f.inContext);
|
|
3278
|
+
const coveredTransitions = manifest.transitions.filter((t) => t.eventTag);
|
|
3279
|
+
const missingTransitions = manifest.transitions.filter((t) => !t.eventTag);
|
|
3280
|
+
const ruleScore = domainBranches.length > 0 ? coveredDomain.length / domainBranches.length * 40 : 40;
|
|
3281
|
+
const constraintScore = invariantBranches.length > 0 ? coveredInvariants.length / invariantBranches.length * 20 : 20;
|
|
3282
|
+
const contractScore = needContracts.length > 0 ? haveContracts.length / needContracts.length * 15 : 15;
|
|
3283
|
+
const contextScore = neededFields.length > 0 ? coveredFields.length / neededFields.length * 15 : 15;
|
|
3284
|
+
const eventScore = manifest.transitions.length > 0 ? coveredTransitions.length / manifest.transitions.length * 10 : 10;
|
|
3285
|
+
const score = Math.round(ruleScore + constraintScore + contractScore + contextScore + eventScore);
|
|
3286
|
+
const rating = score >= 90 ? "complete" : score >= 70 ? "good" : score >= 50 ? "partial" : "incomplete";
|
|
3287
|
+
const report = {
|
|
3288
|
+
score,
|
|
3289
|
+
rating,
|
|
3290
|
+
rules: { total: domainBranches.length, covered: coveredDomain.length, uncovered: uncoveredDomain },
|
|
3291
|
+
constraints: { total: invariantBranches.length, covered: coveredInvariants.length, uncovered: uncoveredInvariants },
|
|
3292
|
+
contracts: { total: needContracts.length, withContracts: haveContracts.length, missing: missingContracts },
|
|
3293
|
+
context: { total: neededFields.length, covered: coveredFields.length, missing: missingFields },
|
|
3294
|
+
events: { total: manifest.transitions.length, covered: coveredTransitions.length, missing: missingTransitions }
|
|
3295
|
+
};
|
|
3296
|
+
if (config?.strict && score < threshold) {
|
|
3297
|
+
throw new Error(`Praxis completeness ${score}/100 (${rating}) \u2014 below threshold ${threshold}. ${uncoveredDomain.length} uncovered rules, ${uncoveredInvariants.length} uncovered invariants, ${missingContracts.length} missing contracts.`);
|
|
3298
|
+
}
|
|
3299
|
+
return report;
|
|
3300
|
+
}
|
|
3301
|
+
function formatReport(report) {
|
|
3302
|
+
const lines = [];
|
|
3303
|
+
const icon = report.rating === "complete" ? "\u2705" : report.rating === "good" ? "\u{1F7E2}" : report.rating === "partial" ? "\u{1F7E1}" : "\u{1F534}";
|
|
3304
|
+
lines.push(`${icon} Praxis Completeness: ${report.score}/100 (${report.rating})`);
|
|
3305
|
+
lines.push("");
|
|
3306
|
+
lines.push(`Rules: ${report.rules.covered}/${report.rules.total} domain branches covered (${pct(report.rules.covered, report.rules.total)})`);
|
|
3307
|
+
lines.push(`Constraints: ${report.constraints.covered}/${report.constraints.total} invariants covered (${pct(report.constraints.covered, report.constraints.total)})`);
|
|
3308
|
+
lines.push(`Contracts: ${report.contracts.withContracts}/${report.contracts.total} rules have contracts (${pct(report.contracts.withContracts, report.contracts.total)})`);
|
|
3309
|
+
lines.push(`Context: ${report.context.covered}/${report.context.total} state fields in context (${pct(report.context.covered, report.context.total)})`);
|
|
3310
|
+
lines.push(`Events: ${report.events.covered}/${report.events.total} transitions have events (${pct(report.events.covered, report.events.total)})`);
|
|
3311
|
+
if (report.rules.uncovered.length > 0) {
|
|
3312
|
+
lines.push("");
|
|
3313
|
+
lines.push("Uncovered domain logic:");
|
|
3314
|
+
for (const b of report.rules.uncovered) {
|
|
3315
|
+
lines.push(` \u274C ${b.location}: ${b.condition}${b.note ? ` \u2014 ${b.note}` : ""}`);
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
if (report.constraints.uncovered.length > 0) {
|
|
3319
|
+
lines.push("");
|
|
3320
|
+
lines.push("Uncovered invariants:");
|
|
3321
|
+
for (const b of report.constraints.uncovered) {
|
|
3322
|
+
lines.push(` \u274C ${b.location}: ${b.condition}${b.note ? ` \u2014 ${b.note}` : ""}`);
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
if (report.contracts.missing.length > 0) {
|
|
3326
|
+
lines.push("");
|
|
3327
|
+
lines.push("Rules missing contracts:");
|
|
3328
|
+
for (const id of report.contracts.missing) {
|
|
3329
|
+
lines.push(` \u{1F4DD} ${id}`);
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
if (report.events.missing.length > 0) {
|
|
3333
|
+
lines.push("");
|
|
3334
|
+
lines.push("State transitions without events:");
|
|
3335
|
+
for (const t of report.events.missing) {
|
|
3336
|
+
lines.push(` \u26A1 ${t.location}: ${t.description}`);
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
return lines.join("\n");
|
|
3340
|
+
}
|
|
3341
|
+
function pct(a, b) {
|
|
3342
|
+
if (b === 0) return "100%";
|
|
3343
|
+
return Math.round(a / b * 100) + "%";
|
|
3344
|
+
}
|
|
3026
3345
|
export {
|
|
3027
3346
|
ActorManager,
|
|
3347
|
+
Expectation,
|
|
3348
|
+
ExpectationSet,
|
|
3028
3349
|
ReactiveLogicEngine2 as FrameworkAgnosticReactiveEngine,
|
|
3029
3350
|
InMemoryPraxisDB,
|
|
3030
3351
|
LogicEngine,
|
|
@@ -3037,14 +3358,18 @@ export {
|
|
|
3037
3358
|
PraxisSchemaRegistry,
|
|
3038
3359
|
ReactiveLogicEngine,
|
|
3039
3360
|
RegistryIntrospector,
|
|
3361
|
+
RuleResult,
|
|
3040
3362
|
StateDocsGenerator,
|
|
3041
3363
|
attachAllIntegrations,
|
|
3042
3364
|
attachTauriToEngine,
|
|
3043
3365
|
attachToEngine,
|
|
3044
3366
|
attachUnumToEngine,
|
|
3367
|
+
auditCompleteness,
|
|
3368
|
+
branchRules,
|
|
3045
3369
|
canvasToMermaid,
|
|
3046
3370
|
canvasToSchema,
|
|
3047
3371
|
canvasToYaml,
|
|
3372
|
+
commitFromState,
|
|
3048
3373
|
createCanvasEditor,
|
|
3049
3374
|
createReactiveEngine2 as createFrameworkAgnosticReactiveEngine,
|
|
3050
3375
|
createInMemoryDB,
|
|
@@ -3063,28 +3388,56 @@ export {
|
|
|
3063
3388
|
createStateDocsGenerator,
|
|
3064
3389
|
createTauriPraxisAdapter,
|
|
3065
3390
|
createTimerActor,
|
|
3391
|
+
createUIModule,
|
|
3066
3392
|
createUnifiedApp,
|
|
3067
3393
|
createUnumAdapter,
|
|
3394
|
+
dataRules,
|
|
3068
3395
|
defineConstraint,
|
|
3069
3396
|
defineEvent,
|
|
3070
3397
|
defineFact,
|
|
3398
|
+
defineGate,
|
|
3071
3399
|
defineModule,
|
|
3072
3400
|
defineRule,
|
|
3401
|
+
dirtyGuardRule,
|
|
3402
|
+
errorDisplayRule,
|
|
3403
|
+
expectBehavior,
|
|
3404
|
+
expectationGate,
|
|
3405
|
+
fact,
|
|
3073
3406
|
filterEvents,
|
|
3074
3407
|
filterFacts,
|
|
3075
3408
|
findEvent,
|
|
3076
3409
|
findFact,
|
|
3410
|
+
formRules,
|
|
3411
|
+
formatGate,
|
|
3412
|
+
formatReport,
|
|
3413
|
+
formatVerificationReport,
|
|
3077
3414
|
generateDocs,
|
|
3078
3415
|
generateId,
|
|
3079
3416
|
generateTauriConfig,
|
|
3080
3417
|
getEventPath,
|
|
3081
3418
|
getFactPath,
|
|
3082
3419
|
getSchemaPath,
|
|
3420
|
+
initGateRule,
|
|
3421
|
+
inputRules,
|
|
3422
|
+
lintGate,
|
|
3083
3423
|
loadSchemaFromJson,
|
|
3084
3424
|
loadSchemaFromYaml,
|
|
3425
|
+
loadingGateRule,
|
|
3426
|
+
mustBeInitializedConstraint,
|
|
3427
|
+
navigationRequest,
|
|
3428
|
+
navigationRules,
|
|
3429
|
+
noInteractionWhileLoadingConstraint,
|
|
3430
|
+
offlineIndicatorRule,
|
|
3085
3431
|
registerSchema,
|
|
3432
|
+
resizeEvent,
|
|
3086
3433
|
schemaToCanvas,
|
|
3434
|
+
semverContract,
|
|
3435
|
+
toastRules,
|
|
3436
|
+
uiModule,
|
|
3437
|
+
uiStateChanged,
|
|
3087
3438
|
validateForGeneration,
|
|
3088
3439
|
validateSchema,
|
|
3089
|
-
validateWithGuardian
|
|
3440
|
+
validateWithGuardian,
|
|
3441
|
+
verify,
|
|
3442
|
+
viewportRule
|
|
3090
3443
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { L as LogicEngine } from '../reactive-engine.svelte-DgVTqHLc.js';
|
|
2
|
+
export { R as ReactiveEngineOptions, a as ReactiveLogicEngine, c as createReactiveEngine } from '../reactive-engine.svelte-DgVTqHLc.js';
|
|
3
|
+
import { P as PraxisState, a as PraxisEvent } from '../rules-i1LHpnGd.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Svelte v5 Integration
|