@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
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Project Chronicle — Core Event Store
3
+ *
4
+ * Records project lifecycle events (rule registrations, contract changes,
5
+ * gate transitions, build events, etc.) as a queryable, append-only log.
6
+ *
7
+ * This is the development-lifecycle counterpart to runtime Chronos:
8
+ * where runtime Chronos records "user typed, rule fired, fact emitted",
9
+ * project-level Chronos records "rule registered, contract updated,
10
+ * gate opened, completeness score changed."
11
+ */
12
+
13
+ // ─── Types ──────────────────────────────────────────────────────────────────
14
+
15
+ /** The kind of project lifecycle event. */
16
+ export type ProjectEventKind =
17
+ | 'rule'
18
+ | 'contract'
19
+ | 'expectation'
20
+ | 'gate'
21
+ | 'build'
22
+ | 'fact';
23
+
24
+ /**
25
+ * A single project lifecycle event.
26
+ *
27
+ * Immutable once recorded — the chronicle is append-only.
28
+ */
29
+ export interface ProjectEvent {
30
+ /** Event kind (rule, contract, expectation, gate, build, fact). */
31
+ kind: ProjectEventKind;
32
+ /**
33
+ * Action that occurred.
34
+ * Examples: 'registered', 'modified', 'removed', 'satisfied', 'violated',
35
+ * 'opened', 'closed', 'blocked', 'audit-complete', 'introduced', 'deprecated'.
36
+ */
37
+ action: string;
38
+ /** The subject (rule id, contract id, gate name, fact tag, etc.). */
39
+ subject: string;
40
+ /** Unix ms timestamp. */
41
+ timestamp: number;
42
+ /** Arbitrary metadata for this event. */
43
+ metadata: Record<string, unknown>;
44
+ /** Optional before/after diff for modifications. */
45
+ diff?: { before: unknown; after: unknown };
46
+ }
47
+
48
+ /** Options for creating a ProjectChronicle. */
49
+ export interface ProjectChronicleOptions {
50
+ /** Maximum events to retain (0 = unlimited, default 10_000). */
51
+ maxEvents?: number;
52
+ /** Optional clock function (for testing). */
53
+ now?: () => number;
54
+ }
55
+
56
+ // ─── ProjectChronicle ───────────────────────────────────────────────────────
57
+
58
+ /**
59
+ * In-memory, append-only chronicle of project lifecycle events.
60
+ *
61
+ * Thread-safe for single-threaded JS; immutable snapshots via `getEvents()`.
62
+ */
63
+ export class ProjectChronicle {
64
+ private events: ProjectEvent[] = [];
65
+ private readonly maxEvents: number;
66
+ private readonly now: () => number;
67
+
68
+ constructor(options: ProjectChronicleOptions = {}) {
69
+ this.maxEvents = options.maxEvents ?? 10_000;
70
+ this.now = options.now ?? (() => Date.now());
71
+ }
72
+
73
+ // ── Recording ───────────────────────────────────────────────────────────
74
+
75
+ /**
76
+ * Record a project event. Returns the recorded event (with timestamp filled in).
77
+ */
78
+ record(
79
+ event: Omit<ProjectEvent, 'timestamp'> & { timestamp?: number },
80
+ ): ProjectEvent {
81
+ const full: ProjectEvent = {
82
+ kind: event.kind,
83
+ action: event.action,
84
+ subject: event.subject,
85
+ timestamp: event.timestamp ?? this.now(),
86
+ metadata: event.metadata,
87
+ diff: event.diff,
88
+ };
89
+ this.events.push(full);
90
+
91
+ // Evict oldest if over cap
92
+ if (this.maxEvents > 0 && this.events.length > this.maxEvents) {
93
+ this.events = this.events.slice(this.events.length - this.maxEvents);
94
+ }
95
+
96
+ return full;
97
+ }
98
+
99
+ // ── Convenience recorders ─────────────────────────────────────────────
100
+
101
+ recordRuleRegistered(ruleId: string, meta: Record<string, unknown> = {}): ProjectEvent {
102
+ return this.record({ kind: 'rule', action: 'registered', subject: ruleId, metadata: meta });
103
+ }
104
+
105
+ recordRuleModified(
106
+ ruleId: string,
107
+ diff: { before: unknown; after: unknown },
108
+ meta: Record<string, unknown> = {},
109
+ ): ProjectEvent {
110
+ return this.record({ kind: 'rule', action: 'modified', subject: ruleId, metadata: meta, diff });
111
+ }
112
+
113
+ recordRuleRemoved(ruleId: string, meta: Record<string, unknown> = {}): ProjectEvent {
114
+ return this.record({ kind: 'rule', action: 'removed', subject: ruleId, metadata: meta });
115
+ }
116
+
117
+ recordContractAdded(contractId: string, meta: Record<string, unknown> = {}): ProjectEvent {
118
+ return this.record({ kind: 'contract', action: 'added', subject: contractId, metadata: meta });
119
+ }
120
+
121
+ recordContractModified(
122
+ contractId: string,
123
+ diff: { before: unknown; after: unknown },
124
+ meta: Record<string, unknown> = {},
125
+ ): ProjectEvent {
126
+ return this.record({ kind: 'contract', action: 'modified', subject: contractId, metadata: meta, diff });
127
+ }
128
+
129
+ recordExpectationSatisfied(name: string, meta: Record<string, unknown> = {}): ProjectEvent {
130
+ return this.record({ kind: 'expectation', action: 'satisfied', subject: name, metadata: meta });
131
+ }
132
+
133
+ recordExpectationViolated(name: string, meta: Record<string, unknown> = {}): ProjectEvent {
134
+ return this.record({ kind: 'expectation', action: 'violated', subject: name, metadata: meta });
135
+ }
136
+
137
+ recordGateTransition(
138
+ gateName: string,
139
+ from: string,
140
+ to: string,
141
+ meta: Record<string, unknown> = {},
142
+ ): ProjectEvent {
143
+ return this.record({
144
+ kind: 'gate',
145
+ action: to,
146
+ subject: gateName,
147
+ metadata: { ...meta, from, to },
148
+ diff: { before: from, after: to },
149
+ });
150
+ }
151
+
152
+ recordBuildAudit(
153
+ score: number,
154
+ delta: number,
155
+ meta: Record<string, unknown> = {},
156
+ ): ProjectEvent {
157
+ return this.record({
158
+ kind: 'build',
159
+ action: 'audit-complete',
160
+ subject: 'completeness',
161
+ metadata: { ...meta, score, delta },
162
+ });
163
+ }
164
+
165
+ recordFactIntroduced(factTag: string, meta: Record<string, unknown> = {}): ProjectEvent {
166
+ return this.record({ kind: 'fact', action: 'introduced', subject: factTag, metadata: meta });
167
+ }
168
+
169
+ recordFactDeprecated(factTag: string, meta: Record<string, unknown> = {}): ProjectEvent {
170
+ return this.record({ kind: 'fact', action: 'deprecated', subject: factTag, metadata: meta });
171
+ }
172
+
173
+ // ── Access ──────────────────────────────────────────────────────────────
174
+
175
+ /** Return a shallow copy of all events. */
176
+ getEvents(): ProjectEvent[] {
177
+ return [...this.events];
178
+ }
179
+
180
+ /** Total number of recorded events. */
181
+ get size(): number {
182
+ return this.events.length;
183
+ }
184
+
185
+ /** Clear all events (primarily for testing). */
186
+ clear(): void {
187
+ this.events = [];
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Create a new ProjectChronicle instance.
193
+ */
194
+ export function createProjectChronicle(
195
+ options?: ProjectChronicleOptions,
196
+ ): ProjectChronicle {
197
+ return new ProjectChronicle(options);
198
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Timeline — Queryable view over ProjectChronicle events
3
+ *
4
+ * Provides filtering, range queries, subject history, and behavioral deltas.
5
+ */
6
+
7
+ import type { ProjectEvent, ProjectEventKind } from './project-chronicle.js';
8
+ import type { ProjectChronicle } from './project-chronicle.js';
9
+
10
+ // ─── Filter Types ───────────────────────────────────────────────────────────
11
+
12
+ /** Filter criteria for timeline queries. All fields are optional (AND logic). */
13
+ export interface TimelineFilter {
14
+ /** Filter by event kind(s). */
15
+ kind?: ProjectEventKind | ProjectEventKind[];
16
+ /** Filter by action string(s). */
17
+ action?: string | string[];
18
+ /** Filter by subject (exact match or array). */
19
+ subject?: string | string[];
20
+ /** Only events at or after this timestamp (inclusive). */
21
+ since?: number;
22
+ /** Only events at or before this timestamp (inclusive). */
23
+ until?: number;
24
+ }
25
+
26
+ /** Summary of changes between two points in time. */
27
+ export interface BehavioralDelta {
28
+ /** Time range of this delta. */
29
+ from: number;
30
+ to: number;
31
+ /** Events within the range. */
32
+ events: ProjectEvent[];
33
+ /** Summary counts by kind. */
34
+ summary: Record<ProjectEventKind, number>;
35
+ /** Subjects that were added (first 'registered' / 'added' / 'introduced'). */
36
+ added: string[];
37
+ /** Subjects that were removed ('removed' / 'deprecated'). */
38
+ removed: string[];
39
+ /** Subjects that were modified. */
40
+ modified: string[];
41
+ }
42
+
43
+ // ─── Timeline Class ─────────────────────────────────────────────────────────
44
+
45
+ /**
46
+ * Queryable timeline wrapping a ProjectChronicle.
47
+ *
48
+ * All query methods return new arrays — never the internal event store.
49
+ */
50
+ export class Timeline {
51
+ constructor(private readonly chronicle: ProjectChronicle) {}
52
+
53
+ // ── Queries ─────────────────────────────────────────────────────────────
54
+
55
+ /**
56
+ * Query events with optional filtering.
57
+ * Returns matching events sorted chronologically (oldest first).
58
+ */
59
+ getTimeline(filter?: TimelineFilter): ProjectEvent[] {
60
+ let events = this.chronicle.getEvents();
61
+ if (!filter) return events;
62
+ events = applyFilter(events, filter);
63
+ return events;
64
+ }
65
+
66
+ /**
67
+ * Get all events since a timestamp (inclusive).
68
+ */
69
+ getEventsSince(timestamp: number): ProjectEvent[] {
70
+ return this.getTimeline({ since: timestamp });
71
+ }
72
+
73
+ /**
74
+ * Compute a behavioral delta between two timestamps.
75
+ */
76
+ getDelta(from: number, to: number): BehavioralDelta {
77
+ const events = this.getTimeline({ since: from, until: to });
78
+ return buildDelta(from, to, events);
79
+ }
80
+
81
+ /**
82
+ * Get full history for a specific subject (rule id, gate name, etc.).
83
+ * Sorted chronologically.
84
+ */
85
+ getHistory(subjectId: string): ProjectEvent[] {
86
+ return this.getTimeline({ subject: subjectId });
87
+ }
88
+ }
89
+
90
+ // ─── Helpers ────────────────────────────────────────────────────────────────
91
+
92
+ function applyFilter(events: ProjectEvent[], filter: TimelineFilter): ProjectEvent[] {
93
+ return events.filter(e => {
94
+ if (filter.kind) {
95
+ const kinds = Array.isArray(filter.kind) ? filter.kind : [filter.kind];
96
+ if (!kinds.includes(e.kind)) return false;
97
+ }
98
+ if (filter.action) {
99
+ const actions = Array.isArray(filter.action) ? filter.action : [filter.action];
100
+ if (!actions.includes(e.action)) return false;
101
+ }
102
+ if (filter.subject) {
103
+ const subjects = Array.isArray(filter.subject) ? filter.subject : [filter.subject];
104
+ if (!subjects.includes(e.subject)) return false;
105
+ }
106
+ if (filter.since != null && e.timestamp < filter.since) return false;
107
+ if (filter.until != null && e.timestamp > filter.until) return false;
108
+ return true;
109
+ });
110
+ }
111
+
112
+ const ADD_ACTIONS = new Set(['registered', 'added', 'introduced', 'opened']);
113
+ const REMOVE_ACTIONS = new Set(['removed', 'deprecated', 'closed']);
114
+ const MODIFY_ACTIONS = new Set(['modified', 'updated']);
115
+
116
+ function buildDelta(from: number, to: number, events: ProjectEvent[]): BehavioralDelta {
117
+ const summary: Record<string, number> = {};
118
+ const added = new Set<string>();
119
+ const removed = new Set<string>();
120
+ const modified = new Set<string>();
121
+
122
+ for (const e of events) {
123
+ summary[e.kind] = (summary[e.kind] ?? 0) + 1;
124
+
125
+ if (ADD_ACTIONS.has(e.action)) {
126
+ added.add(e.subject);
127
+ removed.delete(e.subject); // re-added overrides removal
128
+ } else if (REMOVE_ACTIONS.has(e.action)) {
129
+ removed.add(e.subject);
130
+ added.delete(e.subject); // removed overrides addition
131
+ } else if (MODIFY_ACTIONS.has(e.action)) {
132
+ modified.add(e.subject);
133
+ }
134
+ }
135
+
136
+ return {
137
+ from,
138
+ to,
139
+ events,
140
+ summary: summary as Record<ProjectEventKind, number>,
141
+ added: [...added],
142
+ removed: [...removed],
143
+ modified: [...modified],
144
+ };
145
+ }
146
+
147
+ /**
148
+ * Create a Timeline for a given chronicle.
149
+ */
150
+ export function createTimeline(chronicle: ProjectChronicle): Timeline {
151
+ return new Timeline(chronicle);
152
+ }
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Decision Ledger — Analyzer Types
3
+ *
4
+ * Types for the graph analysis engine that understands the entire rule graph
5
+ * and finds what's missing, broken, or unreachable.
6
+ */
7
+
8
+ // ─── Dependency Graph ───────────────────────────────────────────────────────
9
+
10
+ /** A node in the fact dependency graph */
11
+ export interface FactNode {
12
+ /** The fact tag */
13
+ tag: string;
14
+ /** Rules that produce this fact */
15
+ producedBy: string[];
16
+ /** Rules that read/consume this fact */
17
+ consumedBy: string[];
18
+ }
19
+
20
+ /** An edge in the dependency graph (rule → fact or fact → rule) */
21
+ export interface DependencyEdge {
22
+ from: string;
23
+ to: string;
24
+ type: 'produces' | 'consumes';
25
+ }
26
+
27
+ /** The full dependency graph */
28
+ export interface DependencyGraph {
29
+ /** Fact nodes keyed by tag */
30
+ facts: Map<string, FactNode>;
31
+ /** All edges */
32
+ edges: DependencyEdge[];
33
+ /** Rule IDs that produce facts */
34
+ producers: Map<string, string[]>; // ruleId → fact tags produced
35
+ /** Rule IDs that consume facts */
36
+ consumers: Map<string, string[]>; // ruleId → fact tags consumed
37
+ }
38
+
39
+ // ─── Derivation Chains ──────────────────────────────────────────────────────
40
+
41
+ /** A single step in a derivation chain */
42
+ export interface DerivationStep {
43
+ type: 'event' | 'rule-fired' | 'fact-produced' | 'fact-read';
44
+ id: string; // event tag, rule id, or fact tag
45
+ description: string;
46
+ }
47
+
48
+ /** A chain showing how a fact was derived */
49
+ export interface DerivationChain {
50
+ /** The target fact tag */
51
+ targetFact: string;
52
+ /** Ordered steps from origin to target */
53
+ steps: DerivationStep[];
54
+ /** Depth of the chain */
55
+ depth: number;
56
+ }
57
+
58
+ // ─── Dead Rules ─────────────────────────────────────────────────────────────
59
+
60
+ /** A rule that can never fire given known event types */
61
+ export interface DeadRule {
62
+ ruleId: string;
63
+ description: string;
64
+ /** Event types the rule requires */
65
+ requiredEventTypes: string[];
66
+ /** Why it's dead */
67
+ reason: string;
68
+ }
69
+
70
+ // ─── Unreachable States ─────────────────────────────────────────────────────
71
+
72
+ /** A fact combination that no rule sequence can produce */
73
+ export interface UnreachableState {
74
+ /** The fact tags that can't be produced together */
75
+ factTags: string[];
76
+ /** Why it's unreachable */
77
+ reason: string;
78
+ }
79
+
80
+ // ─── Shadowed Rules ─────────────────────────────────────────────────────────
81
+
82
+ /** A rule that always loses to another */
83
+ export interface ShadowedRule {
84
+ /** The shadowed rule */
85
+ ruleId: string;
86
+ /** The rule that shadows it */
87
+ shadowedBy: string;
88
+ /** Shared event types */
89
+ sharedEventTypes: string[];
90
+ /** Why it's shadowed */
91
+ reason: string;
92
+ }
93
+
94
+ // ─── Contradictions ─────────────────────────────────────────────────────────
95
+
96
+ /** Two rules that produce conflicting facts */
97
+ export interface Contradiction {
98
+ /** First rule */
99
+ ruleA: string;
100
+ /** Second rule */
101
+ ruleB: string;
102
+ /** The conflicting fact tag */
103
+ conflictingTag: string;
104
+ /** Description of the conflict */
105
+ reason: string;
106
+ }
107
+
108
+ // ─── Gaps ───────────────────────────────────────────────────────────────────
109
+
110
+ /** An expected behavior with no covering rule */
111
+ export interface Gap {
112
+ /** The expectation name */
113
+ expectationName: string;
114
+ /** What's missing */
115
+ description: string;
116
+ /** Related rule IDs that partially cover */
117
+ partialCoverage: string[];
118
+ /** The type of gap */
119
+ type: 'no-rule' | 'partial-coverage' | 'no-contract';
120
+ }
121
+
122
+ // ─── Impact Analysis ────────────────────────────────────────────────────────
123
+
124
+ /** Impact of removing a fact */
125
+ export interface ImpactReport {
126
+ /** The fact being analyzed */
127
+ factTag: string;
128
+ /** Rules that would stop firing */
129
+ affectedRules: string[];
130
+ /** Facts that would disappear (transitively) */
131
+ affectedFacts: string[];
132
+ /** Total downstream impact depth */
133
+ depth: number;
134
+ }
135
+
136
+ // ─── Contract Verification ──────────────────────────────────────────────────
137
+
138
+ /** Result of running a contract example against its rule */
139
+ export interface ExampleVerification {
140
+ /** The example index */
141
+ index: number;
142
+ /** Given/When/Then description */
143
+ given: string;
144
+ when: string;
145
+ expectedThen: string;
146
+ /** Whether it passed */
147
+ passed: boolean;
148
+ /** Actual output if different */
149
+ actualOutput?: string;
150
+ /** Error if execution failed */
151
+ error?: string;
152
+ }
153
+
154
+ /** Contract verification result for a single rule */
155
+ export interface ContractVerificationResult {
156
+ ruleId: string;
157
+ /** Example verification results */
158
+ examples: ExampleVerification[];
159
+ /** Whether all examples passed */
160
+ allPassed: boolean;
161
+ /** Number of passing examples */
162
+ passCount: number;
163
+ /** Number of failing examples */
164
+ failCount: number;
165
+ }
166
+
167
+ /** Invariant check result */
168
+ export interface InvariantCheck {
169
+ /** The invariant statement */
170
+ invariant: string;
171
+ /** The rule it belongs to */
172
+ ruleId: string;
173
+ /** Whether it holds */
174
+ holds: boolean;
175
+ /** Explanation */
176
+ explanation: string;
177
+ }
178
+
179
+ /** Contract gap finding (deeper than registration-time) */
180
+ export interface ContractCoverageGap {
181
+ ruleId: string;
182
+ /** What's not covered */
183
+ description: string;
184
+ /** Type of gap */
185
+ type: 'missing-edge-case' | 'missing-error-path' | 'missing-boundary' | 'cross-reference-broken';
186
+ }
187
+
188
+ /** Cross-reference result */
189
+ export interface CrossReference {
190
+ /** Rule whose contract references another rule's facts */
191
+ sourceRuleId: string;
192
+ /** The referenced fact tag */
193
+ referencedFactTag: string;
194
+ /** The rule that produces the referenced fact (or null if missing) */
195
+ producerRuleId: string | null;
196
+ /** Whether the producer exists */
197
+ valid: boolean;
198
+ }
199
+
200
+ // ─── Suggestions ────────────────────────────────────────────────────────────
201
+
202
+ /** Types of findings that can generate suggestions */
203
+ export type FindingType = 'dead-rule' | 'gap' | 'contradiction' | 'unreachable-state' | 'shadowed-rule' | 'contract-gap';
204
+
205
+ /** An actionable fix suggestion */
206
+ export interface Suggestion {
207
+ /** What finding this addresses */
208
+ findingType: FindingType;
209
+ /** Related entity ID */
210
+ entityId: string;
211
+ /** Human-readable suggestion */
212
+ message: string;
213
+ /** Suggested action type */
214
+ action: 'remove' | 'add-rule' | 'modify' | 'merge' | 'add-priority' | 'add-event-type' | 'add-contract';
215
+ /** Priority (higher = more important) */
216
+ priority: number;
217
+ /** Optional code skeleton */
218
+ skeleton?: string;
219
+ }
220
+
221
+ // ─── Full Analysis Report ───────────────────────────────────────────────────
222
+
223
+ /** The complete analysis report from the decision ledger analyzer */
224
+ export interface AnalysisReport {
225
+ /** Timestamp of the analysis */
226
+ timestamp: string;
227
+ /** Fact derivation chains */
228
+ factDerivationChains: DerivationChain[];
229
+ /** Rules that can never fire */
230
+ deadRules: DeadRule[];
231
+ /** Fact combos no rule can produce */
232
+ unreachableStates: UnreachableState[];
233
+ /** Rules always overshadowed by another */
234
+ shadowedRules: ShadowedRule[];
235
+ /** Rules producing conflicting facts */
236
+ contradictions: Contradiction[];
237
+ /** Expected behaviors with no covering rule */
238
+ gaps: Gap[];
239
+ /** Actionable fix suggestions */
240
+ suggestions: Suggestion[];
241
+ /** Summary statistics */
242
+ summary: {
243
+ totalRules: number;
244
+ totalConstraints: number;
245
+ deadRuleCount: number;
246
+ unreachableStateCount: number;
247
+ shadowedRuleCount: number;
248
+ contradictionCount: number;
249
+ gapCount: number;
250
+ suggestionCount: number;
251
+ healthScore: number; // 0-100
252
+ };
253
+ }
254
+
255
+ // ─── Ledger Diff ────────────────────────────────────────────────────────────
256
+
257
+ /** A change between two analysis runs */
258
+ export interface LedgerDiffEntry {
259
+ type: 'added' | 'removed' | 'changed';
260
+ category: 'dead-rule' | 'unreachable-state' | 'shadowed-rule' | 'contradiction' | 'gap' | 'suggestion';
261
+ description: string;
262
+ /** Entity ID (rule, state, etc.) */
263
+ entityId: string;
264
+ }
265
+
266
+ /** Diff between two analysis reports */
267
+ export interface LedgerDiff {
268
+ /** Timestamp of the diff */
269
+ timestamp: string;
270
+ /** Before report timestamp */
271
+ beforeTimestamp: string;
272
+ /** After report timestamp */
273
+ afterTimestamp: string;
274
+ /** Changes found */
275
+ changes: LedgerDiffEntry[];
276
+ /** Score change */
277
+ scoreDelta: number;
278
+ /** Summary */
279
+ summary: string;
280
+ }