@plures/praxis 1.1.3 → 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/FRAMEWORK.md +106 -15
- package/README.md +194 -119
- package/dist/browser/adapter-CIMBGDC7.js +14 -0
- package/dist/browser/chunk-K377RW4V.js +230 -0
- package/dist/browser/chunk-MBVHLOU2.js +152 -0
- package/dist/browser/{chunk-R45WXWKH.js → chunk-VOMLVI6V.js} +1 -149
- package/dist/browser/engine-YJZV4SLD.js +8 -0
- package/dist/browser/index.d.ts +161 -5
- package/dist/browser/index.js +156 -141
- package/dist/browser/integrations/svelte.d.ts +2 -2
- package/dist/browser/integrations/svelte.js +2 -1
- package/dist/browser/{reactive-engine.svelte-C9OpcTHf.d.ts → reactive-engine.svelte-9aS0kTa8.d.ts} +136 -1
- package/dist/node/adapter-75ISSMWD.js +15 -0
- package/dist/node/chunk-5RH7UAQC.js +486 -0
- package/dist/node/chunk-MBVHLOU2.js +152 -0
- package/dist/node/chunk-PRPQO6R5.js +85 -0
- package/dist/node/chunk-R2PSBPKQ.js +150 -0
- package/dist/node/chunk-S54337I5.js +446 -0
- package/dist/node/{chunk-R45WXWKH.js → chunk-VOMLVI6V.js} +1 -149
- package/dist/node/chunk-WZ6B3LZ6.js +638 -0
- package/dist/node/cli/index.cjs +2936 -897
- package/dist/node/cli/index.js +27 -0
- package/dist/node/components/index.d.cts +3 -2
- package/dist/node/components/index.d.ts +3 -2
- package/dist/node/docs-JFNYTOJA.js +102 -0
- package/dist/node/engine-2DQBKBJC.js +9 -0
- package/dist/node/index.cjs +1114 -354
- package/dist/node/index.d.cts +388 -5
- package/dist/node/index.d.ts +388 -5
- package/dist/node/index.js +201 -640
- 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 +3 -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/docs.ts +147 -0
- package/src/cli/commands/reverse.ts +289 -0
- package/src/cli/commands/validate.ts +264 -0
- package/src/cli/index.ts +68 -0
- package/src/core/pluresdb/adapter.ts +46 -3
- package/src/core/reactive-engine.svelte.ts +6 -1
- package/src/core/reactive-engine.ts +1 -1
- 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 +6 -0
- package/src/index.ts +40 -0
- package/src/integrations/pluresdb.ts +14 -2
- package/src/integrations/unified.ts +350 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decision Ledger - Contract Types
|
|
3
|
+
*
|
|
4
|
+
* Types for defining and validating contracts for rules and constraints.
|
|
5
|
+
* All types are JSON-serializable for cross-language compatibility.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A single assumption made during contract definition.
|
|
10
|
+
*/
|
|
11
|
+
export interface Assumption {
|
|
12
|
+
/** Stable unique identifier for the assumption */
|
|
13
|
+
id: string;
|
|
14
|
+
/** The assumption statement */
|
|
15
|
+
statement: string;
|
|
16
|
+
/** Confidence level (0.0 to 1.0) */
|
|
17
|
+
confidence: number;
|
|
18
|
+
/** Justification for the assumption */
|
|
19
|
+
justification: string;
|
|
20
|
+
/** What this assumption was derived from */
|
|
21
|
+
derivedFrom?: string;
|
|
22
|
+
/** What artifacts this assumption impacts */
|
|
23
|
+
impacts: Array<'spec' | 'tests' | 'code'>;
|
|
24
|
+
/** Current status of the assumption */
|
|
25
|
+
status: 'active' | 'revised' | 'invalidated';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A reference to external documentation or resources.
|
|
30
|
+
*/
|
|
31
|
+
export interface Reference {
|
|
32
|
+
/** Type of reference (e.g., 'doc', 'ticket', 'issue') */
|
|
33
|
+
type: string;
|
|
34
|
+
/** URL to the reference */
|
|
35
|
+
url?: string;
|
|
36
|
+
/** Human-readable description */
|
|
37
|
+
description?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A Given/When/Then example for a contract.
|
|
42
|
+
*/
|
|
43
|
+
export interface Example {
|
|
44
|
+
/** Initial state or preconditions */
|
|
45
|
+
given: string;
|
|
46
|
+
/** Triggering event or action */
|
|
47
|
+
when: string;
|
|
48
|
+
/** Expected outcome or postconditions */
|
|
49
|
+
then: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Contract for a rule or constraint.
|
|
54
|
+
* Documents the expected behavior, test cases, invariants, and assumptions.
|
|
55
|
+
*/
|
|
56
|
+
export interface Contract {
|
|
57
|
+
/** ID of the rule or constraint this contract applies to */
|
|
58
|
+
ruleId: string;
|
|
59
|
+
/** Canonical behavior description */
|
|
60
|
+
behavior: string;
|
|
61
|
+
/** Given/When/Then examples (become test vectors) */
|
|
62
|
+
examples: Example[];
|
|
63
|
+
/** TLA+-friendly invariants or Praxis-level invariants */
|
|
64
|
+
invariants: string[];
|
|
65
|
+
/** Explicit assumptions with confidence levels */
|
|
66
|
+
assumptions?: Assumption[];
|
|
67
|
+
/** References to docs, tickets, links */
|
|
68
|
+
references?: Reference[];
|
|
69
|
+
/** Contract version (for evolution tracking) */
|
|
70
|
+
version?: string;
|
|
71
|
+
/** Timestamp of contract creation */
|
|
72
|
+
timestamp?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Type guard to check if an object is a valid Contract.
|
|
77
|
+
*/
|
|
78
|
+
export function isContract(obj: unknown): obj is Contract {
|
|
79
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const contract = obj as Partial<Contract>;
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
typeof contract.ruleId === 'string' &&
|
|
87
|
+
typeof contract.behavior === 'string' &&
|
|
88
|
+
Array.isArray(contract.examples) &&
|
|
89
|
+
contract.examples.length > 0 &&
|
|
90
|
+
contract.examples.every(
|
|
91
|
+
(ex) =>
|
|
92
|
+
typeof ex === 'object' &&
|
|
93
|
+
ex !== null &&
|
|
94
|
+
typeof ex.given === 'string' &&
|
|
95
|
+
typeof ex.when === 'string' &&
|
|
96
|
+
typeof ex.then === 'string'
|
|
97
|
+
) &&
|
|
98
|
+
Array.isArray(contract.invariants) &&
|
|
99
|
+
contract.invariants.every((inv) => typeof inv === 'string')
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Options for defining a contract.
|
|
105
|
+
*/
|
|
106
|
+
export interface DefineContractOptions {
|
|
107
|
+
/** ID of the rule or constraint */
|
|
108
|
+
ruleId: string;
|
|
109
|
+
/** Canonical behavior description */
|
|
110
|
+
behavior: string;
|
|
111
|
+
/** Given/When/Then examples */
|
|
112
|
+
examples: Example[];
|
|
113
|
+
/** Invariants that must hold */
|
|
114
|
+
invariants: string[];
|
|
115
|
+
/** Optional assumptions */
|
|
116
|
+
assumptions?: Assumption[];
|
|
117
|
+
/** Optional references */
|
|
118
|
+
references?: Reference[];
|
|
119
|
+
/** Optional version */
|
|
120
|
+
version?: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Define a contract for a rule or constraint.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* const loginContract = defineContract({
|
|
128
|
+
* ruleId: 'auth.login',
|
|
129
|
+
* behavior: 'Process login events and create user session facts',
|
|
130
|
+
* examples: [
|
|
131
|
+
* {
|
|
132
|
+
* given: 'User provides valid credentials',
|
|
133
|
+
* when: 'LOGIN event is received',
|
|
134
|
+
* then: 'UserSessionCreated fact is emitted'
|
|
135
|
+
* }
|
|
136
|
+
* ],
|
|
137
|
+
* invariants: ['Session must have unique ID']
|
|
138
|
+
* });
|
|
139
|
+
*/
|
|
140
|
+
export function defineContract(options: DefineContractOptions): Contract {
|
|
141
|
+
if (options.examples.length === 0) {
|
|
142
|
+
throw new Error('Contract must have at least one example');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Validate assumption confidence values are in range [0.0, 1.0]
|
|
146
|
+
if (options.assumptions) {
|
|
147
|
+
for (const assumption of options.assumptions) {
|
|
148
|
+
if (assumption.confidence < 0.0 || assumption.confidence > 1.0) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Assumption '${assumption.id}' has invalid confidence value ${assumption.confidence}. Must be between 0.0 and 1.0`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
ruleId: options.ruleId,
|
|
158
|
+
behavior: options.behavior,
|
|
159
|
+
examples: options.examples,
|
|
160
|
+
invariants: options.invariants,
|
|
161
|
+
assumptions: options.assumptions,
|
|
162
|
+
references: options.references,
|
|
163
|
+
version: options.version || '1.0.0',
|
|
164
|
+
timestamp: new Date().toISOString(),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Extract contract from rule/constraint metadata.
|
|
170
|
+
*/
|
|
171
|
+
export function getContract(meta?: Record<string, unknown>): Contract | undefined {
|
|
172
|
+
if (!meta || !meta.contract) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (isContract(meta.contract)) {
|
|
177
|
+
return meta.contract;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Extract contract from a rule or constraint descriptor.
|
|
185
|
+
*/
|
|
186
|
+
export function getContractFromDescriptor(descriptor?: {
|
|
187
|
+
contract?: Contract;
|
|
188
|
+
meta?: Record<string, unknown>;
|
|
189
|
+
}): Contract | undefined {
|
|
190
|
+
if (!descriptor) {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (descriptor.contract && isContract(descriptor.contract)) {
|
|
195
|
+
return descriptor.contract;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return getContract(descriptor.meta);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Severity levels for contract gaps.
|
|
203
|
+
*/
|
|
204
|
+
export type Severity = 'warning' | 'error' | 'info';
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Types of missing contract artifacts.
|
|
208
|
+
*/
|
|
209
|
+
/**
|
|
210
|
+
* Types of artifacts that can be missing from a contract.
|
|
211
|
+
*
|
|
212
|
+
* Note: 'tests' and 'spec' are included in this type for future extensibility
|
|
213
|
+
* and SARIF reporting compatibility, but are not currently validated by the
|
|
214
|
+
* validateContract function. To check for these, implement custom validation
|
|
215
|
+
* logic that scans for test files or spec files in your codebase.
|
|
216
|
+
*/
|
|
217
|
+
export type MissingArtifact = 'behavior' | 'examples' | 'invariants' | 'tests' | 'spec' | 'contract';
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* A gap in contract coverage.
|
|
221
|
+
*/
|
|
222
|
+
export interface ContractGap {
|
|
223
|
+
/** ID of the rule or constraint */
|
|
224
|
+
ruleId: string;
|
|
225
|
+
/** What is missing */
|
|
226
|
+
missing: MissingArtifact[];
|
|
227
|
+
/** Severity of the gap */
|
|
228
|
+
severity: Severity;
|
|
229
|
+
/** Optional human-readable message */
|
|
230
|
+
message?: string;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Result of contract validation.
|
|
235
|
+
*/
|
|
236
|
+
export interface ValidationReport {
|
|
237
|
+
/** Rules/constraints with complete contracts */
|
|
238
|
+
complete: Array<{ ruleId: string; contract: Contract }>;
|
|
239
|
+
/** Rules/constraints with incomplete contracts */
|
|
240
|
+
incomplete: ContractGap[];
|
|
241
|
+
/** Rules/constraints with no contract at all */
|
|
242
|
+
missing: string[];
|
|
243
|
+
/** Total number of rules/constraints validated */
|
|
244
|
+
total: number;
|
|
245
|
+
/** Timestamp of validation */
|
|
246
|
+
timestamp: string;
|
|
247
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decision Ledger - Validation
|
|
3
|
+
*
|
|
4
|
+
* Contract validation logic for rules and constraints.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PraxisRegistry } from '../core/rules.js';
|
|
8
|
+
import type {
|
|
9
|
+
Contract,
|
|
10
|
+
ContractGap,
|
|
11
|
+
ValidationReport,
|
|
12
|
+
MissingArtifact,
|
|
13
|
+
Severity,
|
|
14
|
+
} from './types.js';
|
|
15
|
+
import { getContractFromDescriptor } from './types.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for contract validation.
|
|
19
|
+
*/
|
|
20
|
+
export interface ValidateOptions {
|
|
21
|
+
/** Treat missing contracts as errors instead of warnings */
|
|
22
|
+
strict?: boolean;
|
|
23
|
+
/** Severity for missing contracts (default: 'warning') */
|
|
24
|
+
missingSeverity?: Severity;
|
|
25
|
+
/** Severity for incomplete contracts (default: 'warning') */
|
|
26
|
+
incompleteSeverity?: Severity;
|
|
27
|
+
/** Required contract fields */
|
|
28
|
+
requiredFields?: Array<'behavior' | 'examples' | 'invariants'>;
|
|
29
|
+
/** Optional index of artifacts for test/spec presence */
|
|
30
|
+
artifactIndex?: ArtifactIndex;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Artifact index for contract compliance checks.
|
|
35
|
+
*/
|
|
36
|
+
export interface ArtifactIndex {
|
|
37
|
+
/** Rule IDs that have associated tests */
|
|
38
|
+
tests?: Set<string>;
|
|
39
|
+
/** Rule IDs that have associated specs (e.g., TLA+) */
|
|
40
|
+
spec?: Set<string>;
|
|
41
|
+
/** Optional mapping of rule IDs to contract versions (for drift detection) */
|
|
42
|
+
contractVersions?: Map<string, string>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Validate contracts in a registry.
|
|
47
|
+
*
|
|
48
|
+
* @param registry The registry to validate
|
|
49
|
+
* @param options Validation options
|
|
50
|
+
* @returns Validation report
|
|
51
|
+
*/
|
|
52
|
+
export function validateContracts<TContext = unknown>(
|
|
53
|
+
registry: PraxisRegistry<TContext>,
|
|
54
|
+
options: ValidateOptions = {}
|
|
55
|
+
): ValidationReport {
|
|
56
|
+
const {
|
|
57
|
+
incompleteSeverity = 'warning',
|
|
58
|
+
requiredFields = ['behavior', 'examples'],
|
|
59
|
+
artifactIndex,
|
|
60
|
+
} = options;
|
|
61
|
+
|
|
62
|
+
const complete: Array<{ ruleId: string; contract: Contract }> = [];
|
|
63
|
+
const incomplete: ContractGap[] = [];
|
|
64
|
+
const missing: string[] = [];
|
|
65
|
+
|
|
66
|
+
// Validate rules
|
|
67
|
+
for (const rule of registry.getAllRules()) {
|
|
68
|
+
const contract = getContractFromDescriptor(rule);
|
|
69
|
+
|
|
70
|
+
if (!contract) {
|
|
71
|
+
missing.push(rule.id);
|
|
72
|
+
if (options.missingSeverity) {
|
|
73
|
+
incomplete.push({
|
|
74
|
+
ruleId: rule.id,
|
|
75
|
+
missing: ['contract'],
|
|
76
|
+
severity: options.missingSeverity,
|
|
77
|
+
message: `Rule '${rule.id}' has no contract`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const gaps = validateContract(contract, requiredFields, artifactIndex);
|
|
84
|
+
|
|
85
|
+
if (gaps.length > 0) {
|
|
86
|
+
incomplete.push({
|
|
87
|
+
ruleId: rule.id,
|
|
88
|
+
missing: gaps,
|
|
89
|
+
severity: incompleteSeverity,
|
|
90
|
+
message: `Rule '${rule.id}' contract is incomplete: missing ${gaps.join(', ')}`,
|
|
91
|
+
});
|
|
92
|
+
} else {
|
|
93
|
+
complete.push({ ruleId: rule.id, contract });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Validate constraints
|
|
98
|
+
for (const constraint of registry.getAllConstraints()) {
|
|
99
|
+
const contract = getContractFromDescriptor(constraint);
|
|
100
|
+
|
|
101
|
+
if (!contract) {
|
|
102
|
+
missing.push(constraint.id);
|
|
103
|
+
if (options.missingSeverity) {
|
|
104
|
+
incomplete.push({
|
|
105
|
+
ruleId: constraint.id,
|
|
106
|
+
missing: ['contract'],
|
|
107
|
+
severity: options.missingSeverity,
|
|
108
|
+
message: `Constraint '${constraint.id}' has no contract`,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const gaps = validateContract(contract, requiredFields, artifactIndex);
|
|
115
|
+
|
|
116
|
+
if (gaps.length > 0) {
|
|
117
|
+
incomplete.push({
|
|
118
|
+
ruleId: constraint.id,
|
|
119
|
+
missing: gaps,
|
|
120
|
+
severity: incompleteSeverity,
|
|
121
|
+
message: `Constraint '${constraint.id}' contract is incomplete: missing ${gaps.join(', ')}`,
|
|
122
|
+
});
|
|
123
|
+
} else {
|
|
124
|
+
complete.push({ ruleId: constraint.id, contract });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const total = registry.getAllRules().length + registry.getAllConstraints().length;
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
complete,
|
|
132
|
+
incomplete,
|
|
133
|
+
missing,
|
|
134
|
+
total,
|
|
135
|
+
timestamp: new Date().toISOString(),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Validate a single contract for completeness.
|
|
141
|
+
*
|
|
142
|
+
* @param contract The contract to validate
|
|
143
|
+
* @param requiredFields Fields that must be present
|
|
144
|
+
* @returns Array of missing artifacts
|
|
145
|
+
*/
|
|
146
|
+
function validateContract(
|
|
147
|
+
contract: Contract,
|
|
148
|
+
requiredFields: Array<'behavior' | 'examples' | 'invariants'>,
|
|
149
|
+
artifactIndex?: ArtifactIndex
|
|
150
|
+
): MissingArtifact[] {
|
|
151
|
+
const missing: MissingArtifact[] = [];
|
|
152
|
+
|
|
153
|
+
if (requiredFields.includes('behavior') && isFieldEmpty(contract.behavior)) {
|
|
154
|
+
missing.push('behavior');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (requiredFields.includes('examples') && (!contract.examples || contract.examples.length === 0)) {
|
|
158
|
+
missing.push('examples');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (requiredFields.includes('invariants') && (!contract.invariants || contract.invariants.length === 0)) {
|
|
162
|
+
missing.push('invariants');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (artifactIndex?.tests && !artifactIndex.tests.has(contract.ruleId)) {
|
|
166
|
+
missing.push('tests');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (artifactIndex?.spec && !artifactIndex.spec.has(contract.ruleId)) {
|
|
170
|
+
missing.push('spec');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return missing;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Check if a string field is empty or undefined.
|
|
178
|
+
*/
|
|
179
|
+
function isFieldEmpty(value: string | undefined): boolean {
|
|
180
|
+
return !value || value.trim() === '';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Format validation report as human-readable text.
|
|
185
|
+
*
|
|
186
|
+
* @param report The validation report
|
|
187
|
+
* @returns Formatted string
|
|
188
|
+
*/
|
|
189
|
+
export function formatValidationReport(report: ValidationReport): string {
|
|
190
|
+
const lines: string[] = [];
|
|
191
|
+
|
|
192
|
+
lines.push('Contract Validation Report');
|
|
193
|
+
lines.push('='.repeat(50));
|
|
194
|
+
lines.push('');
|
|
195
|
+
lines.push(`Total: ${report.total}`);
|
|
196
|
+
lines.push(`Complete: ${report.complete.length}`);
|
|
197
|
+
lines.push(`Incomplete: ${report.incomplete.length}`);
|
|
198
|
+
lines.push(`Missing: ${report.missing.length}`);
|
|
199
|
+
lines.push('');
|
|
200
|
+
|
|
201
|
+
if (report.complete.length > 0) {
|
|
202
|
+
lines.push('✓ Complete Contracts:');
|
|
203
|
+
for (const { ruleId, contract } of report.complete) {
|
|
204
|
+
lines.push(` ✓ ${ruleId} (v${contract.version || '1.0.0'})`);
|
|
205
|
+
}
|
|
206
|
+
lines.push('');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (report.incomplete.length > 0) {
|
|
210
|
+
lines.push('✗ Incomplete Contracts:');
|
|
211
|
+
for (const gap of report.incomplete) {
|
|
212
|
+
const icon = gap.severity === 'error' ? '✗' : gap.severity === 'warning' ? '⚠' : 'ℹ';
|
|
213
|
+
lines.push(` ${icon} ${gap.ruleId} - Missing: ${gap.missing.join(', ')}`);
|
|
214
|
+
if (gap.message) {
|
|
215
|
+
lines.push(` ${gap.message}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
lines.push('');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (report.missing.length > 0) {
|
|
222
|
+
lines.push('✗ No Contract:');
|
|
223
|
+
for (const ruleId of report.missing) {
|
|
224
|
+
lines.push(` ✗ ${ruleId}`);
|
|
225
|
+
}
|
|
226
|
+
lines.push('');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
lines.push(`Validated at: ${report.timestamp}`);
|
|
230
|
+
|
|
231
|
+
return lines.join('\n');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Format validation report as JSON.
|
|
236
|
+
*
|
|
237
|
+
* @param report The validation report
|
|
238
|
+
* @returns JSON string
|
|
239
|
+
*/
|
|
240
|
+
export function formatValidationReportJSON(report: ValidationReport): string {
|
|
241
|
+
return JSON.stringify(report, null, 2);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Format validation report as SARIF (Static Analysis Results Interchange Format).
|
|
246
|
+
*
|
|
247
|
+
* @param report The validation report
|
|
248
|
+
* @returns SARIF JSON string
|
|
249
|
+
*/
|
|
250
|
+
export function formatValidationReportSARIF(report: ValidationReport): string {
|
|
251
|
+
const results = report.incomplete.map((gap) => {
|
|
252
|
+
// Use first missing item or 'contract' as fallback
|
|
253
|
+
const primaryMissing = gap.missing.length > 0 ? gap.missing[0] : 'contract';
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
ruleId: `decision-ledger/${primaryMissing}`,
|
|
257
|
+
level: gap.severity === 'error' ? 'error' : gap.severity === 'warning' ? 'warning' : 'note',
|
|
258
|
+
message: {
|
|
259
|
+
text: gap.message || `Missing: ${gap.missing.join(', ')}`,
|
|
260
|
+
},
|
|
261
|
+
locations: [
|
|
262
|
+
{
|
|
263
|
+
physicalLocation: {
|
|
264
|
+
artifactLocation: {
|
|
265
|
+
uri: 'registry',
|
|
266
|
+
},
|
|
267
|
+
region: {
|
|
268
|
+
startLine: 1,
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
properties: {
|
|
274
|
+
ruleId: gap.ruleId,
|
|
275
|
+
missing: gap.missing,
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const sarif = {
|
|
281
|
+
version: '2.1.0',
|
|
282
|
+
$schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
|
|
283
|
+
runs: [
|
|
284
|
+
{
|
|
285
|
+
tool: {
|
|
286
|
+
driver: {
|
|
287
|
+
name: 'Praxis Decision Ledger',
|
|
288
|
+
version: '1.0.0',
|
|
289
|
+
informationUri: 'https://github.com/plures/praxis',
|
|
290
|
+
rules: [
|
|
291
|
+
{
|
|
292
|
+
id: 'decision-ledger/contract',
|
|
293
|
+
shortDescription: {
|
|
294
|
+
text: 'Rule or constraint missing contract',
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
id: 'decision-ledger/behavior',
|
|
299
|
+
shortDescription: {
|
|
300
|
+
text: 'Contract missing behavior description',
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
id: 'decision-ledger/examples',
|
|
305
|
+
shortDescription: {
|
|
306
|
+
text: 'Contract missing examples',
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: 'decision-ledger/invariants',
|
|
311
|
+
shortDescription: {
|
|
312
|
+
text: 'Contract missing invariants',
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
id: 'decision-ledger/tests',
|
|
317
|
+
shortDescription: {
|
|
318
|
+
text: 'Contract missing tests',
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: 'decision-ledger/spec',
|
|
323
|
+
shortDescription: {
|
|
324
|
+
text: 'Contract missing spec',
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
results,
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
return JSON.stringify(sarif, null, 2);
|
|
336
|
+
}
|
package/src/dsl/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
RuleFn,
|
|
14
14
|
ConstraintFn,
|
|
15
15
|
} from '../core/rules.js';
|
|
16
|
+
import type { Contract } from '../decision-ledger/types.js';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Strongly typed fact definition
|
|
@@ -84,6 +85,7 @@ export interface DefineRuleOptions<TContext = unknown> {
|
|
|
84
85
|
id: string;
|
|
85
86
|
description: string;
|
|
86
87
|
impl: RuleFn<TContext>;
|
|
88
|
+
contract?: Contract;
|
|
87
89
|
meta?: Record<string, unknown>;
|
|
88
90
|
}
|
|
89
91
|
|
|
@@ -106,11 +108,15 @@ export interface DefineRuleOptions<TContext = unknown> {
|
|
|
106
108
|
export function defineRule<TContext = unknown>(
|
|
107
109
|
options: DefineRuleOptions<TContext>
|
|
108
110
|
): RuleDescriptor<TContext> {
|
|
111
|
+
const contract = options.contract ?? (options.meta?.contract as Contract | undefined);
|
|
112
|
+
const meta = contract ? { ...(options.meta ?? {}), contract } : options.meta;
|
|
113
|
+
|
|
109
114
|
return {
|
|
110
115
|
id: options.id,
|
|
111
116
|
description: options.description,
|
|
112
117
|
impl: options.impl,
|
|
113
|
-
|
|
118
|
+
contract,
|
|
119
|
+
meta,
|
|
114
120
|
};
|
|
115
121
|
}
|
|
116
122
|
|
|
@@ -121,6 +127,7 @@ export interface DefineConstraintOptions<TContext = unknown> {
|
|
|
121
127
|
id: string;
|
|
122
128
|
description: string;
|
|
123
129
|
impl: ConstraintFn<TContext>;
|
|
130
|
+
contract?: Contract;
|
|
124
131
|
meta?: Record<string, unknown>;
|
|
125
132
|
}
|
|
126
133
|
|
|
@@ -140,11 +147,15 @@ export interface DefineConstraintOptions<TContext = unknown> {
|
|
|
140
147
|
export function defineConstraint<TContext = unknown>(
|
|
141
148
|
options: DefineConstraintOptions<TContext>
|
|
142
149
|
): ConstraintDescriptor<TContext> {
|
|
150
|
+
const contract = options.contract ?? (options.meta?.contract as Contract | undefined);
|
|
151
|
+
const meta = contract ? { ...(options.meta ?? {}), contract } : options.meta;
|
|
152
|
+
|
|
143
153
|
return {
|
|
144
154
|
id: options.id,
|
|
145
155
|
description: options.description,
|
|
146
156
|
impl: options.impl,
|
|
147
|
-
|
|
157
|
+
contract,
|
|
158
|
+
meta,
|
|
148
159
|
};
|
|
149
160
|
}
|
|
150
161
|
|
package/src/index.browser.ts
CHANGED
|
@@ -112,6 +112,7 @@ export type {
|
|
|
112
112
|
UnsubscribeFn,
|
|
113
113
|
PluresDBInstance,
|
|
114
114
|
PluresDBAdapterConfig,
|
|
115
|
+
PraxisLocalFirstOptions,
|
|
115
116
|
EventStreamEntry,
|
|
116
117
|
PraxisDBStoreOptions,
|
|
117
118
|
StoredSchema,
|
|
@@ -125,6 +126,7 @@ export {
|
|
|
125
126
|
createInMemoryDB,
|
|
126
127
|
PluresDBPraxisAdapter,
|
|
127
128
|
createPluresDB,
|
|
129
|
+
createPraxisLocalFirst,
|
|
128
130
|
PraxisDBStore,
|
|
129
131
|
createPraxisDBStore,
|
|
130
132
|
PRAXIS_PATHS,
|
|
@@ -214,3 +216,7 @@ export {
|
|
|
214
216
|
attachTauriToEngine,
|
|
215
217
|
generateTauriConfig,
|
|
216
218
|
} from './integrations/tauri.js';
|
|
219
|
+
|
|
220
|
+
// Unified Integration Helpers
|
|
221
|
+
export type { UnifiedAppConfig, UnifiedApp } from './integrations/unified.js';
|
|
222
|
+
export { createUnifiedApp, attachAllIntegrations } from './integrations/unified.js';
|