@plures/praxis 1.2.41 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/{chunk-BBP2F7TT.js → chunk-MJK3IYTJ.js} +123 -5
- package/dist/browser/{chunk-FCEH7WMH.js → chunk-N63K4KWS.js} +1 -1
- package/dist/browser/{engine-65QDGCAN.js → engine-YIEGSX7U.js} +1 -1
- package/dist/browser/index.d.ts +2 -2
- package/dist/browser/index.js +10 -5
- package/dist/browser/integrations/svelte.d.ts +2 -2
- package/dist/browser/integrations/svelte.js +2 -2
- package/dist/browser/{reactive-engine.svelte-Cqd8Mod2.d.ts → reactive-engine.svelte-DjynI82A.d.ts} +83 -4
- package/dist/node/{chunk-32YFEEML.js → chunk-5JQJZADT.js} +1 -1
- package/dist/node/{chunk-BBP2F7TT.js → chunk-KMJWAFZV.js} +128 -5
- package/dist/node/cloud/index.d.cts +1 -1
- package/dist/node/cloud/index.d.ts +1 -1
- package/dist/node/{engine-7CXQV6RC.js → engine-FEN5IYZ5.js} +1 -1
- package/dist/node/index.cjs +522 -59
- package/dist/node/index.d.cts +271 -5
- package/dist/node/index.d.ts +271 -5
- package/dist/node/index.js +355 -39
- package/dist/node/integrations/svelte.cjs +123 -5
- package/dist/node/integrations/svelte.d.cts +3 -3
- package/dist/node/integrations/svelte.d.ts +3 -3
- package/dist/node/integrations/svelte.js +2 -2
- package/dist/node/{protocol-BocKczNv.d.ts → protocol-DcyGMmWY.d.cts} +7 -0
- package/dist/node/{protocol-BocKczNv.d.cts → protocol-DcyGMmWY.d.ts} +7 -0
- package/dist/node/{reactive-engine.svelte-D-xTDxT5.d.ts → reactive-engine.svelte-Cg0Yc2Hs.d.cts} +90 -6
- package/dist/node/{reactive-engine.svelte-CGe8SpVE.d.cts → reactive-engine.svelte-DekxqFu0.d.ts} +90 -6
- package/package.json +2 -2
- package/src/__tests__/engine-v2.test.ts +532 -0
- package/src/core/completeness.ts +274 -0
- package/src/core/engine.ts +47 -5
- package/src/core/pluresdb/store.ts +9 -3
- package/src/core/protocol.ts +7 -0
- package/src/core/rule-result.ts +130 -0
- package/src/core/rules.ts +12 -5
- package/src/core/ui-rules.ts +340 -0
- package/src/index.ts +27 -0
- package/src/vite/completeness-plugin.ts +72 -0
package/dist/node/index.cjs
CHANGED
|
@@ -39,6 +39,99 @@ var init_protocol = __esm({
|
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
// src/core/rule-result.ts
|
|
43
|
+
function fact(tag, payload) {
|
|
44
|
+
return { tag, payload };
|
|
45
|
+
}
|
|
46
|
+
var RuleResult;
|
|
47
|
+
var init_rule_result = __esm({
|
|
48
|
+
"src/core/rule-result.ts"() {
|
|
49
|
+
"use strict";
|
|
50
|
+
RuleResult = class _RuleResult {
|
|
51
|
+
/** The kind of result */
|
|
52
|
+
kind;
|
|
53
|
+
/** Facts produced (only for 'emit') */
|
|
54
|
+
facts;
|
|
55
|
+
/** Fact tags to retract (only for 'retract') */
|
|
56
|
+
retractTags;
|
|
57
|
+
/** Optional reason (for noop/skip/retract — useful for debugging) */
|
|
58
|
+
reason;
|
|
59
|
+
/** The rule ID that produced this result (set by engine) */
|
|
60
|
+
ruleId;
|
|
61
|
+
constructor(kind, facts, retractTags, reason) {
|
|
62
|
+
this.kind = kind;
|
|
63
|
+
this.facts = facts;
|
|
64
|
+
this.retractTags = retractTags;
|
|
65
|
+
this.reason = reason;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Rule produced facts.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* return RuleResult.emit([
|
|
72
|
+
* { tag: 'sprint.behind', payload: { deficit: 5 } }
|
|
73
|
+
* ]);
|
|
74
|
+
*/
|
|
75
|
+
static emit(facts) {
|
|
76
|
+
if (facts.length === 0) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"RuleResult.emit() requires at least one fact. Use RuleResult.noop() or RuleResult.skip() when a rule has nothing to say."
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return new _RuleResult("emit", facts, []);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Rule evaluated but had nothing to report.
|
|
85
|
+
* Unlike returning [], this is explicit and traceable.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* if (ctx.completedHours >= expectedHours) {
|
|
89
|
+
* return RuleResult.noop('Sprint is on pace');
|
|
90
|
+
* }
|
|
91
|
+
*/
|
|
92
|
+
static noop(reason) {
|
|
93
|
+
return new _RuleResult("noop", [], [], reason);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Rule decided to skip because preconditions were not met.
|
|
97
|
+
* Distinct from noop: skip means "I can't evaluate", noop means "I evaluated and found nothing".
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* if (!ctx.sprintName) {
|
|
101
|
+
* return RuleResult.skip('No active sprint');
|
|
102
|
+
* }
|
|
103
|
+
*/
|
|
104
|
+
static skip(reason) {
|
|
105
|
+
return new _RuleResult("skip", [], [], reason);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Rule retracts previously emitted facts by tag.
|
|
109
|
+
* Used when a condition that previously produced facts is no longer true.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* // Sprint was behind, but caught up
|
|
113
|
+
* if (ctx.completedHours >= expectedHours) {
|
|
114
|
+
* return RuleResult.retract(['sprint.behind'], 'Sprint caught up');
|
|
115
|
+
* }
|
|
116
|
+
*/
|
|
117
|
+
static retract(tags, reason) {
|
|
118
|
+
if (tags.length === 0) {
|
|
119
|
+
throw new Error("RuleResult.retract() requires at least one tag.");
|
|
120
|
+
}
|
|
121
|
+
return new _RuleResult("retract", [], tags, reason);
|
|
122
|
+
}
|
|
123
|
+
/** Whether this result produced facts */
|
|
124
|
+
get hasFacts() {
|
|
125
|
+
return this.facts.length > 0;
|
|
126
|
+
}
|
|
127
|
+
/** Whether this result retracts facts */
|
|
128
|
+
get hasRetractions() {
|
|
129
|
+
return this.retractTags.length > 0;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
42
135
|
// src/core/engine.ts
|
|
43
136
|
var engine_exports = {};
|
|
44
137
|
__export(engine_exports, {
|
|
@@ -68,6 +161,7 @@ var init_engine = __esm({
|
|
|
68
161
|
"src/core/engine.ts"() {
|
|
69
162
|
"use strict";
|
|
70
163
|
init_protocol();
|
|
164
|
+
init_rule_result();
|
|
71
165
|
LogicEngine = class {
|
|
72
166
|
state;
|
|
73
167
|
registry;
|
|
@@ -131,7 +225,13 @@ var init_engine = __esm({
|
|
|
131
225
|
stepWithConfig(events, config) {
|
|
132
226
|
const diagnostics = [];
|
|
133
227
|
let newState = { ...this.state };
|
|
228
|
+
const stateWithEvents = {
|
|
229
|
+
...newState,
|
|
230
|
+
events
|
|
231
|
+
// current batch — rules can read state.events
|
|
232
|
+
};
|
|
134
233
|
const newFacts = [];
|
|
234
|
+
const retractions = [];
|
|
135
235
|
const eventTags = new Set(events.map((e) => e.tag));
|
|
136
236
|
for (const ruleId of config.ruleIds) {
|
|
137
237
|
const rule = this.registry.getRule(ruleId);
|
|
@@ -150,8 +250,31 @@ var init_engine = __esm({
|
|
|
150
250
|
}
|
|
151
251
|
}
|
|
152
252
|
try {
|
|
153
|
-
const
|
|
154
|
-
|
|
253
|
+
const rawResult = rule.impl(stateWithEvents, events);
|
|
254
|
+
if (rawResult instanceof RuleResult) {
|
|
255
|
+
rawResult.ruleId = ruleId;
|
|
256
|
+
switch (rawResult.kind) {
|
|
257
|
+
case "emit":
|
|
258
|
+
newFacts.push(...rawResult.facts);
|
|
259
|
+
break;
|
|
260
|
+
case "retract":
|
|
261
|
+
retractions.push(...rawResult.retractTags);
|
|
262
|
+
break;
|
|
263
|
+
case "noop":
|
|
264
|
+
case "skip":
|
|
265
|
+
if (rawResult.reason) {
|
|
266
|
+
diagnostics.push({
|
|
267
|
+
kind: "rule-error",
|
|
268
|
+
// reused kind — could add 'rule-trace' in protocol v2
|
|
269
|
+
message: `[${rawResult.kind}] ${ruleId}: ${rawResult.reason}`,
|
|
270
|
+
data: { ruleId, resultKind: rawResult.kind, reason: rawResult.reason }
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
} else if (Array.isArray(rawResult)) {
|
|
276
|
+
newFacts.push(...rawResult);
|
|
277
|
+
}
|
|
155
278
|
} catch (error) {
|
|
156
279
|
diagnostics.push({
|
|
157
280
|
kind: "rule-error",
|
|
@@ -160,21 +283,26 @@ var init_engine = __esm({
|
|
|
160
283
|
});
|
|
161
284
|
}
|
|
162
285
|
}
|
|
286
|
+
let existingFacts = newState.facts;
|
|
287
|
+
if (retractions.length > 0) {
|
|
288
|
+
const retractSet = new Set(retractions);
|
|
289
|
+
existingFacts = existingFacts.filter((f) => !retractSet.has(f.tag));
|
|
290
|
+
}
|
|
163
291
|
let mergedFacts;
|
|
164
292
|
switch (this.factDedup) {
|
|
165
293
|
case "last-write-wins": {
|
|
166
294
|
const factMap = /* @__PURE__ */ new Map();
|
|
167
|
-
for (const f of
|
|
295
|
+
for (const f of existingFacts) factMap.set(f.tag, f);
|
|
168
296
|
for (const f of newFacts) factMap.set(f.tag, f);
|
|
169
297
|
mergedFacts = Array.from(factMap.values());
|
|
170
298
|
break;
|
|
171
299
|
}
|
|
172
300
|
case "append":
|
|
173
|
-
mergedFacts = [...
|
|
301
|
+
mergedFacts = [...existingFacts, ...newFacts];
|
|
174
302
|
break;
|
|
175
303
|
case "none":
|
|
176
304
|
default:
|
|
177
|
-
mergedFacts = [...
|
|
305
|
+
mergedFacts = [...existingFacts, ...newFacts];
|
|
178
306
|
break;
|
|
179
307
|
}
|
|
180
308
|
if (this.maxFacts > 0 && mergedFacts.length > this.maxFacts) {
|
|
@@ -494,6 +622,7 @@ __export(src_exports, {
|
|
|
494
622
|
PraxisSchemaRegistry: () => PraxisSchemaRegistry,
|
|
495
623
|
ReactiveLogicEngine: () => ReactiveLogicEngine,
|
|
496
624
|
RegistryIntrospector: () => RegistryIntrospector,
|
|
625
|
+
RuleResult: () => RuleResult,
|
|
497
626
|
StateDocsGenerator: () => StateDocsGenerator,
|
|
498
627
|
TerminalAdapter: () => TerminalAdapter,
|
|
499
628
|
ValidateContracts: () => ValidateContracts,
|
|
@@ -501,6 +630,7 @@ __export(src_exports, {
|
|
|
501
630
|
attachTauriToEngine: () => attachTauriToEngine,
|
|
502
631
|
attachToEngine: () => attachToEngine,
|
|
503
632
|
attachUnumToEngine: () => attachUnumToEngine,
|
|
633
|
+
auditCompleteness: () => auditCompleteness,
|
|
504
634
|
canvasToMermaid: () => canvasToMermaid,
|
|
505
635
|
canvasToSchema: () => canvasToSchema,
|
|
506
636
|
canvasToYaml: () => canvasToYaml,
|
|
@@ -526,6 +656,7 @@ __export(src_exports, {
|
|
|
526
656
|
createTauriPraxisAdapter: () => createTauriPraxisAdapter,
|
|
527
657
|
createTerminalAdapter: () => createTerminalAdapter,
|
|
528
658
|
createTimerActor: () => createTimerActor,
|
|
659
|
+
createUIModule: () => createUIModule,
|
|
529
660
|
createUnifiedApp: () => createUnifiedApp,
|
|
530
661
|
createUnumAdapter: () => createUnumAdapter,
|
|
531
662
|
defineConstraint: () => defineConstraint,
|
|
@@ -534,10 +665,14 @@ __export(src_exports, {
|
|
|
534
665
|
defineFact: () => defineFact,
|
|
535
666
|
defineModule: () => defineModule,
|
|
536
667
|
defineRule: () => defineRule,
|
|
668
|
+
dirtyGuardRule: () => dirtyGuardRule,
|
|
669
|
+
errorDisplayRule: () => errorDisplayRule,
|
|
670
|
+
fact: () => fact,
|
|
537
671
|
filterEvents: () => filterEvents,
|
|
538
672
|
filterFacts: () => filterFacts,
|
|
539
673
|
findEvent: () => findEvent,
|
|
540
674
|
findFact: () => findFact,
|
|
675
|
+
formatReport: () => formatReport,
|
|
541
676
|
formatValidationReport: () => formatValidationReport,
|
|
542
677
|
formatValidationReportJSON: () => formatValidationReportJSON,
|
|
543
678
|
formatValidationReportSARIF: () => formatValidationReportSARIF,
|
|
@@ -548,18 +683,28 @@ __export(src_exports, {
|
|
|
548
683
|
getEventPath: () => getEventPath,
|
|
549
684
|
getFactPath: () => getFactPath,
|
|
550
685
|
getSchemaPath: () => getSchemaPath,
|
|
686
|
+
initGateRule: () => initGateRule,
|
|
551
687
|
isContract: () => isContract,
|
|
552
688
|
loadSchema: () => loadSchema,
|
|
553
689
|
loadSchemaFromFile: () => loadSchemaFromFile,
|
|
554
690
|
loadSchemaFromJson: () => loadSchemaFromJson,
|
|
555
691
|
loadSchemaFromYaml: () => loadSchemaFromYaml,
|
|
692
|
+
loadingGateRule: () => loadingGateRule,
|
|
693
|
+
mustBeInitializedConstraint: () => mustBeInitializedConstraint,
|
|
694
|
+
navigationRequest: () => navigationRequest,
|
|
695
|
+
noInteractionWhileLoadingConstraint: () => noInteractionWhileLoadingConstraint,
|
|
696
|
+
offlineIndicatorRule: () => offlineIndicatorRule,
|
|
556
697
|
registerSchema: () => registerSchema,
|
|
698
|
+
resizeEvent: () => resizeEvent,
|
|
557
699
|
runTerminalCommand: () => runTerminalCommand,
|
|
558
700
|
schemaToCanvas: () => schemaToCanvas,
|
|
701
|
+
uiModule: () => uiModule,
|
|
702
|
+
uiStateChanged: () => uiStateChanged,
|
|
559
703
|
validateContracts: () => validateContracts,
|
|
560
704
|
validateForGeneration: () => validateForGeneration,
|
|
561
705
|
validateSchema: () => validateSchema,
|
|
562
|
-
validateWithGuardian: () => validateWithGuardian
|
|
706
|
+
validateWithGuardian: () => validateWithGuardian,
|
|
707
|
+
viewportRule: () => viewportRule
|
|
563
708
|
});
|
|
564
709
|
module.exports = __toCommonJS(src_exports);
|
|
565
710
|
init_protocol();
|
|
@@ -1336,8 +1481,8 @@ function defineFact(tag) {
|
|
|
1336
1481
|
create(payload) {
|
|
1337
1482
|
return { tag, payload };
|
|
1338
1483
|
},
|
|
1339
|
-
is(
|
|
1340
|
-
return
|
|
1484
|
+
is(fact2) {
|
|
1485
|
+
return fact2.tag === tag;
|
|
1341
1486
|
}
|
|
1342
1487
|
};
|
|
1343
1488
|
}
|
|
@@ -2090,16 +2235,16 @@ function validateSchema(schema) {
|
|
|
2090
2235
|
if (schema.logic) {
|
|
2091
2236
|
schema.logic.forEach((logic, logicIndex) => {
|
|
2092
2237
|
if (logic.facts) {
|
|
2093
|
-
logic.facts.forEach((
|
|
2094
|
-
if (!
|
|
2238
|
+
logic.facts.forEach((fact2, factIndex) => {
|
|
2239
|
+
if (!fact2.tag) {
|
|
2095
2240
|
errors.push({
|
|
2096
2241
|
path: `logic[${logicIndex}].facts[${factIndex}].tag`,
|
|
2097
2242
|
message: "Fact tag is required"
|
|
2098
2243
|
});
|
|
2099
|
-
} else if (!isValidIdentifier(
|
|
2244
|
+
} else if (!isValidIdentifier(fact2.tag)) {
|
|
2100
2245
|
errors.push({
|
|
2101
2246
|
path: `logic[${logicIndex}].facts[${factIndex}].tag`,
|
|
2102
|
-
message: `Fact tag "${
|
|
2247
|
+
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.`
|
|
2103
2248
|
});
|
|
2104
2249
|
}
|
|
2105
2250
|
});
|
|
@@ -2484,37 +2629,37 @@ var PraxisDBStore = class {
|
|
|
2484
2629
|
* @param fact The fact to store
|
|
2485
2630
|
* @returns Promise that resolves when the fact is stored
|
|
2486
2631
|
*/
|
|
2487
|
-
async storeFact(
|
|
2488
|
-
const constraintResult = await this.checkConstraints([
|
|
2632
|
+
async storeFact(fact2) {
|
|
2633
|
+
const constraintResult = await this.checkConstraints([fact2]);
|
|
2489
2634
|
if (!constraintResult.valid) {
|
|
2490
2635
|
throw new Error(`Constraint violation: ${constraintResult.errors.join(", ")}`);
|
|
2491
2636
|
}
|
|
2492
2637
|
let before;
|
|
2493
2638
|
if (this.chronicle) {
|
|
2494
|
-
const payload =
|
|
2639
|
+
const payload = fact2.payload;
|
|
2495
2640
|
const id = payload?.id;
|
|
2496
2641
|
if (id) {
|
|
2497
|
-
before = await this.getFact(
|
|
2642
|
+
before = await this.getFact(fact2.tag, id);
|
|
2498
2643
|
}
|
|
2499
2644
|
}
|
|
2500
|
-
await this.persistFact(
|
|
2645
|
+
await this.persistFact(fact2);
|
|
2501
2646
|
if (this.chronicle) {
|
|
2502
|
-
const payload =
|
|
2647
|
+
const payload = fact2.payload;
|
|
2503
2648
|
const id = payload?.id ?? "";
|
|
2504
2649
|
const span = ChronicleContext.current;
|
|
2505
2650
|
try {
|
|
2506
2651
|
await this.chronicle.record({
|
|
2507
|
-
path: getFactPath(
|
|
2652
|
+
path: getFactPath(fact2.tag, id),
|
|
2508
2653
|
before,
|
|
2509
|
-
after:
|
|
2654
|
+
after: fact2,
|
|
2510
2655
|
cause: span?.spanId,
|
|
2511
2656
|
context: span?.contextId,
|
|
2512
|
-
metadata: { factTag:
|
|
2657
|
+
metadata: { factTag: fact2.tag, operation: "storeFact" }
|
|
2513
2658
|
});
|
|
2514
2659
|
} catch {
|
|
2515
2660
|
}
|
|
2516
2661
|
}
|
|
2517
|
-
await this.triggerRules([
|
|
2662
|
+
await this.triggerRules([fact2]);
|
|
2518
2663
|
}
|
|
2519
2664
|
/**
|
|
2520
2665
|
* Store multiple facts in PluresDB
|
|
@@ -2526,28 +2671,28 @@ var PraxisDBStore = class {
|
|
|
2526
2671
|
if (!constraintResult.valid) {
|
|
2527
2672
|
throw new Error(`Constraint violation: ${constraintResult.errors.join(", ")}`);
|
|
2528
2673
|
}
|
|
2529
|
-
for (const
|
|
2674
|
+
for (const fact2 of facts) {
|
|
2530
2675
|
let before;
|
|
2531
2676
|
if (this.chronicle) {
|
|
2532
|
-
const payload =
|
|
2677
|
+
const payload = fact2.payload;
|
|
2533
2678
|
const id = payload?.id;
|
|
2534
2679
|
if (id) {
|
|
2535
|
-
before = await this.getFact(
|
|
2680
|
+
before = await this.getFact(fact2.tag, id);
|
|
2536
2681
|
}
|
|
2537
2682
|
}
|
|
2538
|
-
await this.persistFact(
|
|
2683
|
+
await this.persistFact(fact2);
|
|
2539
2684
|
if (this.chronicle) {
|
|
2540
|
-
const payload =
|
|
2685
|
+
const payload = fact2.payload;
|
|
2541
2686
|
const id = payload?.id ?? "";
|
|
2542
2687
|
const span = ChronicleContext.current;
|
|
2543
2688
|
try {
|
|
2544
2689
|
await this.chronicle.record({
|
|
2545
|
-
path: getFactPath(
|
|
2690
|
+
path: getFactPath(fact2.tag, id),
|
|
2546
2691
|
before,
|
|
2547
|
-
after:
|
|
2692
|
+
after: fact2,
|
|
2548
2693
|
cause: span?.spanId,
|
|
2549
2694
|
context: span?.contextId,
|
|
2550
|
-
metadata: { factTag:
|
|
2695
|
+
metadata: { factTag: fact2.tag, operation: "storeFacts" }
|
|
2551
2696
|
});
|
|
2552
2697
|
} catch {
|
|
2553
2698
|
}
|
|
@@ -2559,11 +2704,11 @@ var PraxisDBStore = class {
|
|
|
2559
2704
|
* Internal method to persist a fact without constraint checking
|
|
2560
2705
|
* Used by both storeFact and derived fact storage
|
|
2561
2706
|
*/
|
|
2562
|
-
async persistFact(
|
|
2563
|
-
const payload =
|
|
2707
|
+
async persistFact(fact2) {
|
|
2708
|
+
const payload = fact2.payload;
|
|
2564
2709
|
const id = payload?.id ?? generateId();
|
|
2565
|
-
const path3 = getFactPath(
|
|
2566
|
-
await this.db.set(path3,
|
|
2710
|
+
const path3 = getFactPath(fact2.tag, id);
|
|
2711
|
+
await this.db.set(path3, fact2);
|
|
2567
2712
|
}
|
|
2568
2713
|
/**
|
|
2569
2714
|
* Get a fact by tag and id
|
|
@@ -2700,8 +2845,8 @@ var PraxisDBStore = class {
|
|
|
2700
2845
|
if (watchers) {
|
|
2701
2846
|
watchers.add(callback);
|
|
2702
2847
|
}
|
|
2703
|
-
const unsubscribe = this.db.watch(path3, (
|
|
2704
|
-
callback([
|
|
2848
|
+
const unsubscribe = this.db.watch(path3, (fact2) => {
|
|
2849
|
+
callback([fact2]);
|
|
2705
2850
|
});
|
|
2706
2851
|
this.subscriptions.push(unsubscribe);
|
|
2707
2852
|
return () => {
|
|
@@ -2759,13 +2904,18 @@ var PraxisDBStore = class {
|
|
|
2759
2904
|
const state2 = {
|
|
2760
2905
|
context: this.context,
|
|
2761
2906
|
facts: [],
|
|
2907
|
+
events,
|
|
2762
2908
|
meta: {}
|
|
2763
2909
|
};
|
|
2764
2910
|
const derivedFacts = [];
|
|
2765
2911
|
for (const rule of rules) {
|
|
2766
2912
|
try {
|
|
2767
|
-
const
|
|
2768
|
-
|
|
2913
|
+
const result = rule.impl(state2, events);
|
|
2914
|
+
if (Array.isArray(result)) {
|
|
2915
|
+
derivedFacts.push(...result);
|
|
2916
|
+
} else if (result && "kind" in result && result.kind === "emit") {
|
|
2917
|
+
derivedFacts.push(...result.facts);
|
|
2918
|
+
}
|
|
2769
2919
|
} catch (error) {
|
|
2770
2920
|
this.onRuleError(rule.id, error);
|
|
2771
2921
|
}
|
|
@@ -2773,19 +2923,19 @@ var PraxisDBStore = class {
|
|
|
2773
2923
|
if (derivedFacts.length > 0) {
|
|
2774
2924
|
const constraintResult = await this.checkConstraints(derivedFacts);
|
|
2775
2925
|
if (constraintResult.valid) {
|
|
2776
|
-
for (const
|
|
2777
|
-
await this.persistFact(
|
|
2926
|
+
for (const fact2 of derivedFacts) {
|
|
2927
|
+
await this.persistFact(fact2);
|
|
2778
2928
|
if (this.chronicle) {
|
|
2779
|
-
const payload =
|
|
2929
|
+
const payload = fact2.payload;
|
|
2780
2930
|
const id = payload?.id ?? "";
|
|
2781
2931
|
const span = ChronicleContext.current;
|
|
2782
2932
|
try {
|
|
2783
2933
|
await this.chronicle.record({
|
|
2784
|
-
path: getFactPath(
|
|
2785
|
-
after:
|
|
2934
|
+
path: getFactPath(fact2.tag, id),
|
|
2935
|
+
after: fact2,
|
|
2786
2936
|
cause: span?.spanId,
|
|
2787
2937
|
context: span?.contextId,
|
|
2788
|
-
metadata: { factTag:
|
|
2938
|
+
metadata: { factTag: fact2.tag, operation: "derivedFact" }
|
|
2789
2939
|
});
|
|
2790
2940
|
} catch {
|
|
2791
2941
|
}
|
|
@@ -3398,7 +3548,7 @@ async function createUnumAdapter(config) {
|
|
|
3398
3548
|
type: "event"
|
|
3399
3549
|
});
|
|
3400
3550
|
}
|
|
3401
|
-
async function broadcastFact(channelId,
|
|
3551
|
+
async function broadcastFact(channelId, fact2) {
|
|
3402
3552
|
const channel = channels.get(channelId);
|
|
3403
3553
|
if (!channel) {
|
|
3404
3554
|
throw new Error(`Not joined to channel ${channelId}`);
|
|
@@ -3406,7 +3556,7 @@ async function createUnumAdapter(config) {
|
|
|
3406
3556
|
await channel.publish({
|
|
3407
3557
|
id: generateId2(),
|
|
3408
3558
|
sender: currentIdentity || { id: "anonymous", createdAt: Date.now() },
|
|
3409
|
-
content:
|
|
3559
|
+
content: fact2,
|
|
3410
3560
|
type: "fact"
|
|
3411
3561
|
});
|
|
3412
3562
|
}
|
|
@@ -3549,17 +3699,17 @@ function schemaToCanvas(schema, _options = {}) {
|
|
|
3549
3699
|
yOffset += schema.events.length * ySpacing + 30;
|
|
3550
3700
|
}
|
|
3551
3701
|
if (schema.facts) {
|
|
3552
|
-
schema.facts.forEach((
|
|
3553
|
-
const pos =
|
|
3702
|
+
schema.facts.forEach((fact2, index) => {
|
|
3703
|
+
const pos = fact2.position && (fact2.position.x !== 0 || fact2.position.y !== 0) ? fact2.position : { x: 50 + xSpacing * 3, y: yOffset + index * ySpacing };
|
|
3554
3704
|
const node = {
|
|
3555
|
-
id:
|
|
3705
|
+
id: fact2.id || `fact-${nodeId++}`,
|
|
3556
3706
|
type: "fact",
|
|
3557
|
-
label:
|
|
3707
|
+
label: fact2.tag,
|
|
3558
3708
|
x: pos.x,
|
|
3559
3709
|
y: pos.y,
|
|
3560
3710
|
width: 150,
|
|
3561
3711
|
height: 50,
|
|
3562
|
-
data:
|
|
3712
|
+
data: fact2,
|
|
3563
3713
|
style: {
|
|
3564
3714
|
backgroundColor: "#fce4ec",
|
|
3565
3715
|
borderColor: "#c2185b"
|
|
@@ -4014,8 +4164,8 @@ var StateDocsGenerator = class {
|
|
|
4014
4164
|
if (logic.facts && logic.facts.length > 0) {
|
|
4015
4165
|
lines.push("**Facts:**");
|
|
4016
4166
|
lines.push("");
|
|
4017
|
-
for (const
|
|
4018
|
-
lines.push(`- \`${
|
|
4167
|
+
for (const fact2 of logic.facts) {
|
|
4168
|
+
lines.push(`- \`${fact2.tag}\`: ${fact2.description || ""}`);
|
|
4019
4169
|
}
|
|
4020
4170
|
lines.push("");
|
|
4021
4171
|
}
|
|
@@ -4147,9 +4297,9 @@ var StateDocsGenerator = class {
|
|
|
4147
4297
|
lines.push("");
|
|
4148
4298
|
lines.push("| Fact | Description | Payload |");
|
|
4149
4299
|
lines.push("|------|-------------|---------|");
|
|
4150
|
-
for (const
|
|
4151
|
-
const payload =
|
|
4152
|
-
lines.push(`| \`${
|
|
4300
|
+
for (const fact2 of logic.facts) {
|
|
4301
|
+
const payload = fact2.payload ? Object.entries(fact2.payload).map(([k, v]) => `${k}: ${v}`).join(", ") : "-";
|
|
4302
|
+
lines.push(`| \`${fact2.tag}\` | ${fact2.description || "-"} | ${payload} |`);
|
|
4153
4303
|
}
|
|
4154
4304
|
lines.push("");
|
|
4155
4305
|
}
|
|
@@ -4201,8 +4351,8 @@ var StateDocsGenerator = class {
|
|
|
4201
4351
|
for (const event of logic.events) {
|
|
4202
4352
|
lines.push(` Processing --> ${event.tag.replace(/[^a-zA-Z0-9]/g, "")}: ${event.tag}`);
|
|
4203
4353
|
}
|
|
4204
|
-
for (const
|
|
4205
|
-
lines.push(` ${
|
|
4354
|
+
for (const fact2 of logic.facts) {
|
|
4355
|
+
lines.push(` ${fact2.tag.replace(/[^a-zA-Z0-9]/g, "")} --> [*]`);
|
|
4206
4356
|
}
|
|
4207
4357
|
}
|
|
4208
4358
|
return {
|
|
@@ -4653,6 +4803,302 @@ async function attachAllIntegrations(engine, registry, options = {}) {
|
|
|
4653
4803
|
}
|
|
4654
4804
|
};
|
|
4655
4805
|
}
|
|
4806
|
+
|
|
4807
|
+
// src/index.ts
|
|
4808
|
+
init_rule_result();
|
|
4809
|
+
|
|
4810
|
+
// src/core/ui-rules.ts
|
|
4811
|
+
init_rule_result();
|
|
4812
|
+
var loadingGateRule = {
|
|
4813
|
+
id: "ui/loading-gate",
|
|
4814
|
+
description: "Signals when the app is in a loading state",
|
|
4815
|
+
eventTypes: ["ui.state-change", "app.init"],
|
|
4816
|
+
impl: (state2) => {
|
|
4817
|
+
const ctx = state2.context;
|
|
4818
|
+
if (ctx.loading) {
|
|
4819
|
+
return RuleResult.emit([fact("ui.loading-gate", { active: true })]);
|
|
4820
|
+
}
|
|
4821
|
+
return RuleResult.retract(["ui.loading-gate"], "Not loading");
|
|
4822
|
+
},
|
|
4823
|
+
contract: {
|
|
4824
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
4825
|
+
behavior: "Emits ui.loading-gate when context.loading is true, retracts when false",
|
|
4826
|
+
examples: [
|
|
4827
|
+
{ given: "loading is true", when: "ui state changes", then: "ui.loading-gate emitted" },
|
|
4828
|
+
{ given: "loading is false", when: "ui state changes", then: "ui.loading-gate retracted" }
|
|
4829
|
+
],
|
|
4830
|
+
invariants: ["Loading gate must reflect context.loading exactly"]
|
|
4831
|
+
}
|
|
4832
|
+
};
|
|
4833
|
+
var errorDisplayRule = {
|
|
4834
|
+
id: "ui/error-display",
|
|
4835
|
+
description: "Signals when an error should be displayed to the user",
|
|
4836
|
+
eventTypes: ["ui.state-change", "app.error"],
|
|
4837
|
+
impl: (state2) => {
|
|
4838
|
+
const ctx = state2.context;
|
|
4839
|
+
if (ctx.error) {
|
|
4840
|
+
return RuleResult.emit([fact("ui.error-display", { message: ctx.error, severity: "error" })]);
|
|
4841
|
+
}
|
|
4842
|
+
return RuleResult.retract(["ui.error-display"], "Error cleared");
|
|
4843
|
+
},
|
|
4844
|
+
contract: {
|
|
4845
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
4846
|
+
behavior: "Emits ui.error-display when context.error is non-null, retracts when cleared",
|
|
4847
|
+
examples: [
|
|
4848
|
+
{ given: "error is set", when: "ui state changes", then: "ui.error-display emitted with message" },
|
|
4849
|
+
{ given: "error is null", when: "ui state changes", then: "ui.error-display retracted" }
|
|
4850
|
+
],
|
|
4851
|
+
invariants: ["Error display must clear when error is null"]
|
|
4852
|
+
}
|
|
4853
|
+
};
|
|
4854
|
+
var offlineIndicatorRule = {
|
|
4855
|
+
id: "ui/offline-indicator",
|
|
4856
|
+
description: "Signals when the app is offline",
|
|
4857
|
+
eventTypes: ["ui.state-change", "network.change"],
|
|
4858
|
+
impl: (state2) => {
|
|
4859
|
+
if (state2.context.offline) {
|
|
4860
|
+
return RuleResult.emit([fact("ui.offline", { message: "You are offline. Changes will sync when reconnected." })]);
|
|
4861
|
+
}
|
|
4862
|
+
return RuleResult.retract(["ui.offline"], "Back online");
|
|
4863
|
+
},
|
|
4864
|
+
contract: {
|
|
4865
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
4866
|
+
behavior: "Emits ui.offline when context.offline is true, retracts when back online",
|
|
4867
|
+
examples: [
|
|
4868
|
+
{ given: "offline is true", when: "network changes", then: "ui.offline emitted" },
|
|
4869
|
+
{ given: "offline is false", when: "network changes", then: "ui.offline retracted" }
|
|
4870
|
+
],
|
|
4871
|
+
invariants: ["Offline indicator must match actual connectivity"]
|
|
4872
|
+
}
|
|
4873
|
+
};
|
|
4874
|
+
var dirtyGuardRule = {
|
|
4875
|
+
id: "ui/dirty-guard",
|
|
4876
|
+
description: "Warns when there are unsaved changes",
|
|
4877
|
+
eventTypes: ["ui.state-change", "navigation.request"],
|
|
4878
|
+
impl: (state2) => {
|
|
4879
|
+
if (state2.context.dirty) {
|
|
4880
|
+
return RuleResult.emit([fact("ui.unsaved-warning", {
|
|
4881
|
+
message: "You have unsaved changes",
|
|
4882
|
+
blocking: true
|
|
4883
|
+
})]);
|
|
4884
|
+
}
|
|
4885
|
+
return RuleResult.retract(["ui.unsaved-warning"], "No unsaved changes");
|
|
4886
|
+
},
|
|
4887
|
+
contract: {
|
|
4888
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
4889
|
+
behavior: "Emits ui.unsaved-warning when context.dirty is true, retracts when saved",
|
|
4890
|
+
examples: [
|
|
4891
|
+
{ given: "dirty is true", when: "ui state changes", then: "ui.unsaved-warning emitted with blocking=true" },
|
|
4892
|
+
{ given: "dirty is false", when: "ui state changes", then: "ui.unsaved-warning retracted" }
|
|
4893
|
+
],
|
|
4894
|
+
invariants: ["Dirty guard must clear after save"]
|
|
4895
|
+
}
|
|
4896
|
+
};
|
|
4897
|
+
var initGateRule = {
|
|
4898
|
+
id: "ui/init-gate",
|
|
4899
|
+
description: "Signals whether the app has completed initialization",
|
|
4900
|
+
eventTypes: ["ui.state-change", "app.init"],
|
|
4901
|
+
impl: (state2) => {
|
|
4902
|
+
if (!state2.context.initialized) {
|
|
4903
|
+
return RuleResult.emit([fact("ui.init-pending", {
|
|
4904
|
+
message: "App is initializing..."
|
|
4905
|
+
})]);
|
|
4906
|
+
}
|
|
4907
|
+
return RuleResult.retract(["ui.init-pending"], "App initialized");
|
|
4908
|
+
},
|
|
4909
|
+
contract: {
|
|
4910
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
4911
|
+
behavior: "Emits ui.init-pending until context.initialized is true",
|
|
4912
|
+
examples: [
|
|
4913
|
+
{ given: "initialized is false", when: "app starts", then: "ui.init-pending emitted" },
|
|
4914
|
+
{ given: "initialized is true", when: "init completes", then: "ui.init-pending retracted" }
|
|
4915
|
+
],
|
|
4916
|
+
invariants: ["Init gate must clear exactly once, when initialization completes"]
|
|
4917
|
+
}
|
|
4918
|
+
};
|
|
4919
|
+
var viewportRule = {
|
|
4920
|
+
id: "ui/viewport-class",
|
|
4921
|
+
description: "Classifies viewport size for responsive layout decisions",
|
|
4922
|
+
eventTypes: ["ui.state-change", "ui.resize"],
|
|
4923
|
+
impl: (state2) => {
|
|
4924
|
+
const vp = state2.context.viewport;
|
|
4925
|
+
if (!vp) return RuleResult.skip("No viewport data");
|
|
4926
|
+
return RuleResult.emit([fact("ui.viewport-class", {
|
|
4927
|
+
viewport: vp,
|
|
4928
|
+
compact: vp === "mobile",
|
|
4929
|
+
showSidebar: vp !== "mobile"
|
|
4930
|
+
})]);
|
|
4931
|
+
},
|
|
4932
|
+
contract: {
|
|
4933
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
4934
|
+
behavior: "Classifies viewport into responsive layout hints",
|
|
4935
|
+
examples: [
|
|
4936
|
+
{ given: "viewport is mobile", when: "resize event", then: "compact=true, showSidebar=false" },
|
|
4937
|
+
{ given: "viewport is desktop", when: "resize event", then: "compact=false, showSidebar=true" }
|
|
4938
|
+
],
|
|
4939
|
+
invariants: ["Viewport class must update on every resize event"]
|
|
4940
|
+
}
|
|
4941
|
+
};
|
|
4942
|
+
var noInteractionWhileLoadingConstraint = {
|
|
4943
|
+
id: "ui/no-interaction-while-loading",
|
|
4944
|
+
description: "Prevents data mutations while a load operation is in progress",
|
|
4945
|
+
impl: (state2) => {
|
|
4946
|
+
if (state2.context.loading) {
|
|
4947
|
+
return "Cannot perform action while data is loading";
|
|
4948
|
+
}
|
|
4949
|
+
return true;
|
|
4950
|
+
},
|
|
4951
|
+
contract: {
|
|
4952
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
4953
|
+
behavior: "Fails when context.loading is true",
|
|
4954
|
+
examples: [
|
|
4955
|
+
{ given: "loading is true", when: "action attempted", then: "violation" },
|
|
4956
|
+
{ given: "loading is false", when: "action attempted", then: "pass" }
|
|
4957
|
+
],
|
|
4958
|
+
invariants: ["Must always fail during loading"]
|
|
4959
|
+
}
|
|
4960
|
+
};
|
|
4961
|
+
var mustBeInitializedConstraint = {
|
|
4962
|
+
id: "ui/must-be-initialized",
|
|
4963
|
+
description: "Requires app initialization before user interactions",
|
|
4964
|
+
impl: (state2) => {
|
|
4965
|
+
if (!state2.context.initialized) {
|
|
4966
|
+
return "App must be initialized before performing this action";
|
|
4967
|
+
}
|
|
4968
|
+
return true;
|
|
4969
|
+
},
|
|
4970
|
+
contract: {
|
|
4971
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
4972
|
+
behavior: "Fails when context.initialized is false",
|
|
4973
|
+
examples: [
|
|
4974
|
+
{ given: "initialized is false", when: "action attempted", then: "violation" },
|
|
4975
|
+
{ given: "initialized is true", when: "action attempted", then: "pass" }
|
|
4976
|
+
],
|
|
4977
|
+
invariants: ["Must always fail before init completes"]
|
|
4978
|
+
}
|
|
4979
|
+
};
|
|
4980
|
+
var uiModule = {
|
|
4981
|
+
rules: [
|
|
4982
|
+
loadingGateRule,
|
|
4983
|
+
errorDisplayRule,
|
|
4984
|
+
offlineIndicatorRule,
|
|
4985
|
+
dirtyGuardRule,
|
|
4986
|
+
initGateRule,
|
|
4987
|
+
viewportRule
|
|
4988
|
+
],
|
|
4989
|
+
constraints: [
|
|
4990
|
+
noInteractionWhileLoadingConstraint,
|
|
4991
|
+
mustBeInitializedConstraint
|
|
4992
|
+
],
|
|
4993
|
+
meta: {
|
|
4994
|
+
name: "praxis-ui",
|
|
4995
|
+
version: "1.0.0",
|
|
4996
|
+
description: "Predefined UI rules and constraints \u2014 separate from business logic"
|
|
4997
|
+
}
|
|
4998
|
+
};
|
|
4999
|
+
function createUIModule(options) {
|
|
5000
|
+
const allRules = uiModule.rules;
|
|
5001
|
+
const allConstraints = uiModule.constraints;
|
|
5002
|
+
const selectedRules = options.rules ? allRules.filter((r) => options.rules.includes(r.id)) : allRules;
|
|
5003
|
+
const selectedConstraints = options.constraints ? allConstraints.filter((c) => options.constraints.includes(c.id)) : allConstraints;
|
|
5004
|
+
return {
|
|
5005
|
+
rules: [...selectedRules, ...options.extraRules ?? []],
|
|
5006
|
+
constraints: [...selectedConstraints, ...options.extraConstraints ?? []],
|
|
5007
|
+
meta: { ...uiModule.meta, customized: true }
|
|
5008
|
+
};
|
|
5009
|
+
}
|
|
5010
|
+
function uiStateChanged(changes) {
|
|
5011
|
+
return { tag: "ui.state-change", payload: changes ?? {} };
|
|
5012
|
+
}
|
|
5013
|
+
function navigationRequest(to) {
|
|
5014
|
+
return { tag: "navigation.request", payload: { to } };
|
|
5015
|
+
}
|
|
5016
|
+
function resizeEvent(width, height) {
|
|
5017
|
+
return { tag: "ui.resize", payload: { width, height } };
|
|
5018
|
+
}
|
|
5019
|
+
|
|
5020
|
+
// src/core/completeness.ts
|
|
5021
|
+
function auditCompleteness(manifest, registryRuleIds, registryConstraintIds, rulesWithContracts, config) {
|
|
5022
|
+
const threshold = config?.threshold ?? 90;
|
|
5023
|
+
const domainBranches = manifest.branches.filter((b) => b.kind === "domain");
|
|
5024
|
+
const coveredDomain = domainBranches.filter((b) => b.coveredBy && registryRuleIds.includes(b.coveredBy));
|
|
5025
|
+
const uncoveredDomain = domainBranches.filter((b) => !b.coveredBy || !registryRuleIds.includes(b.coveredBy));
|
|
5026
|
+
const invariantBranches = manifest.branches.filter((b) => b.kind === "invariant");
|
|
5027
|
+
const coveredInvariants = invariantBranches.filter((b) => b.coveredBy && registryConstraintIds.includes(b.coveredBy));
|
|
5028
|
+
const uncoveredInvariants = invariantBranches.filter((b) => !b.coveredBy || !registryConstraintIds.includes(b.coveredBy));
|
|
5029
|
+
const needContracts = manifest.rulesNeedingContracts;
|
|
5030
|
+
const haveContracts = needContracts.filter((id) => rulesWithContracts.includes(id));
|
|
5031
|
+
const missingContracts = needContracts.filter((id) => !rulesWithContracts.includes(id));
|
|
5032
|
+
const neededFields = manifest.stateFields.filter((f) => f.usedByRule);
|
|
5033
|
+
const coveredFields = neededFields.filter((f) => f.inContext);
|
|
5034
|
+
const missingFields = neededFields.filter((f) => !f.inContext);
|
|
5035
|
+
const coveredTransitions = manifest.transitions.filter((t) => t.eventTag);
|
|
5036
|
+
const missingTransitions = manifest.transitions.filter((t) => !t.eventTag);
|
|
5037
|
+
const ruleScore = domainBranches.length > 0 ? coveredDomain.length / domainBranches.length * 40 : 40;
|
|
5038
|
+
const constraintScore = invariantBranches.length > 0 ? coveredInvariants.length / invariantBranches.length * 20 : 20;
|
|
5039
|
+
const contractScore = needContracts.length > 0 ? haveContracts.length / needContracts.length * 15 : 15;
|
|
5040
|
+
const contextScore = neededFields.length > 0 ? coveredFields.length / neededFields.length * 15 : 15;
|
|
5041
|
+
const eventScore = manifest.transitions.length > 0 ? coveredTransitions.length / manifest.transitions.length * 10 : 10;
|
|
5042
|
+
const score = Math.round(ruleScore + constraintScore + contractScore + contextScore + eventScore);
|
|
5043
|
+
const rating = score >= 90 ? "complete" : score >= 70 ? "good" : score >= 50 ? "partial" : "incomplete";
|
|
5044
|
+
const report = {
|
|
5045
|
+
score,
|
|
5046
|
+
rating,
|
|
5047
|
+
rules: { total: domainBranches.length, covered: coveredDomain.length, uncovered: uncoveredDomain },
|
|
5048
|
+
constraints: { total: invariantBranches.length, covered: coveredInvariants.length, uncovered: uncoveredInvariants },
|
|
5049
|
+
contracts: { total: needContracts.length, withContracts: haveContracts.length, missing: missingContracts },
|
|
5050
|
+
context: { total: neededFields.length, covered: coveredFields.length, missing: missingFields },
|
|
5051
|
+
events: { total: manifest.transitions.length, covered: coveredTransitions.length, missing: missingTransitions }
|
|
5052
|
+
};
|
|
5053
|
+
if (config?.strict && score < threshold) {
|
|
5054
|
+
throw new Error(`Praxis completeness ${score}/100 (${rating}) \u2014 below threshold ${threshold}. ${uncoveredDomain.length} uncovered rules, ${uncoveredInvariants.length} uncovered invariants, ${missingContracts.length} missing contracts.`);
|
|
5055
|
+
}
|
|
5056
|
+
return report;
|
|
5057
|
+
}
|
|
5058
|
+
function formatReport(report) {
|
|
5059
|
+
const lines = [];
|
|
5060
|
+
const icon = report.rating === "complete" ? "\u2705" : report.rating === "good" ? "\u{1F7E2}" : report.rating === "partial" ? "\u{1F7E1}" : "\u{1F534}";
|
|
5061
|
+
lines.push(`${icon} Praxis Completeness: ${report.score}/100 (${report.rating})`);
|
|
5062
|
+
lines.push("");
|
|
5063
|
+
lines.push(`Rules: ${report.rules.covered}/${report.rules.total} domain branches covered (${pct(report.rules.covered, report.rules.total)})`);
|
|
5064
|
+
lines.push(`Constraints: ${report.constraints.covered}/${report.constraints.total} invariants covered (${pct(report.constraints.covered, report.constraints.total)})`);
|
|
5065
|
+
lines.push(`Contracts: ${report.contracts.withContracts}/${report.contracts.total} rules have contracts (${pct(report.contracts.withContracts, report.contracts.total)})`);
|
|
5066
|
+
lines.push(`Context: ${report.context.covered}/${report.context.total} state fields in context (${pct(report.context.covered, report.context.total)})`);
|
|
5067
|
+
lines.push(`Events: ${report.events.covered}/${report.events.total} transitions have events (${pct(report.events.covered, report.events.total)})`);
|
|
5068
|
+
if (report.rules.uncovered.length > 0) {
|
|
5069
|
+
lines.push("");
|
|
5070
|
+
lines.push("Uncovered domain logic:");
|
|
5071
|
+
for (const b of report.rules.uncovered) {
|
|
5072
|
+
lines.push(` \u274C ${b.location}: ${b.condition}${b.note ? ` \u2014 ${b.note}` : ""}`);
|
|
5073
|
+
}
|
|
5074
|
+
}
|
|
5075
|
+
if (report.constraints.uncovered.length > 0) {
|
|
5076
|
+
lines.push("");
|
|
5077
|
+
lines.push("Uncovered invariants:");
|
|
5078
|
+
for (const b of report.constraints.uncovered) {
|
|
5079
|
+
lines.push(` \u274C ${b.location}: ${b.condition}${b.note ? ` \u2014 ${b.note}` : ""}`);
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
5082
|
+
if (report.contracts.missing.length > 0) {
|
|
5083
|
+
lines.push("");
|
|
5084
|
+
lines.push("Rules missing contracts:");
|
|
5085
|
+
for (const id of report.contracts.missing) {
|
|
5086
|
+
lines.push(` \u{1F4DD} ${id}`);
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
if (report.events.missing.length > 0) {
|
|
5090
|
+
lines.push("");
|
|
5091
|
+
lines.push("State transitions without events:");
|
|
5092
|
+
for (const t of report.events.missing) {
|
|
5093
|
+
lines.push(` \u26A1 ${t.location}: ${t.description}`);
|
|
5094
|
+
}
|
|
5095
|
+
}
|
|
5096
|
+
return lines.join("\n");
|
|
5097
|
+
}
|
|
5098
|
+
function pct(a, b) {
|
|
5099
|
+
if (b === 0) return "100%";
|
|
5100
|
+
return Math.round(a / b * 100) + "%";
|
|
5101
|
+
}
|
|
4656
5102
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4657
5103
|
0 && (module.exports = {
|
|
4658
5104
|
AcknowledgeContractGap,
|
|
@@ -4678,6 +5124,7 @@ async function attachAllIntegrations(engine, registry, options = {}) {
|
|
|
4678
5124
|
PraxisSchemaRegistry,
|
|
4679
5125
|
ReactiveLogicEngine,
|
|
4680
5126
|
RegistryIntrospector,
|
|
5127
|
+
RuleResult,
|
|
4681
5128
|
StateDocsGenerator,
|
|
4682
5129
|
TerminalAdapter,
|
|
4683
5130
|
ValidateContracts,
|
|
@@ -4685,6 +5132,7 @@ async function attachAllIntegrations(engine, registry, options = {}) {
|
|
|
4685
5132
|
attachTauriToEngine,
|
|
4686
5133
|
attachToEngine,
|
|
4687
5134
|
attachUnumToEngine,
|
|
5135
|
+
auditCompleteness,
|
|
4688
5136
|
canvasToMermaid,
|
|
4689
5137
|
canvasToSchema,
|
|
4690
5138
|
canvasToYaml,
|
|
@@ -4710,6 +5158,7 @@ async function attachAllIntegrations(engine, registry, options = {}) {
|
|
|
4710
5158
|
createTauriPraxisAdapter,
|
|
4711
5159
|
createTerminalAdapter,
|
|
4712
5160
|
createTimerActor,
|
|
5161
|
+
createUIModule,
|
|
4713
5162
|
createUnifiedApp,
|
|
4714
5163
|
createUnumAdapter,
|
|
4715
5164
|
defineConstraint,
|
|
@@ -4718,10 +5167,14 @@ async function attachAllIntegrations(engine, registry, options = {}) {
|
|
|
4718
5167
|
defineFact,
|
|
4719
5168
|
defineModule,
|
|
4720
5169
|
defineRule,
|
|
5170
|
+
dirtyGuardRule,
|
|
5171
|
+
errorDisplayRule,
|
|
5172
|
+
fact,
|
|
4721
5173
|
filterEvents,
|
|
4722
5174
|
filterFacts,
|
|
4723
5175
|
findEvent,
|
|
4724
5176
|
findFact,
|
|
5177
|
+
formatReport,
|
|
4725
5178
|
formatValidationReport,
|
|
4726
5179
|
formatValidationReportJSON,
|
|
4727
5180
|
formatValidationReportSARIF,
|
|
@@ -4732,16 +5185,26 @@ async function attachAllIntegrations(engine, registry, options = {}) {
|
|
|
4732
5185
|
getEventPath,
|
|
4733
5186
|
getFactPath,
|
|
4734
5187
|
getSchemaPath,
|
|
5188
|
+
initGateRule,
|
|
4735
5189
|
isContract,
|
|
4736
5190
|
loadSchema,
|
|
4737
5191
|
loadSchemaFromFile,
|
|
4738
5192
|
loadSchemaFromJson,
|
|
4739
5193
|
loadSchemaFromYaml,
|
|
5194
|
+
loadingGateRule,
|
|
5195
|
+
mustBeInitializedConstraint,
|
|
5196
|
+
navigationRequest,
|
|
5197
|
+
noInteractionWhileLoadingConstraint,
|
|
5198
|
+
offlineIndicatorRule,
|
|
4740
5199
|
registerSchema,
|
|
5200
|
+
resizeEvent,
|
|
4741
5201
|
runTerminalCommand,
|
|
4742
5202
|
schemaToCanvas,
|
|
5203
|
+
uiModule,
|
|
5204
|
+
uiStateChanged,
|
|
4743
5205
|
validateContracts,
|
|
4744
5206
|
validateForGeneration,
|
|
4745
5207
|
validateSchema,
|
|
4746
|
-
validateWithGuardian
|
|
5208
|
+
validateWithGuardian,
|
|
5209
|
+
viewportRule
|
|
4747
5210
|
});
|