@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.
Files changed (74) hide show
  1. package/FRAMEWORK.md +106 -15
  2. package/README.md +194 -119
  3. package/dist/browser/adapter-CIMBGDC7.js +14 -0
  4. package/dist/browser/chunk-K377RW4V.js +230 -0
  5. package/dist/browser/chunk-MBVHLOU2.js +152 -0
  6. package/dist/browser/{chunk-R45WXWKH.js → chunk-VOMLVI6V.js} +1 -149
  7. package/dist/browser/engine-YJZV4SLD.js +8 -0
  8. package/dist/browser/index.d.ts +161 -5
  9. package/dist/browser/index.js +156 -141
  10. package/dist/browser/integrations/svelte.d.ts +2 -2
  11. package/dist/browser/integrations/svelte.js +2 -1
  12. package/dist/browser/{reactive-engine.svelte-C9OpcTHf.d.ts → reactive-engine.svelte-9aS0kTa8.d.ts} +136 -1
  13. package/dist/node/adapter-75ISSMWD.js +15 -0
  14. package/dist/node/chunk-5RH7UAQC.js +486 -0
  15. package/dist/node/chunk-MBVHLOU2.js +152 -0
  16. package/dist/node/chunk-PRPQO6R5.js +85 -0
  17. package/dist/node/chunk-R2PSBPKQ.js +150 -0
  18. package/dist/node/chunk-S54337I5.js +446 -0
  19. package/dist/node/{chunk-R45WXWKH.js → chunk-VOMLVI6V.js} +1 -149
  20. package/dist/node/chunk-WZ6B3LZ6.js +638 -0
  21. package/dist/node/cli/index.cjs +2936 -897
  22. package/dist/node/cli/index.js +27 -0
  23. package/dist/node/components/index.d.cts +3 -2
  24. package/dist/node/components/index.d.ts +3 -2
  25. package/dist/node/docs-JFNYTOJA.js +102 -0
  26. package/dist/node/engine-2DQBKBJC.js +9 -0
  27. package/dist/node/index.cjs +1114 -354
  28. package/dist/node/index.d.cts +388 -5
  29. package/dist/node/index.d.ts +388 -5
  30. package/dist/node/index.js +201 -640
  31. package/dist/node/integrations/svelte.cjs +76 -0
  32. package/dist/node/integrations/svelte.d.cts +2 -2
  33. package/dist/node/integrations/svelte.d.ts +2 -2
  34. package/dist/node/integrations/svelte.js +3 -1
  35. package/dist/node/{reactive-engine.svelte-1M4m_C_v.d.cts → reactive-engine.svelte-BFIZfawz.d.cts} +199 -1
  36. package/dist/node/{reactive-engine.svelte-ChNFn4Hj.d.ts → reactive-engine.svelte-CRNqHlbv.d.ts} +199 -1
  37. package/dist/node/reverse-W7THPV45.js +193 -0
  38. package/dist/node/{terminal-adapter-CWka-yL8.d.ts → terminal-adapter-B-UK_Vdz.d.ts} +28 -3
  39. package/dist/node/{terminal-adapter-CDzxoLKR.d.cts → terminal-adapter-BQSIF5bf.d.cts} +28 -3
  40. package/dist/node/validate-CNHUULQE.js +180 -0
  41. package/docs/core/pluresdb-integration.md +15 -15
  42. package/docs/decision-ledger/BEHAVIOR_LEDGER.md +225 -0
  43. package/docs/decision-ledger/DecisionLedger.tla +180 -0
  44. package/docs/decision-ledger/IMPLEMENTATION_SUMMARY.md +217 -0
  45. package/docs/decision-ledger/LATEST.md +166 -0
  46. package/docs/guides/cicd-pipeline.md +142 -0
  47. package/package.json +2 -2
  48. package/src/__tests__/cli-validate.test.ts +197 -0
  49. package/src/__tests__/decision-ledger.test.ts +485 -0
  50. package/src/__tests__/reverse-generator.test.ts +189 -0
  51. package/src/__tests__/scanner.test.ts +215 -0
  52. package/src/cli/commands/docs.ts +147 -0
  53. package/src/cli/commands/reverse.ts +289 -0
  54. package/src/cli/commands/validate.ts +264 -0
  55. package/src/cli/index.ts +68 -0
  56. package/src/core/pluresdb/adapter.ts +46 -3
  57. package/src/core/reactive-engine.svelte.ts +6 -1
  58. package/src/core/reactive-engine.ts +1 -1
  59. package/src/core/rules.ts +133 -0
  60. package/src/decision-ledger/README.md +400 -0
  61. package/src/decision-ledger/REVERSE_ENGINEERING.md +484 -0
  62. package/src/decision-ledger/facts-events.ts +121 -0
  63. package/src/decision-ledger/index.ts +70 -0
  64. package/src/decision-ledger/ledger.ts +246 -0
  65. package/src/decision-ledger/logic-ledger.ts +158 -0
  66. package/src/decision-ledger/reverse-generator.ts +426 -0
  67. package/src/decision-ledger/scanner.ts +506 -0
  68. package/src/decision-ledger/types.ts +247 -0
  69. package/src/decision-ledger/validation.ts +336 -0
  70. package/src/dsl/index.ts +13 -2
  71. package/src/index.browser.ts +6 -0
  72. package/src/index.ts +40 -0
  73. package/src/integrations/pluresdb.ts +14 -2
  74. 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
- meta: options.meta,
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
- meta: options.meta,
157
+ contract,
158
+ meta,
148
159
  };
149
160
  }
150
161
 
@@ -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';