@plures/praxis 1.2.0 → 1.2.10
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/README.md +10 -96
- package/dist/browser/{adapter-TM4IS5KT.js → adapter-CIMBGDC7.js} +5 -3
- package/dist/browser/{chunk-LE2ZJYFC.js → chunk-K377RW4V.js} +76 -0
- package/dist/{node/chunk-JQ64KMLN.js → browser/chunk-MBVHLOU2.js} +12 -1
- package/dist/browser/index.d.ts +32 -5
- package/dist/browser/index.js +15 -7
- package/dist/browser/integrations/svelte.d.ts +2 -2
- package/dist/browser/integrations/svelte.js +1 -1
- package/dist/browser/{reactive-engine.svelte-C9OpcTHf.d.ts → reactive-engine.svelte-9aS0kTa8.d.ts} +136 -1
- package/dist/node/{adapter-K6DOX6XS.js → adapter-75ISSMWD.js} +5 -3
- package/dist/node/chunk-5RH7UAQC.js +486 -0
- package/dist/{browser/chunk-JQ64KMLN.js → node/chunk-MBVHLOU2.js} +12 -1
- package/dist/node/{chunk-LE2ZJYFC.js → chunk-PRPQO6R5.js} +3 -72
- package/dist/node/chunk-R2PSBPKQ.js +150 -0
- package/dist/node/chunk-WZ6B3LZ6.js +638 -0
- package/dist/node/cli/index.cjs +2316 -832
- package/dist/node/cli/index.js +18 -0
- package/dist/node/components/index.d.cts +3 -2
- package/dist/node/components/index.d.ts +3 -2
- package/dist/node/index.cjs +620 -38
- package/dist/node/index.d.cts +259 -5
- package/dist/node/index.d.ts +259 -5
- package/dist/node/index.js +55 -65
- package/dist/node/integrations/svelte.cjs +76 -0
- package/dist/node/integrations/svelte.d.cts +2 -2
- package/dist/node/integrations/svelte.d.ts +2 -2
- package/dist/node/integrations/svelte.js +2 -1
- package/dist/node/{reactive-engine.svelte-1M4m_C_v.d.cts → reactive-engine.svelte-BFIZfawz.d.cts} +199 -1
- package/dist/node/{reactive-engine.svelte-ChNFn4Hj.d.ts → reactive-engine.svelte-CRNqHlbv.d.ts} +199 -1
- package/dist/node/reverse-W7THPV45.js +193 -0
- package/dist/node/{terminal-adapter-CWka-yL8.d.ts → terminal-adapter-B-UK_Vdz.d.ts} +28 -3
- package/dist/node/{terminal-adapter-CDzxoLKR.d.cts → terminal-adapter-BQSIF5bf.d.cts} +28 -3
- package/dist/node/validate-CNHUULQE.js +180 -0
- package/docs/core/pluresdb-integration.md +15 -15
- package/docs/decision-ledger/BEHAVIOR_LEDGER.md +225 -0
- package/docs/decision-ledger/DecisionLedger.tla +180 -0
- package/docs/decision-ledger/IMPLEMENTATION_SUMMARY.md +217 -0
- package/docs/decision-ledger/LATEST.md +166 -0
- package/docs/guides/cicd-pipeline.md +142 -0
- package/package.json +2 -2
- package/src/__tests__/cli-validate.test.ts +197 -0
- package/src/__tests__/decision-ledger.test.ts +485 -0
- package/src/__tests__/reverse-generator.test.ts +189 -0
- package/src/__tests__/scanner.test.ts +215 -0
- package/src/cli/commands/reverse.ts +289 -0
- package/src/cli/commands/validate.ts +264 -0
- package/src/cli/index.ts +47 -0
- package/src/core/pluresdb/adapter.ts +45 -2
- package/src/core/rules.ts +133 -0
- package/src/decision-ledger/README.md +400 -0
- package/src/decision-ledger/REVERSE_ENGINEERING.md +484 -0
- package/src/decision-ledger/facts-events.ts +121 -0
- package/src/decision-ledger/index.ts +70 -0
- package/src/decision-ledger/ledger.ts +246 -0
- package/src/decision-ledger/logic-ledger.ts +158 -0
- package/src/decision-ledger/reverse-generator.ts +426 -0
- package/src/decision-ledger/scanner.ts +506 -0
- package/src/decision-ledger/types.ts +247 -0
- package/src/decision-ledger/validation.ts +336 -0
- package/src/dsl/index.ts +13 -2
- package/src/index.browser.ts +2 -0
- package/src/index.ts +36 -0
- package/src/integrations/pluresdb.ts +14 -2
package/dist/browser/{reactive-engine.svelte-C9OpcTHf.d.ts → reactive-engine.svelte-9aS0kTa8.d.ts}
RENAMED
|
@@ -119,6 +119,105 @@ interface PraxisStepResult {
|
|
|
119
119
|
*/
|
|
120
120
|
type PraxisStepFn = (state: PraxisState, events: PraxisEvent[], config: PraxisStepConfig) => PraxisStepResult;
|
|
121
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Decision Ledger - Contract Types
|
|
124
|
+
*
|
|
125
|
+
* Types for defining and validating contracts for rules and constraints.
|
|
126
|
+
* All types are JSON-serializable for cross-language compatibility.
|
|
127
|
+
*/
|
|
128
|
+
/**
|
|
129
|
+
* A single assumption made during contract definition.
|
|
130
|
+
*/
|
|
131
|
+
interface Assumption {
|
|
132
|
+
/** Stable unique identifier for the assumption */
|
|
133
|
+
id: string;
|
|
134
|
+
/** The assumption statement */
|
|
135
|
+
statement: string;
|
|
136
|
+
/** Confidence level (0.0 to 1.0) */
|
|
137
|
+
confidence: number;
|
|
138
|
+
/** Justification for the assumption */
|
|
139
|
+
justification: string;
|
|
140
|
+
/** What this assumption was derived from */
|
|
141
|
+
derivedFrom?: string;
|
|
142
|
+
/** What artifacts this assumption impacts */
|
|
143
|
+
impacts: Array<'spec' | 'tests' | 'code'>;
|
|
144
|
+
/** Current status of the assumption */
|
|
145
|
+
status: 'active' | 'revised' | 'invalidated';
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* A reference to external documentation or resources.
|
|
149
|
+
*/
|
|
150
|
+
interface Reference {
|
|
151
|
+
/** Type of reference (e.g., 'doc', 'ticket', 'issue') */
|
|
152
|
+
type: string;
|
|
153
|
+
/** URL to the reference */
|
|
154
|
+
url?: string;
|
|
155
|
+
/** Human-readable description */
|
|
156
|
+
description?: string;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* A Given/When/Then example for a contract.
|
|
160
|
+
*/
|
|
161
|
+
interface Example {
|
|
162
|
+
/** Initial state or preconditions */
|
|
163
|
+
given: string;
|
|
164
|
+
/** Triggering event or action */
|
|
165
|
+
when: string;
|
|
166
|
+
/** Expected outcome or postconditions */
|
|
167
|
+
then: string;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Contract for a rule or constraint.
|
|
171
|
+
* Documents the expected behavior, test cases, invariants, and assumptions.
|
|
172
|
+
*/
|
|
173
|
+
interface Contract {
|
|
174
|
+
/** ID of the rule or constraint this contract applies to */
|
|
175
|
+
ruleId: string;
|
|
176
|
+
/** Canonical behavior description */
|
|
177
|
+
behavior: string;
|
|
178
|
+
/** Given/When/Then examples (become test vectors) */
|
|
179
|
+
examples: Example[];
|
|
180
|
+
/** TLA+-friendly invariants or Praxis-level invariants */
|
|
181
|
+
invariants: string[];
|
|
182
|
+
/** Explicit assumptions with confidence levels */
|
|
183
|
+
assumptions?: Assumption[];
|
|
184
|
+
/** References to docs, tickets, links */
|
|
185
|
+
references?: Reference[];
|
|
186
|
+
/** Contract version (for evolution tracking) */
|
|
187
|
+
version?: string;
|
|
188
|
+
/** Timestamp of contract creation */
|
|
189
|
+
timestamp?: string;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Severity levels for contract gaps.
|
|
193
|
+
*/
|
|
194
|
+
type Severity = 'warning' | 'error' | 'info';
|
|
195
|
+
/**
|
|
196
|
+
* Types of missing contract artifacts.
|
|
197
|
+
*/
|
|
198
|
+
/**
|
|
199
|
+
* Types of artifacts that can be missing from a contract.
|
|
200
|
+
*
|
|
201
|
+
* Note: 'tests' and 'spec' are included in this type for future extensibility
|
|
202
|
+
* and SARIF reporting compatibility, but are not currently validated by the
|
|
203
|
+
* validateContract function. To check for these, implement custom validation
|
|
204
|
+
* logic that scans for test files or spec files in your codebase.
|
|
205
|
+
*/
|
|
206
|
+
type MissingArtifact = 'behavior' | 'examples' | 'invariants' | 'tests' | 'spec' | 'contract';
|
|
207
|
+
/**
|
|
208
|
+
* A gap in contract coverage.
|
|
209
|
+
*/
|
|
210
|
+
interface ContractGap {
|
|
211
|
+
/** ID of the rule or constraint */
|
|
212
|
+
ruleId: string;
|
|
213
|
+
/** What is missing */
|
|
214
|
+
missing: MissingArtifact[];
|
|
215
|
+
/** Severity of the gap */
|
|
216
|
+
severity: Severity;
|
|
217
|
+
/** Optional human-readable message */
|
|
218
|
+
message?: string;
|
|
219
|
+
}
|
|
220
|
+
|
|
122
221
|
/**
|
|
123
222
|
* Rules and Constraints System
|
|
124
223
|
*
|
|
@@ -166,6 +265,8 @@ interface RuleDescriptor<TContext = unknown> {
|
|
|
166
265
|
description: string;
|
|
167
266
|
/** Implementation function */
|
|
168
267
|
impl: RuleFn<TContext>;
|
|
268
|
+
/** Optional contract for rule behavior */
|
|
269
|
+
contract?: Contract;
|
|
169
270
|
/** Optional metadata */
|
|
170
271
|
meta?: Record<string, unknown>;
|
|
171
272
|
}
|
|
@@ -179,6 +280,8 @@ interface ConstraintDescriptor<TContext = unknown> {
|
|
|
179
280
|
description: string;
|
|
180
281
|
/** Implementation function */
|
|
181
282
|
impl: ConstraintFn<TContext>;
|
|
283
|
+
/** Optional contract for constraint behavior */
|
|
284
|
+
contract?: Contract;
|
|
182
285
|
/** Optional metadata */
|
|
183
286
|
meta?: Record<string, unknown>;
|
|
184
287
|
}
|
|
@@ -194,6 +297,25 @@ interface PraxisModule<TContext = unknown> {
|
|
|
194
297
|
/** Optional module metadata */
|
|
195
298
|
meta?: Record<string, unknown>;
|
|
196
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* Compliance validation options for rule/constraint registration.
|
|
302
|
+
*/
|
|
303
|
+
interface RegistryComplianceOptions {
|
|
304
|
+
/** Enable contract checks during registration (default: true in dev) */
|
|
305
|
+
enabled?: boolean;
|
|
306
|
+
/** Required contract fields to be present */
|
|
307
|
+
requiredFields?: Array<'behavior' | 'examples' | 'invariants'>;
|
|
308
|
+
/** Severity to use for missing contracts */
|
|
309
|
+
missingSeverity?: Severity;
|
|
310
|
+
/** Callback for contract gaps (e.g., to emit facts) */
|
|
311
|
+
onGap?: (gap: ContractGap) => void;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* PraxisRegistry configuration options.
|
|
315
|
+
*/
|
|
316
|
+
interface PraxisRegistryOptions {
|
|
317
|
+
compliance?: RegistryComplianceOptions;
|
|
318
|
+
}
|
|
197
319
|
/**
|
|
198
320
|
* Registry for rules and constraints.
|
|
199
321
|
* Maps IDs to their descriptors.
|
|
@@ -201,6 +323,9 @@ interface PraxisModule<TContext = unknown> {
|
|
|
201
323
|
declare class PraxisRegistry<TContext = unknown> {
|
|
202
324
|
private rules;
|
|
203
325
|
private constraints;
|
|
326
|
+
private readonly compliance;
|
|
327
|
+
private contractGaps;
|
|
328
|
+
constructor(options?: PraxisRegistryOptions);
|
|
204
329
|
/**
|
|
205
330
|
* Register a rule
|
|
206
331
|
*/
|
|
@@ -237,6 +362,16 @@ declare class PraxisRegistry<TContext = unknown> {
|
|
|
237
362
|
* Get all constraints
|
|
238
363
|
*/
|
|
239
364
|
getAllConstraints(): ConstraintDescriptor<TContext>[];
|
|
365
|
+
/**
|
|
366
|
+
* Get collected contract gaps from registration-time validation.
|
|
367
|
+
*/
|
|
368
|
+
getContractGaps(): ContractGap[];
|
|
369
|
+
/**
|
|
370
|
+
* Clear collected contract gaps.
|
|
371
|
+
*/
|
|
372
|
+
clearContractGaps(): void;
|
|
373
|
+
private trackContractCompliance;
|
|
374
|
+
private validateDescriptorContract;
|
|
240
375
|
}
|
|
241
376
|
|
|
242
377
|
/**
|
|
@@ -416,4 +551,4 @@ declare class ReactiveLogicEngine<TContext extends object> {
|
|
|
416
551
|
*/
|
|
417
552
|
declare function createReactiveEngine<TContext extends object>(options: ReactiveEngineOptions<TContext>): ReactiveLogicEngine<TContext>;
|
|
418
553
|
|
|
419
|
-
export { type ConstraintDescriptor as C, LogicEngine as L, type PraxisState as P, type RuleDescriptor as R, type PraxisEvent as a, PraxisRegistry as b, type PraxisFact as c, type RuleFn as d, type
|
|
554
|
+
export { type ConstraintDescriptor as C, LogicEngine as L, type PraxisState as P, type RuleDescriptor as R, type PraxisEvent as a, PraxisRegistry as b, type PraxisFact as c, type RuleFn as d, type Contract as e, type ConstraintFn as f, type PraxisModule as g, type PraxisDiagnostics as h, type PraxisStepConfig as i, type PraxisStepResult as j, type PraxisStepFn as k, PRAXIS_PROTOCOL_VERSION as l, type RuleId as m, type ConstraintId as n, type PraxisEngineOptions as o, createPraxisEngine as p, type ReactiveEngineOptions as q, ReactiveLogicEngine as r, createReactiveEngine as s };
|
|
@@ -2,12 +2,14 @@ import {
|
|
|
2
2
|
InMemoryPraxisDB,
|
|
3
3
|
PluresDBPraxisAdapter,
|
|
4
4
|
createInMemoryDB,
|
|
5
|
-
createPluresDB
|
|
6
|
-
|
|
5
|
+
createPluresDB,
|
|
6
|
+
createPraxisLocalFirst
|
|
7
|
+
} from "./chunk-MBVHLOU2.js";
|
|
7
8
|
import "./chunk-QGM4M3NI.js";
|
|
8
9
|
export {
|
|
9
10
|
InMemoryPraxisDB,
|
|
10
11
|
PluresDBPraxisAdapter,
|
|
11
12
|
createInMemoryDB,
|
|
12
|
-
createPluresDB
|
|
13
|
+
createPluresDB,
|
|
14
|
+
createPraxisLocalFirst
|
|
13
15
|
};
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getContractFromDescriptor
|
|
3
|
+
} from "./chunk-WZ6B3LZ6.js";
|
|
4
|
+
|
|
5
|
+
// src/dsl/index.ts
|
|
6
|
+
function defineFact(tag) {
|
|
7
|
+
return {
|
|
8
|
+
tag,
|
|
9
|
+
create(payload) {
|
|
10
|
+
return { tag, payload };
|
|
11
|
+
},
|
|
12
|
+
is(fact) {
|
|
13
|
+
return fact.tag === tag;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function defineEvent(tag) {
|
|
18
|
+
return {
|
|
19
|
+
tag,
|
|
20
|
+
create(payload) {
|
|
21
|
+
return { tag, payload };
|
|
22
|
+
},
|
|
23
|
+
is(event) {
|
|
24
|
+
return event.tag === tag;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function defineRule(options) {
|
|
29
|
+
const contract = options.contract ?? options.meta?.contract;
|
|
30
|
+
const meta = contract ? { ...options.meta ?? {}, contract } : options.meta;
|
|
31
|
+
return {
|
|
32
|
+
id: options.id,
|
|
33
|
+
description: options.description,
|
|
34
|
+
impl: options.impl,
|
|
35
|
+
contract,
|
|
36
|
+
meta
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function defineConstraint(options) {
|
|
40
|
+
const contract = options.contract ?? options.meta?.contract;
|
|
41
|
+
const meta = contract ? { ...options.meta ?? {}, contract } : options.meta;
|
|
42
|
+
return {
|
|
43
|
+
id: options.id,
|
|
44
|
+
description: options.description,
|
|
45
|
+
impl: options.impl,
|
|
46
|
+
contract,
|
|
47
|
+
meta
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function defineModule(options) {
|
|
51
|
+
return {
|
|
52
|
+
rules: options.rules ?? [],
|
|
53
|
+
constraints: options.constraints ?? [],
|
|
54
|
+
meta: options.meta
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function filterEvents(events, definition) {
|
|
58
|
+
return events.filter(definition.is);
|
|
59
|
+
}
|
|
60
|
+
function filterFacts(facts, definition) {
|
|
61
|
+
return facts.filter(definition.is);
|
|
62
|
+
}
|
|
63
|
+
function findEvent(events, definition) {
|
|
64
|
+
return events.find(definition.is);
|
|
65
|
+
}
|
|
66
|
+
function findFact(facts, definition) {
|
|
67
|
+
return facts.find(definition.is);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/decision-ledger/facts-events.ts
|
|
71
|
+
var ContractMissing = defineFact("ContractMissing");
|
|
72
|
+
var ContractValidated = defineFact("ContractValidated");
|
|
73
|
+
var AcknowledgeContractGap = defineEvent("ACKNOWLEDGE_CONTRACT_GAP");
|
|
74
|
+
var ValidateContracts = defineEvent("VALIDATE_CONTRACTS");
|
|
75
|
+
var ContractGapAcknowledged = defineFact("ContractGapAcknowledged");
|
|
76
|
+
var ContractAdded = defineEvent("CONTRACT_ADDED");
|
|
77
|
+
var ContractUpdated = defineEvent("CONTRACT_UPDATED");
|
|
78
|
+
var ContractGapEmitted = defineEvent("CONTRACT_GAP_EMITTED");
|
|
79
|
+
|
|
80
|
+
// src/decision-ledger/validation.ts
|
|
81
|
+
function validateContracts(registry, options = {}) {
|
|
82
|
+
const {
|
|
83
|
+
incompleteSeverity = "warning",
|
|
84
|
+
requiredFields = ["behavior", "examples"],
|
|
85
|
+
artifactIndex
|
|
86
|
+
} = options;
|
|
87
|
+
const complete = [];
|
|
88
|
+
const incomplete = [];
|
|
89
|
+
const missing = [];
|
|
90
|
+
for (const rule of registry.getAllRules()) {
|
|
91
|
+
const contract = getContractFromDescriptor(rule);
|
|
92
|
+
if (!contract) {
|
|
93
|
+
missing.push(rule.id);
|
|
94
|
+
if (options.missingSeverity) {
|
|
95
|
+
incomplete.push({
|
|
96
|
+
ruleId: rule.id,
|
|
97
|
+
missing: ["contract"],
|
|
98
|
+
severity: options.missingSeverity,
|
|
99
|
+
message: `Rule '${rule.id}' has no contract`
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const gaps = validateContract(contract, requiredFields, artifactIndex);
|
|
105
|
+
if (gaps.length > 0) {
|
|
106
|
+
incomplete.push({
|
|
107
|
+
ruleId: rule.id,
|
|
108
|
+
missing: gaps,
|
|
109
|
+
severity: incompleteSeverity,
|
|
110
|
+
message: `Rule '${rule.id}' contract is incomplete: missing ${gaps.join(", ")}`
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
complete.push({ ruleId: rule.id, contract });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const constraint of registry.getAllConstraints()) {
|
|
117
|
+
const contract = getContractFromDescriptor(constraint);
|
|
118
|
+
if (!contract) {
|
|
119
|
+
missing.push(constraint.id);
|
|
120
|
+
if (options.missingSeverity) {
|
|
121
|
+
incomplete.push({
|
|
122
|
+
ruleId: constraint.id,
|
|
123
|
+
missing: ["contract"],
|
|
124
|
+
severity: options.missingSeverity,
|
|
125
|
+
message: `Constraint '${constraint.id}' has no contract`
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const gaps = validateContract(contract, requiredFields, artifactIndex);
|
|
131
|
+
if (gaps.length > 0) {
|
|
132
|
+
incomplete.push({
|
|
133
|
+
ruleId: constraint.id,
|
|
134
|
+
missing: gaps,
|
|
135
|
+
severity: incompleteSeverity,
|
|
136
|
+
message: `Constraint '${constraint.id}' contract is incomplete: missing ${gaps.join(", ")}`
|
|
137
|
+
});
|
|
138
|
+
} else {
|
|
139
|
+
complete.push({ ruleId: constraint.id, contract });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const total = registry.getAllRules().length + registry.getAllConstraints().length;
|
|
143
|
+
return {
|
|
144
|
+
complete,
|
|
145
|
+
incomplete,
|
|
146
|
+
missing,
|
|
147
|
+
total,
|
|
148
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function validateContract(contract, requiredFields, artifactIndex) {
|
|
152
|
+
const missing = [];
|
|
153
|
+
if (requiredFields.includes("behavior") && isFieldEmpty(contract.behavior)) {
|
|
154
|
+
missing.push("behavior");
|
|
155
|
+
}
|
|
156
|
+
if (requiredFields.includes("examples") && (!contract.examples || contract.examples.length === 0)) {
|
|
157
|
+
missing.push("examples");
|
|
158
|
+
}
|
|
159
|
+
if (requiredFields.includes("invariants") && (!contract.invariants || contract.invariants.length === 0)) {
|
|
160
|
+
missing.push("invariants");
|
|
161
|
+
}
|
|
162
|
+
if (artifactIndex?.tests && !artifactIndex.tests.has(contract.ruleId)) {
|
|
163
|
+
missing.push("tests");
|
|
164
|
+
}
|
|
165
|
+
if (artifactIndex?.spec && !artifactIndex.spec.has(contract.ruleId)) {
|
|
166
|
+
missing.push("spec");
|
|
167
|
+
}
|
|
168
|
+
return missing;
|
|
169
|
+
}
|
|
170
|
+
function isFieldEmpty(value) {
|
|
171
|
+
return !value || value.trim() === "";
|
|
172
|
+
}
|
|
173
|
+
function formatValidationReport(report) {
|
|
174
|
+
const lines = [];
|
|
175
|
+
lines.push("Contract Validation Report");
|
|
176
|
+
lines.push("=".repeat(50));
|
|
177
|
+
lines.push("");
|
|
178
|
+
lines.push(`Total: ${report.total}`);
|
|
179
|
+
lines.push(`Complete: ${report.complete.length}`);
|
|
180
|
+
lines.push(`Incomplete: ${report.incomplete.length}`);
|
|
181
|
+
lines.push(`Missing: ${report.missing.length}`);
|
|
182
|
+
lines.push("");
|
|
183
|
+
if (report.complete.length > 0) {
|
|
184
|
+
lines.push("\u2713 Complete Contracts:");
|
|
185
|
+
for (const { ruleId, contract } of report.complete) {
|
|
186
|
+
lines.push(` \u2713 ${ruleId} (v${contract.version || "1.0.0"})`);
|
|
187
|
+
}
|
|
188
|
+
lines.push("");
|
|
189
|
+
}
|
|
190
|
+
if (report.incomplete.length > 0) {
|
|
191
|
+
lines.push("\u2717 Incomplete Contracts:");
|
|
192
|
+
for (const gap of report.incomplete) {
|
|
193
|
+
const icon = gap.severity === "error" ? "\u2717" : gap.severity === "warning" ? "\u26A0" : "\u2139";
|
|
194
|
+
lines.push(` ${icon} ${gap.ruleId} - Missing: ${gap.missing.join(", ")}`);
|
|
195
|
+
if (gap.message) {
|
|
196
|
+
lines.push(` ${gap.message}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
lines.push("");
|
|
200
|
+
}
|
|
201
|
+
if (report.missing.length > 0) {
|
|
202
|
+
lines.push("\u2717 No Contract:");
|
|
203
|
+
for (const ruleId of report.missing) {
|
|
204
|
+
lines.push(` \u2717 ${ruleId}`);
|
|
205
|
+
}
|
|
206
|
+
lines.push("");
|
|
207
|
+
}
|
|
208
|
+
lines.push(`Validated at: ${report.timestamp}`);
|
|
209
|
+
return lines.join("\n");
|
|
210
|
+
}
|
|
211
|
+
function formatValidationReportJSON(report) {
|
|
212
|
+
return JSON.stringify(report, null, 2);
|
|
213
|
+
}
|
|
214
|
+
function formatValidationReportSARIF(report) {
|
|
215
|
+
const results = report.incomplete.map((gap) => {
|
|
216
|
+
const primaryMissing = gap.missing.length > 0 ? gap.missing[0] : "contract";
|
|
217
|
+
return {
|
|
218
|
+
ruleId: `decision-ledger/${primaryMissing}`,
|
|
219
|
+
level: gap.severity === "error" ? "error" : gap.severity === "warning" ? "warning" : "note",
|
|
220
|
+
message: {
|
|
221
|
+
text: gap.message || `Missing: ${gap.missing.join(", ")}`
|
|
222
|
+
},
|
|
223
|
+
locations: [
|
|
224
|
+
{
|
|
225
|
+
physicalLocation: {
|
|
226
|
+
artifactLocation: {
|
|
227
|
+
uri: "registry"
|
|
228
|
+
},
|
|
229
|
+
region: {
|
|
230
|
+
startLine: 1
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
],
|
|
235
|
+
properties: {
|
|
236
|
+
ruleId: gap.ruleId,
|
|
237
|
+
missing: gap.missing
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
const sarif = {
|
|
242
|
+
version: "2.1.0",
|
|
243
|
+
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
|
|
244
|
+
runs: [
|
|
245
|
+
{
|
|
246
|
+
tool: {
|
|
247
|
+
driver: {
|
|
248
|
+
name: "Praxis Decision Ledger",
|
|
249
|
+
version: "1.0.0",
|
|
250
|
+
informationUri: "https://github.com/plures/praxis",
|
|
251
|
+
rules: [
|
|
252
|
+
{
|
|
253
|
+
id: "decision-ledger/contract",
|
|
254
|
+
shortDescription: {
|
|
255
|
+
text: "Rule or constraint missing contract"
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: "decision-ledger/behavior",
|
|
260
|
+
shortDescription: {
|
|
261
|
+
text: "Contract missing behavior description"
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: "decision-ledger/examples",
|
|
266
|
+
shortDescription: {
|
|
267
|
+
text: "Contract missing examples"
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: "decision-ledger/invariants",
|
|
272
|
+
shortDescription: {
|
|
273
|
+
text: "Contract missing invariants"
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
id: "decision-ledger/tests",
|
|
278
|
+
shortDescription: {
|
|
279
|
+
text: "Contract missing tests"
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: "decision-ledger/spec",
|
|
284
|
+
shortDescription: {
|
|
285
|
+
text: "Contract missing spec"
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
results
|
|
292
|
+
}
|
|
293
|
+
]
|
|
294
|
+
};
|
|
295
|
+
return JSON.stringify(sarif, null, 2);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// src/decision-ledger/ledger.ts
|
|
299
|
+
var BehaviorLedger = class _BehaviorLedger {
|
|
300
|
+
entries = [];
|
|
301
|
+
entryMap = /* @__PURE__ */ new Map();
|
|
302
|
+
/**
|
|
303
|
+
* Append a new entry to the ledger.
|
|
304
|
+
*
|
|
305
|
+
* @param entry The entry to append
|
|
306
|
+
* @throws Error if entry ID already exists
|
|
307
|
+
*/
|
|
308
|
+
append(entry) {
|
|
309
|
+
if (this.entryMap.has(entry.id)) {
|
|
310
|
+
throw new Error(`Ledger entry with ID '${entry.id}' already exists`);
|
|
311
|
+
}
|
|
312
|
+
if (entry.supersedes) {
|
|
313
|
+
const superseded = this.entryMap.get(entry.supersedes);
|
|
314
|
+
if (superseded && superseded.status === "active") {
|
|
315
|
+
const updatedEntry = {
|
|
316
|
+
...superseded,
|
|
317
|
+
status: "superseded"
|
|
318
|
+
};
|
|
319
|
+
this.entryMap.set(entry.supersedes, updatedEntry);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
this.entries.push(entry);
|
|
323
|
+
this.entryMap.set(entry.id, entry);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Get an entry by ID.
|
|
327
|
+
*
|
|
328
|
+
* @param id The entry ID
|
|
329
|
+
* @returns The entry, or undefined if not found
|
|
330
|
+
*/
|
|
331
|
+
getEntry(id) {
|
|
332
|
+
return this.entryMap.get(id);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Get all entries (in order of append) with current status.
|
|
336
|
+
*
|
|
337
|
+
* @returns Array of all entries with current status from the map
|
|
338
|
+
*/
|
|
339
|
+
getAllEntries() {
|
|
340
|
+
return this.entries.map((entry) => this.entryMap.get(entry.id));
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Get entries for a specific rule ID.
|
|
344
|
+
*
|
|
345
|
+
* @param ruleId The rule ID
|
|
346
|
+
* @returns Array of entries for this rule with current status
|
|
347
|
+
*/
|
|
348
|
+
getEntriesForRule(ruleId) {
|
|
349
|
+
return this.entries.map((entry) => this.entryMap.get(entry.id)).filter((entry) => entry.contract.ruleId === ruleId);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Get the latest active entry for a rule.
|
|
353
|
+
*
|
|
354
|
+
* @param ruleId The rule ID
|
|
355
|
+
* @returns The latest active entry, or undefined if none
|
|
356
|
+
*/
|
|
357
|
+
getLatestEntry(ruleId) {
|
|
358
|
+
const entries = this.getEntriesForRule(ruleId);
|
|
359
|
+
const activeEntries = entries.filter((entry) => entry.status === "active");
|
|
360
|
+
if (activeEntries.length === 0) {
|
|
361
|
+
return void 0;
|
|
362
|
+
}
|
|
363
|
+
return activeEntries[activeEntries.length - 1];
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Get all active assumptions across all entries.
|
|
367
|
+
*
|
|
368
|
+
* @returns Map of assumption ID to assumption
|
|
369
|
+
*/
|
|
370
|
+
getActiveAssumptions() {
|
|
371
|
+
const assumptions = /* @__PURE__ */ new Map();
|
|
372
|
+
for (const entry of this.entries) {
|
|
373
|
+
const currentEntry = this.entryMap.get(entry.id);
|
|
374
|
+
if (currentEntry.status !== "active") {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
for (const assumption of currentEntry.contract.assumptions || []) {
|
|
378
|
+
if (assumption.status === "active") {
|
|
379
|
+
assumptions.set(assumption.id, assumption);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return assumptions;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Find assumptions that impact a specific artifact type.
|
|
387
|
+
*
|
|
388
|
+
* @param impactType The artifact type ('spec', 'tests', 'code')
|
|
389
|
+
* @returns Array of assumptions
|
|
390
|
+
*/
|
|
391
|
+
findAssumptionsByImpact(impactType) {
|
|
392
|
+
const assumptions = [];
|
|
393
|
+
for (const entry of this.entries) {
|
|
394
|
+
const currentEntry = this.entryMap.get(entry.id);
|
|
395
|
+
if (currentEntry.status !== "active") {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
for (const assumption of currentEntry.contract.assumptions || []) {
|
|
399
|
+
if (assumption.status === "active" && assumption.impacts.includes(impactType)) {
|
|
400
|
+
assumptions.push(assumption);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return assumptions;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Get ledger statistics.
|
|
408
|
+
*/
|
|
409
|
+
getStats() {
|
|
410
|
+
const currentEntries = this.entries.map((e) => this.entryMap.get(e.id));
|
|
411
|
+
const active = currentEntries.filter((e) => e.status === "active").length;
|
|
412
|
+
const superseded = currentEntries.filter((e) => e.status === "superseded").length;
|
|
413
|
+
const deprecated = currentEntries.filter((e) => e.status === "deprecated").length;
|
|
414
|
+
const uniqueRules = new Set(currentEntries.map((e) => e.contract.ruleId)).size;
|
|
415
|
+
return {
|
|
416
|
+
totalEntries: this.entries.length,
|
|
417
|
+
activeEntries: active,
|
|
418
|
+
supersededEntries: superseded,
|
|
419
|
+
deprecatedEntries: deprecated,
|
|
420
|
+
uniqueRules
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Export ledger as JSON.
|
|
425
|
+
*
|
|
426
|
+
* @returns JSON string with current entry status
|
|
427
|
+
*/
|
|
428
|
+
toJSON() {
|
|
429
|
+
return JSON.stringify(
|
|
430
|
+
{
|
|
431
|
+
version: "1.0.0",
|
|
432
|
+
// Export entries with current status from the map
|
|
433
|
+
entries: this.entries.map((entry) => this.entryMap.get(entry.id)),
|
|
434
|
+
stats: this.getStats()
|
|
435
|
+
},
|
|
436
|
+
null,
|
|
437
|
+
2
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Import ledger from JSON.
|
|
442
|
+
*
|
|
443
|
+
* Note: The JSON must contain entries in the order they were originally appended.
|
|
444
|
+
* If a superseding entry appears before the entry it supersedes, the superseding
|
|
445
|
+
* logic will not work correctly. The toJSON method preserves this order.
|
|
446
|
+
*
|
|
447
|
+
* @param json The JSON string
|
|
448
|
+
* @returns A new BehaviorLedger instance
|
|
449
|
+
*/
|
|
450
|
+
static fromJSON(json) {
|
|
451
|
+
const data = JSON.parse(json);
|
|
452
|
+
const ledger = new _BehaviorLedger();
|
|
453
|
+
for (const entry of data.entries || []) {
|
|
454
|
+
ledger.append(entry);
|
|
455
|
+
}
|
|
456
|
+
return ledger;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
function createBehaviorLedger() {
|
|
460
|
+
return new BehaviorLedger();
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export {
|
|
464
|
+
defineFact,
|
|
465
|
+
defineEvent,
|
|
466
|
+
defineRule,
|
|
467
|
+
defineConstraint,
|
|
468
|
+
defineModule,
|
|
469
|
+
filterEvents,
|
|
470
|
+
filterFacts,
|
|
471
|
+
findEvent,
|
|
472
|
+
findFact,
|
|
473
|
+
ContractMissing,
|
|
474
|
+
ContractValidated,
|
|
475
|
+
AcknowledgeContractGap,
|
|
476
|
+
ValidateContracts,
|
|
477
|
+
ContractGapAcknowledged,
|
|
478
|
+
ContractAdded,
|
|
479
|
+
ContractUpdated,
|
|
480
|
+
validateContracts,
|
|
481
|
+
formatValidationReport,
|
|
482
|
+
formatValidationReportJSON,
|
|
483
|
+
formatValidationReportSARIF,
|
|
484
|
+
BehaviorLedger,
|
|
485
|
+
createBehaviorLedger
|
|
486
|
+
};
|
|
@@ -132,10 +132,21 @@ var PluresDBPraxisAdapter = class {
|
|
|
132
132
|
function createPluresDB(config) {
|
|
133
133
|
return new PluresDBPraxisAdapter(config);
|
|
134
134
|
}
|
|
135
|
+
async function createPraxisLocalFirst(options = {}) {
|
|
136
|
+
const { pollInterval, ...localOptions } = options;
|
|
137
|
+
const mod = await import("@plures/pluresdb/local-first");
|
|
138
|
+
const LocalFirstCtor = mod.PluresDBLocalFirst ?? mod.default;
|
|
139
|
+
if (!LocalFirstCtor) {
|
|
140
|
+
throw new Error("Failed to load PluresDBLocalFirst from @plures/pluresdb/local-first");
|
|
141
|
+
}
|
|
142
|
+
const db = new LocalFirstCtor(localOptions);
|
|
143
|
+
return new PluresDBPraxisAdapter({ db, pollInterval });
|
|
144
|
+
}
|
|
135
145
|
|
|
136
146
|
export {
|
|
137
147
|
InMemoryPraxisDB,
|
|
138
148
|
createInMemoryDB,
|
|
139
149
|
PluresDBPraxisAdapter,
|
|
140
|
-
createPluresDB
|
|
150
|
+
createPluresDB,
|
|
151
|
+
createPraxisLocalFirst
|
|
141
152
|
};
|