@plures/praxis 1.2.0 → 1.2.11

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 (63) hide show
  1. package/README.md +93 -96
  2. package/dist/browser/{adapter-TM4IS5KT.js → adapter-CIMBGDC7.js} +5 -3
  3. package/dist/browser/{chunk-LE2ZJYFC.js → chunk-K377RW4V.js} +76 -0
  4. package/dist/{node/chunk-JQ64KMLN.js → browser/chunk-MBVHLOU2.js} +12 -1
  5. package/dist/browser/index.d.ts +32 -5
  6. package/dist/browser/index.js +15 -7
  7. package/dist/browser/integrations/svelte.d.ts +2 -2
  8. package/dist/browser/integrations/svelte.js +1 -1
  9. package/dist/browser/{reactive-engine.svelte-C9OpcTHf.d.ts → reactive-engine.svelte-9aS0kTa8.d.ts} +136 -1
  10. package/dist/node/{adapter-K6DOX6XS.js → adapter-75ISSMWD.js} +5 -3
  11. package/dist/node/chunk-5RH7UAQC.js +486 -0
  12. package/dist/{browser/chunk-JQ64KMLN.js → node/chunk-MBVHLOU2.js} +12 -1
  13. package/dist/node/{chunk-LE2ZJYFC.js → chunk-PRPQO6R5.js} +3 -72
  14. package/dist/node/chunk-R2PSBPKQ.js +150 -0
  15. package/dist/node/chunk-WZ6B3LZ6.js +638 -0
  16. package/dist/node/cli/index.cjs +2316 -832
  17. package/dist/node/cli/index.js +18 -0
  18. package/dist/node/components/index.d.cts +3 -2
  19. package/dist/node/components/index.d.ts +3 -2
  20. package/dist/node/index.cjs +620 -38
  21. package/dist/node/index.d.cts +259 -5
  22. package/dist/node/index.d.ts +259 -5
  23. package/dist/node/index.js +55 -65
  24. package/dist/node/integrations/svelte.cjs +76 -0
  25. package/dist/node/integrations/svelte.d.cts +2 -2
  26. package/dist/node/integrations/svelte.d.ts +2 -2
  27. package/dist/node/integrations/svelte.js +2 -1
  28. package/dist/node/{reactive-engine.svelte-1M4m_C_v.d.cts → reactive-engine.svelte-BFIZfawz.d.cts} +199 -1
  29. package/dist/node/{reactive-engine.svelte-ChNFn4Hj.d.ts → reactive-engine.svelte-CRNqHlbv.d.ts} +199 -1
  30. package/dist/node/reverse-W7THPV45.js +193 -0
  31. package/dist/node/{terminal-adapter-CWka-yL8.d.ts → terminal-adapter-B-UK_Vdz.d.ts} +28 -3
  32. package/dist/node/{terminal-adapter-CDzxoLKR.d.cts → terminal-adapter-BQSIF5bf.d.cts} +28 -3
  33. package/dist/node/validate-CNHUULQE.js +180 -0
  34. package/docs/core/pluresdb-integration.md +15 -15
  35. package/docs/decision-ledger/BEHAVIOR_LEDGER.md +225 -0
  36. package/docs/decision-ledger/DecisionLedger.tla +180 -0
  37. package/docs/decision-ledger/IMPLEMENTATION_SUMMARY.md +217 -0
  38. package/docs/decision-ledger/LATEST.md +166 -0
  39. package/docs/guides/cicd-pipeline.md +142 -0
  40. package/package.json +2 -2
  41. package/src/__tests__/cli-validate.test.ts +197 -0
  42. package/src/__tests__/decision-ledger.test.ts +485 -0
  43. package/src/__tests__/reverse-generator.test.ts +189 -0
  44. package/src/__tests__/scanner.test.ts +215 -0
  45. package/src/cli/commands/reverse.ts +289 -0
  46. package/src/cli/commands/validate.ts +264 -0
  47. package/src/cli/index.ts +47 -0
  48. package/src/core/pluresdb/adapter.ts +45 -2
  49. package/src/core/rules.ts +133 -0
  50. package/src/decision-ledger/README.md +400 -0
  51. package/src/decision-ledger/REVERSE_ENGINEERING.md +484 -0
  52. package/src/decision-ledger/facts-events.ts +121 -0
  53. package/src/decision-ledger/index.ts +70 -0
  54. package/src/decision-ledger/ledger.ts +246 -0
  55. package/src/decision-ledger/logic-ledger.ts +158 -0
  56. package/src/decision-ledger/reverse-generator.ts +426 -0
  57. package/src/decision-ledger/scanner.ts +506 -0
  58. package/src/decision-ledger/types.ts +247 -0
  59. package/src/decision-ledger/validation.ts +336 -0
  60. package/src/dsl/index.ts +13 -2
  61. package/src/index.browser.ts +2 -0
  62. package/src/index.ts +36 -0
  63. package/src/integrations/pluresdb.ts +14 -2
@@ -246,7 +246,8 @@ __export(adapter_exports, {
246
246
  InMemoryPraxisDB: () => InMemoryPraxisDB,
247
247
  PluresDBPraxisAdapter: () => PluresDBPraxisAdapter,
248
248
  createInMemoryDB: () => createInMemoryDB,
249
- createPluresDB: () => createPluresDB
249
+ createPluresDB: () => createPluresDB,
250
+ createPraxisLocalFirst: () => createPraxisLocalFirst
250
251
  });
251
252
  function createInMemoryDB() {
252
253
  return new InMemoryPraxisDB();
@@ -254,6 +255,16 @@ function createInMemoryDB() {
254
255
  function createPluresDB(config) {
255
256
  return new PluresDBPraxisAdapter(config);
256
257
  }
258
+ async function createPraxisLocalFirst(options = {}) {
259
+ const { pollInterval, ...localOptions } = options;
260
+ const mod = await import("@plures/pluresdb/local-first");
261
+ const LocalFirstCtor = mod.PluresDBLocalFirst ?? mod.default;
262
+ if (!LocalFirstCtor) {
263
+ throw new Error("Failed to load PluresDBLocalFirst from @plures/pluresdb/local-first");
264
+ }
265
+ const db = new LocalFirstCtor(localOptions);
266
+ return new PluresDBPraxisAdapter({ db, pollInterval });
267
+ }
257
268
  var InMemoryPraxisDB, PluresDBPraxisAdapter;
258
269
  var init_adapter = __esm({
259
270
  "src/core/pluresdb/adapter.ts"() {
@@ -391,7 +402,14 @@ var init_adapter = __esm({
391
402
  // src/index.ts
392
403
  var src_exports = {};
393
404
  __export(src_exports, {
405
+ AcknowledgeContractGap: () => AcknowledgeContractGap,
394
406
  ActorManager: () => ActorManager,
407
+ BehaviorLedger: () => BehaviorLedger,
408
+ ContractAdded: () => ContractAdded,
409
+ ContractGapAcknowledged: () => ContractGapAcknowledged,
410
+ ContractMissing: () => ContractMissing,
411
+ ContractUpdated: () => ContractUpdated,
412
+ ContractValidated: () => ContractValidated,
395
413
  FrameworkAgnosticReactiveEngine: () => ReactiveLogicEngine2,
396
414
  InMemoryPraxisDB: () => InMemoryPraxisDB,
397
415
  LogicEngine: () => LogicEngine,
@@ -406,6 +424,7 @@ __export(src_exports, {
406
424
  RegistryIntrospector: () => RegistryIntrospector,
407
425
  StateDocsGenerator: () => StateDocsGenerator,
408
426
  TerminalAdapter: () => TerminalAdapter,
427
+ ValidateContracts: () => ValidateContracts,
409
428
  attachAllIntegrations: () => attachAllIntegrations,
410
429
  attachTauriToEngine: () => attachTauriToEngine,
411
430
  attachToEngine: () => attachToEngine,
@@ -413,6 +432,7 @@ __export(src_exports, {
413
432
  canvasToMermaid: () => canvasToMermaid,
414
433
  canvasToSchema: () => canvasToSchema,
415
434
  canvasToYaml: () => canvasToYaml,
435
+ createBehaviorLedger: () => createBehaviorLedger,
416
436
  createCanvasEditor: () => createCanvasEditor,
417
437
  createFrameworkAgnosticReactiveEngine: () => createReactiveEngine2,
418
438
  createInMemoryDB: () => createInMemoryDB,
@@ -424,6 +444,7 @@ __export(src_exports, {
424
444
  createPluresDBGenerator: () => createPluresDBGenerator,
425
445
  createPraxisDBStore: () => createPraxisDBStore,
426
446
  createPraxisEngine: () => createPraxisEngine,
447
+ createPraxisLocalFirst: () => createPraxisLocalFirst,
427
448
  createReactiveEngine: () => createReactiveEngine,
428
449
  createSchemaRegistry: () => createSchemaRegistry,
429
450
  createSchemaTemplate: () => createSchemaTemplate,
@@ -434,6 +455,7 @@ __export(src_exports, {
434
455
  createUnifiedApp: () => createUnifiedApp,
435
456
  createUnumAdapter: () => createUnumAdapter,
436
457
  defineConstraint: () => defineConstraint,
458
+ defineContract: () => defineContract,
437
459
  defineEvent: () => defineEvent,
438
460
  defineFact: () => defineFact,
439
461
  defineModule: () => defineModule,
@@ -442,12 +464,17 @@ __export(src_exports, {
442
464
  filterFacts: () => filterFacts,
443
465
  findEvent: () => findEvent,
444
466
  findFact: () => findFact,
467
+ formatValidationReport: () => formatValidationReport,
468
+ formatValidationReportJSON: () => formatValidationReportJSON,
469
+ formatValidationReportSARIF: () => formatValidationReportSARIF,
445
470
  generateDocs: () => generateDocs,
446
471
  generateId: () => generateId,
447
472
  generateTauriConfig: () => generateTauriConfig,
473
+ getContract: () => getContract,
448
474
  getEventPath: () => getEventPath,
449
475
  getFactPath: () => getFactPath,
450
476
  getSchemaPath: () => getSchemaPath,
477
+ isContract: () => isContract,
451
478
  loadSchema: () => loadSchema,
452
479
  loadSchemaFromFile: () => loadSchemaFromFile,
453
480
  loadSchemaFromJson: () => loadSchemaFromJson,
@@ -455,6 +482,7 @@ __export(src_exports, {
455
482
  registerSchema: () => registerSchema,
456
483
  runTerminalCommand: () => runTerminalCommand,
457
484
  schemaToCanvas: () => schemaToCanvas,
485
+ validateContracts: () => validateContracts,
458
486
  validateForGeneration: () => validateForGeneration,
459
487
  validateSchema: () => validateSchema,
460
488
  validateWithGuardian: () => validateWithGuardian
@@ -466,6 +494,17 @@ init_protocol();
466
494
  var PraxisRegistry = class {
467
495
  rules = /* @__PURE__ */ new Map();
468
496
  constraints = /* @__PURE__ */ new Map();
497
+ compliance;
498
+ contractGaps = [];
499
+ constructor(options = {}) {
500
+ const defaultEnabled = typeof process !== "undefined" ? process.env?.NODE_ENV !== "production" : false;
501
+ this.compliance = {
502
+ enabled: defaultEnabled,
503
+ requiredFields: ["behavior", "examples", "invariants"],
504
+ missingSeverity: "warning",
505
+ ...options.compliance
506
+ };
507
+ }
469
508
  /**
470
509
  * Register a rule
471
510
  */
@@ -474,6 +513,7 @@ var PraxisRegistry = class {
474
513
  throw new Error(`Rule with id "${descriptor.id}" already registered`);
475
514
  }
476
515
  this.rules.set(descriptor.id, descriptor);
516
+ this.trackContractCompliance(descriptor.id, descriptor);
477
517
  }
478
518
  /**
479
519
  * Register a constraint
@@ -483,6 +523,7 @@ var PraxisRegistry = class {
483
523
  throw new Error(`Constraint with id "${descriptor.id}" already registered`);
484
524
  }
485
525
  this.constraints.set(descriptor.id, descriptor);
526
+ this.trackContractCompliance(descriptor.id, descriptor);
486
527
  }
487
528
  /**
488
529
  * Register a module (all its rules and constraints)
@@ -531,6 +572,69 @@ var PraxisRegistry = class {
531
572
  getAllConstraints() {
532
573
  return Array.from(this.constraints.values());
533
574
  }
575
+ /**
576
+ * Get collected contract gaps from registration-time validation.
577
+ */
578
+ getContractGaps() {
579
+ return [...this.contractGaps];
580
+ }
581
+ /**
582
+ * Clear collected contract gaps.
583
+ */
584
+ clearContractGaps() {
585
+ this.contractGaps = [];
586
+ }
587
+ trackContractCompliance(id, descriptor) {
588
+ if (!this.compliance.enabled) {
589
+ return;
590
+ }
591
+ const gaps = this.validateDescriptorContract(id, descriptor);
592
+ for (const gap of gaps) {
593
+ this.contractGaps.push(gap);
594
+ if (this.compliance.onGap) {
595
+ this.compliance.onGap(gap);
596
+ } else {
597
+ const label = gap.severity === "error" ? "ERROR" : gap.severity === "warning" ? "WARN" : "INFO";
598
+ console.warn(`[Praxis][${label}] Contract gap for "${gap.ruleId}": missing ${gap.missing.join(", ")}`);
599
+ }
600
+ }
601
+ }
602
+ validateDescriptorContract(id, descriptor) {
603
+ const requiredFields = this.compliance.requiredFields ?? ["behavior", "examples", "invariants"];
604
+ const missingSeverity = this.compliance.missingSeverity ?? "warning";
605
+ const contract = descriptor.contract ?? (descriptor.meta?.contract && typeof descriptor.meta.contract === "object" ? descriptor.meta.contract : void 0);
606
+ if (!contract) {
607
+ return [
608
+ {
609
+ ruleId: id,
610
+ missing: ["contract"],
611
+ severity: missingSeverity,
612
+ message: `Contract missing for "${id}"`
613
+ }
614
+ ];
615
+ }
616
+ const missing = [];
617
+ if (requiredFields.includes("behavior") && (!contract.behavior || contract.behavior.trim() === "")) {
618
+ missing.push("behavior");
619
+ }
620
+ if (requiredFields.includes("examples") && (!contract.examples || contract.examples.length === 0)) {
621
+ missing.push("examples");
622
+ }
623
+ if (requiredFields.includes("invariants") && (!contract.invariants || contract.invariants.length === 0)) {
624
+ missing.push("invariants");
625
+ }
626
+ if (missing.length === 0) {
627
+ return [];
628
+ }
629
+ return [
630
+ {
631
+ ruleId: id,
632
+ missing,
633
+ severity: "warning",
634
+ message: `Contract for "${id}" is incomplete: missing ${missing.join(", ")}`
635
+ }
636
+ ];
637
+ }
534
638
  };
535
639
 
536
640
  // src/index.ts
@@ -1175,19 +1279,25 @@ function defineEvent(tag) {
1175
1279
  };
1176
1280
  }
1177
1281
  function defineRule(options) {
1282
+ const contract = options.contract ?? options.meta?.contract;
1283
+ const meta = contract ? { ...options.meta ?? {}, contract } : options.meta;
1178
1284
  return {
1179
1285
  id: options.id,
1180
1286
  description: options.description,
1181
1287
  impl: options.impl,
1182
- meta: options.meta
1288
+ contract,
1289
+ meta
1183
1290
  };
1184
1291
  }
1185
1292
  function defineConstraint(options) {
1293
+ const contract = options.contract ?? options.meta?.contract;
1294
+ const meta = contract ? { ...options.meta ?? {}, contract } : options.meta;
1186
1295
  return {
1187
1296
  id: options.id,
1188
1297
  description: options.description,
1189
1298
  impl: options.impl,
1190
- meta: options.meta
1299
+ contract,
1300
+ meta
1191
1301
  };
1192
1302
  }
1193
1303
  function defineModule(options) {
@@ -1210,6 +1320,461 @@ function findFact(facts, definition) {
1210
1320
  return facts.find(definition.is);
1211
1321
  }
1212
1322
 
1323
+ // src/decision-ledger/types.ts
1324
+ function isContract(obj) {
1325
+ if (typeof obj !== "object" || obj === null) {
1326
+ return false;
1327
+ }
1328
+ const contract = obj;
1329
+ return typeof contract.ruleId === "string" && typeof contract.behavior === "string" && Array.isArray(contract.examples) && contract.examples.length > 0 && contract.examples.every(
1330
+ (ex) => typeof ex === "object" && ex !== null && typeof ex.given === "string" && typeof ex.when === "string" && typeof ex.then === "string"
1331
+ ) && Array.isArray(contract.invariants) && contract.invariants.every((inv) => typeof inv === "string");
1332
+ }
1333
+ function defineContract(options) {
1334
+ if (options.examples.length === 0) {
1335
+ throw new Error("Contract must have at least one example");
1336
+ }
1337
+ if (options.assumptions) {
1338
+ for (const assumption of options.assumptions) {
1339
+ if (assumption.confidence < 0 || assumption.confidence > 1) {
1340
+ throw new Error(
1341
+ `Assumption '${assumption.id}' has invalid confidence value ${assumption.confidence}. Must be between 0.0 and 1.0`
1342
+ );
1343
+ }
1344
+ }
1345
+ }
1346
+ return {
1347
+ ruleId: options.ruleId,
1348
+ behavior: options.behavior,
1349
+ examples: options.examples,
1350
+ invariants: options.invariants,
1351
+ assumptions: options.assumptions,
1352
+ references: options.references,
1353
+ version: options.version || "1.0.0",
1354
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1355
+ };
1356
+ }
1357
+ function getContract(meta) {
1358
+ if (!meta || !meta.contract) {
1359
+ return void 0;
1360
+ }
1361
+ if (isContract(meta.contract)) {
1362
+ return meta.contract;
1363
+ }
1364
+ return void 0;
1365
+ }
1366
+ function getContractFromDescriptor(descriptor) {
1367
+ if (!descriptor) {
1368
+ return void 0;
1369
+ }
1370
+ if (descriptor.contract && isContract(descriptor.contract)) {
1371
+ return descriptor.contract;
1372
+ }
1373
+ return getContract(descriptor.meta);
1374
+ }
1375
+
1376
+ // src/decision-ledger/facts-events.ts
1377
+ var ContractMissing = defineFact("ContractMissing");
1378
+ var ContractValidated = defineFact("ContractValidated");
1379
+ var AcknowledgeContractGap = defineEvent("ACKNOWLEDGE_CONTRACT_GAP");
1380
+ var ValidateContracts = defineEvent("VALIDATE_CONTRACTS");
1381
+ var ContractGapAcknowledged = defineFact("ContractGapAcknowledged");
1382
+ var ContractAdded = defineEvent("CONTRACT_ADDED");
1383
+ var ContractUpdated = defineEvent("CONTRACT_UPDATED");
1384
+ var ContractGapEmitted = defineEvent("CONTRACT_GAP_EMITTED");
1385
+
1386
+ // src/decision-ledger/validation.ts
1387
+ function validateContracts(registry, options = {}) {
1388
+ const {
1389
+ incompleteSeverity = "warning",
1390
+ requiredFields = ["behavior", "examples"],
1391
+ artifactIndex
1392
+ } = options;
1393
+ const complete = [];
1394
+ const incomplete = [];
1395
+ const missing = [];
1396
+ for (const rule of registry.getAllRules()) {
1397
+ const contract = getContractFromDescriptor(rule);
1398
+ if (!contract) {
1399
+ missing.push(rule.id);
1400
+ if (options.missingSeverity) {
1401
+ incomplete.push({
1402
+ ruleId: rule.id,
1403
+ missing: ["contract"],
1404
+ severity: options.missingSeverity,
1405
+ message: `Rule '${rule.id}' has no contract`
1406
+ });
1407
+ }
1408
+ continue;
1409
+ }
1410
+ const gaps = validateContract(contract, requiredFields, artifactIndex);
1411
+ if (gaps.length > 0) {
1412
+ incomplete.push({
1413
+ ruleId: rule.id,
1414
+ missing: gaps,
1415
+ severity: incompleteSeverity,
1416
+ message: `Rule '${rule.id}' contract is incomplete: missing ${gaps.join(", ")}`
1417
+ });
1418
+ } else {
1419
+ complete.push({ ruleId: rule.id, contract });
1420
+ }
1421
+ }
1422
+ for (const constraint of registry.getAllConstraints()) {
1423
+ const contract = getContractFromDescriptor(constraint);
1424
+ if (!contract) {
1425
+ missing.push(constraint.id);
1426
+ if (options.missingSeverity) {
1427
+ incomplete.push({
1428
+ ruleId: constraint.id,
1429
+ missing: ["contract"],
1430
+ severity: options.missingSeverity,
1431
+ message: `Constraint '${constraint.id}' has no contract`
1432
+ });
1433
+ }
1434
+ continue;
1435
+ }
1436
+ const gaps = validateContract(contract, requiredFields, artifactIndex);
1437
+ if (gaps.length > 0) {
1438
+ incomplete.push({
1439
+ ruleId: constraint.id,
1440
+ missing: gaps,
1441
+ severity: incompleteSeverity,
1442
+ message: `Constraint '${constraint.id}' contract is incomplete: missing ${gaps.join(", ")}`
1443
+ });
1444
+ } else {
1445
+ complete.push({ ruleId: constraint.id, contract });
1446
+ }
1447
+ }
1448
+ const total = registry.getAllRules().length + registry.getAllConstraints().length;
1449
+ return {
1450
+ complete,
1451
+ incomplete,
1452
+ missing,
1453
+ total,
1454
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1455
+ };
1456
+ }
1457
+ function validateContract(contract, requiredFields, artifactIndex) {
1458
+ const missing = [];
1459
+ if (requiredFields.includes("behavior") && isFieldEmpty(contract.behavior)) {
1460
+ missing.push("behavior");
1461
+ }
1462
+ if (requiredFields.includes("examples") && (!contract.examples || contract.examples.length === 0)) {
1463
+ missing.push("examples");
1464
+ }
1465
+ if (requiredFields.includes("invariants") && (!contract.invariants || contract.invariants.length === 0)) {
1466
+ missing.push("invariants");
1467
+ }
1468
+ if (artifactIndex?.tests && !artifactIndex.tests.has(contract.ruleId)) {
1469
+ missing.push("tests");
1470
+ }
1471
+ if (artifactIndex?.spec && !artifactIndex.spec.has(contract.ruleId)) {
1472
+ missing.push("spec");
1473
+ }
1474
+ return missing;
1475
+ }
1476
+ function isFieldEmpty(value) {
1477
+ return !value || value.trim() === "";
1478
+ }
1479
+ function formatValidationReport(report) {
1480
+ const lines = [];
1481
+ lines.push("Contract Validation Report");
1482
+ lines.push("=".repeat(50));
1483
+ lines.push("");
1484
+ lines.push(`Total: ${report.total}`);
1485
+ lines.push(`Complete: ${report.complete.length}`);
1486
+ lines.push(`Incomplete: ${report.incomplete.length}`);
1487
+ lines.push(`Missing: ${report.missing.length}`);
1488
+ lines.push("");
1489
+ if (report.complete.length > 0) {
1490
+ lines.push("\u2713 Complete Contracts:");
1491
+ for (const { ruleId, contract } of report.complete) {
1492
+ lines.push(` \u2713 ${ruleId} (v${contract.version || "1.0.0"})`);
1493
+ }
1494
+ lines.push("");
1495
+ }
1496
+ if (report.incomplete.length > 0) {
1497
+ lines.push("\u2717 Incomplete Contracts:");
1498
+ for (const gap of report.incomplete) {
1499
+ const icon = gap.severity === "error" ? "\u2717" : gap.severity === "warning" ? "\u26A0" : "\u2139";
1500
+ lines.push(` ${icon} ${gap.ruleId} - Missing: ${gap.missing.join(", ")}`);
1501
+ if (gap.message) {
1502
+ lines.push(` ${gap.message}`);
1503
+ }
1504
+ }
1505
+ lines.push("");
1506
+ }
1507
+ if (report.missing.length > 0) {
1508
+ lines.push("\u2717 No Contract:");
1509
+ for (const ruleId of report.missing) {
1510
+ lines.push(` \u2717 ${ruleId}`);
1511
+ }
1512
+ lines.push("");
1513
+ }
1514
+ lines.push(`Validated at: ${report.timestamp}`);
1515
+ return lines.join("\n");
1516
+ }
1517
+ function formatValidationReportJSON(report) {
1518
+ return JSON.stringify(report, null, 2);
1519
+ }
1520
+ function formatValidationReportSARIF(report) {
1521
+ const results = report.incomplete.map((gap) => {
1522
+ const primaryMissing = gap.missing.length > 0 ? gap.missing[0] : "contract";
1523
+ return {
1524
+ ruleId: `decision-ledger/${primaryMissing}`,
1525
+ level: gap.severity === "error" ? "error" : gap.severity === "warning" ? "warning" : "note",
1526
+ message: {
1527
+ text: gap.message || `Missing: ${gap.missing.join(", ")}`
1528
+ },
1529
+ locations: [
1530
+ {
1531
+ physicalLocation: {
1532
+ artifactLocation: {
1533
+ uri: "registry"
1534
+ },
1535
+ region: {
1536
+ startLine: 1
1537
+ }
1538
+ }
1539
+ }
1540
+ ],
1541
+ properties: {
1542
+ ruleId: gap.ruleId,
1543
+ missing: gap.missing
1544
+ }
1545
+ };
1546
+ });
1547
+ const sarif = {
1548
+ version: "2.1.0",
1549
+ $schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
1550
+ runs: [
1551
+ {
1552
+ tool: {
1553
+ driver: {
1554
+ name: "Praxis Decision Ledger",
1555
+ version: "1.0.0",
1556
+ informationUri: "https://github.com/plures/praxis",
1557
+ rules: [
1558
+ {
1559
+ id: "decision-ledger/contract",
1560
+ shortDescription: {
1561
+ text: "Rule or constraint missing contract"
1562
+ }
1563
+ },
1564
+ {
1565
+ id: "decision-ledger/behavior",
1566
+ shortDescription: {
1567
+ text: "Contract missing behavior description"
1568
+ }
1569
+ },
1570
+ {
1571
+ id: "decision-ledger/examples",
1572
+ shortDescription: {
1573
+ text: "Contract missing examples"
1574
+ }
1575
+ },
1576
+ {
1577
+ id: "decision-ledger/invariants",
1578
+ shortDescription: {
1579
+ text: "Contract missing invariants"
1580
+ }
1581
+ },
1582
+ {
1583
+ id: "decision-ledger/tests",
1584
+ shortDescription: {
1585
+ text: "Contract missing tests"
1586
+ }
1587
+ },
1588
+ {
1589
+ id: "decision-ledger/spec",
1590
+ shortDescription: {
1591
+ text: "Contract missing spec"
1592
+ }
1593
+ }
1594
+ ]
1595
+ }
1596
+ },
1597
+ results
1598
+ }
1599
+ ]
1600
+ };
1601
+ return JSON.stringify(sarif, null, 2);
1602
+ }
1603
+
1604
+ // src/decision-ledger/ledger.ts
1605
+ var BehaviorLedger = class _BehaviorLedger {
1606
+ entries = [];
1607
+ entryMap = /* @__PURE__ */ new Map();
1608
+ /**
1609
+ * Append a new entry to the ledger.
1610
+ *
1611
+ * @param entry The entry to append
1612
+ * @throws Error if entry ID already exists
1613
+ */
1614
+ append(entry) {
1615
+ if (this.entryMap.has(entry.id)) {
1616
+ throw new Error(`Ledger entry with ID '${entry.id}' already exists`);
1617
+ }
1618
+ if (entry.supersedes) {
1619
+ const superseded = this.entryMap.get(entry.supersedes);
1620
+ if (superseded && superseded.status === "active") {
1621
+ const updatedEntry = {
1622
+ ...superseded,
1623
+ status: "superseded"
1624
+ };
1625
+ this.entryMap.set(entry.supersedes, updatedEntry);
1626
+ }
1627
+ }
1628
+ this.entries.push(entry);
1629
+ this.entryMap.set(entry.id, entry);
1630
+ }
1631
+ /**
1632
+ * Get an entry by ID.
1633
+ *
1634
+ * @param id The entry ID
1635
+ * @returns The entry, or undefined if not found
1636
+ */
1637
+ getEntry(id) {
1638
+ return this.entryMap.get(id);
1639
+ }
1640
+ /**
1641
+ * Get all entries (in order of append) with current status.
1642
+ *
1643
+ * @returns Array of all entries with current status from the map
1644
+ */
1645
+ getAllEntries() {
1646
+ return this.entries.map((entry) => this.entryMap.get(entry.id));
1647
+ }
1648
+ /**
1649
+ * Get entries for a specific rule ID.
1650
+ *
1651
+ * @param ruleId The rule ID
1652
+ * @returns Array of entries for this rule with current status
1653
+ */
1654
+ getEntriesForRule(ruleId) {
1655
+ return this.entries.map((entry) => this.entryMap.get(entry.id)).filter((entry) => entry.contract.ruleId === ruleId);
1656
+ }
1657
+ /**
1658
+ * Get the latest active entry for a rule.
1659
+ *
1660
+ * @param ruleId The rule ID
1661
+ * @returns The latest active entry, or undefined if none
1662
+ */
1663
+ getLatestEntry(ruleId) {
1664
+ const entries = this.getEntriesForRule(ruleId);
1665
+ const activeEntries = entries.filter((entry) => entry.status === "active");
1666
+ if (activeEntries.length === 0) {
1667
+ return void 0;
1668
+ }
1669
+ return activeEntries[activeEntries.length - 1];
1670
+ }
1671
+ /**
1672
+ * Get all active assumptions across all entries.
1673
+ *
1674
+ * @returns Map of assumption ID to assumption
1675
+ */
1676
+ getActiveAssumptions() {
1677
+ const assumptions = /* @__PURE__ */ new Map();
1678
+ for (const entry of this.entries) {
1679
+ const currentEntry = this.entryMap.get(entry.id);
1680
+ if (currentEntry.status !== "active") {
1681
+ continue;
1682
+ }
1683
+ for (const assumption of currentEntry.contract.assumptions || []) {
1684
+ if (assumption.status === "active") {
1685
+ assumptions.set(assumption.id, assumption);
1686
+ }
1687
+ }
1688
+ }
1689
+ return assumptions;
1690
+ }
1691
+ /**
1692
+ * Find assumptions that impact a specific artifact type.
1693
+ *
1694
+ * @param impactType The artifact type ('spec', 'tests', 'code')
1695
+ * @returns Array of assumptions
1696
+ */
1697
+ findAssumptionsByImpact(impactType) {
1698
+ const assumptions = [];
1699
+ for (const entry of this.entries) {
1700
+ const currentEntry = this.entryMap.get(entry.id);
1701
+ if (currentEntry.status !== "active") {
1702
+ continue;
1703
+ }
1704
+ for (const assumption of currentEntry.contract.assumptions || []) {
1705
+ if (assumption.status === "active" && assumption.impacts.includes(impactType)) {
1706
+ assumptions.push(assumption);
1707
+ }
1708
+ }
1709
+ }
1710
+ return assumptions;
1711
+ }
1712
+ /**
1713
+ * Get ledger statistics.
1714
+ */
1715
+ getStats() {
1716
+ const currentEntries = this.entries.map((e) => this.entryMap.get(e.id));
1717
+ const active = currentEntries.filter((e) => e.status === "active").length;
1718
+ const superseded = currentEntries.filter((e) => e.status === "superseded").length;
1719
+ const deprecated = currentEntries.filter((e) => e.status === "deprecated").length;
1720
+ const uniqueRules = new Set(currentEntries.map((e) => e.contract.ruleId)).size;
1721
+ return {
1722
+ totalEntries: this.entries.length,
1723
+ activeEntries: active,
1724
+ supersededEntries: superseded,
1725
+ deprecatedEntries: deprecated,
1726
+ uniqueRules
1727
+ };
1728
+ }
1729
+ /**
1730
+ * Export ledger as JSON.
1731
+ *
1732
+ * @returns JSON string with current entry status
1733
+ */
1734
+ toJSON() {
1735
+ return JSON.stringify(
1736
+ {
1737
+ version: "1.0.0",
1738
+ // Export entries with current status from the map
1739
+ entries: this.entries.map((entry) => this.entryMap.get(entry.id)),
1740
+ stats: this.getStats()
1741
+ },
1742
+ null,
1743
+ 2
1744
+ );
1745
+ }
1746
+ /**
1747
+ * Import ledger from JSON.
1748
+ *
1749
+ * Note: The JSON must contain entries in the order they were originally appended.
1750
+ * If a superseding entry appears before the entry it supersedes, the superseding
1751
+ * logic will not work correctly. The toJSON method preserves this order.
1752
+ *
1753
+ * @param json The JSON string
1754
+ * @returns A new BehaviorLedger instance
1755
+ */
1756
+ static fromJSON(json) {
1757
+ const data = JSON.parse(json);
1758
+ const ledger = new _BehaviorLedger();
1759
+ for (const entry of data.entries || []) {
1760
+ ledger.append(entry);
1761
+ }
1762
+ return ledger;
1763
+ }
1764
+ };
1765
+ function createBehaviorLedger() {
1766
+ return new BehaviorLedger();
1767
+ }
1768
+
1769
+ // src/decision-ledger/logic-ledger.ts
1770
+ var import_node_crypto = require("crypto");
1771
+ var import_node_fs = require("fs");
1772
+ var import_node_path = __toESM(require("path"), 1);
1773
+
1774
+ // src/decision-ledger/scanner.ts
1775
+ var import_node_fs2 = require("fs");
1776
+ var import_node_path2 = __toESM(require("path"), 1);
1777
+
1213
1778
  // src/runtime/terminal-adapter.ts
1214
1779
  async function defaultExecutor(command, options) {
1215
1780
  try {
@@ -1330,15 +1895,15 @@ var TerminalAdapter = class {
1330
1895
  * @param path - PluresDB path for storing results
1331
1896
  * @param data - Data to sync
1332
1897
  */
1333
- async syncToPluresDB(path, data) {
1898
+ async syncToPluresDB(path3, data) {
1334
1899
  if (!this.db) return;
1335
1900
  try {
1336
- await this.db.set(path, {
1901
+ await this.db.set(path3, {
1337
1902
  ...data,
1338
1903
  nodeId: this.state.nodeId,
1339
1904
  syncedAt: Date.now()
1340
1905
  });
1341
- const historyPath = `${path}/history`;
1906
+ const historyPath = `${path3}/history`;
1342
1907
  const historyKey = `${historyPath}/${data.timestamp}`;
1343
1908
  await this.db.set(historyKey, data);
1344
1909
  } catch (error) {
@@ -1807,8 +2372,8 @@ var PraxisDBStore = class {
1807
2372
  async persistFact(fact) {
1808
2373
  const payload = fact.payload;
1809
2374
  const id = payload?.id ?? generateId();
1810
- const path = getFactPath(fact.tag, id);
1811
- await this.db.set(path, fact);
2375
+ const path3 = getFactPath(fact.tag, id);
2376
+ await this.db.set(path3, fact);
1812
2377
  }
1813
2378
  /**
1814
2379
  * Get a fact by tag and id
@@ -1818,8 +2383,8 @@ var PraxisDBStore = class {
1818
2383
  * @returns The fact or undefined if not found
1819
2384
  */
1820
2385
  async getFact(factTag, id) {
1821
- const path = getFactPath(factTag, id);
1822
- return this.db.get(path);
2386
+ const path3 = getFactPath(factTag, id);
2387
+ return this.db.get(path3);
1823
2388
  }
1824
2389
  /**
1825
2390
  * Append an event to the event stream
@@ -1829,15 +2394,15 @@ var PraxisDBStore = class {
1829
2394
  * @param event The event to append
1830
2395
  */
1831
2396
  async appendEvent(event) {
1832
- const path = getEventPath(event.tag);
1833
- const existingEvents = await this.db.get(path) ?? [];
2397
+ const path3 = getEventPath(event.tag);
2398
+ const existingEvents = await this.db.get(path3) ?? [];
1834
2399
  const entry = {
1835
2400
  event,
1836
2401
  timestamp: Date.now(),
1837
2402
  sequence: existingEvents.length
1838
2403
  };
1839
2404
  const newEvents = [...existingEvents, entry];
1840
- await this.db.set(path, newEvents);
2405
+ await this.db.set(path3, newEvents);
1841
2406
  await this.triggerRulesForEvents([event]);
1842
2407
  }
1843
2408
  /**
@@ -1852,15 +2417,15 @@ var PraxisDBStore = class {
1852
2417
  eventsByTag.set(event.tag, [...existing, event]);
1853
2418
  }
1854
2419
  for (const [tag, tagEvents] of eventsByTag) {
1855
- const path = getEventPath(tag);
1856
- const existingEvents = await this.db.get(path) ?? [];
2420
+ const path3 = getEventPath(tag);
2421
+ const existingEvents = await this.db.get(path3) ?? [];
1857
2422
  let sequence = existingEvents.length;
1858
2423
  const newEntries = tagEvents.map((event) => ({
1859
2424
  event,
1860
2425
  timestamp: Date.now(),
1861
2426
  sequence: sequence++
1862
2427
  }));
1863
- await this.db.set(path, [...existingEvents, ...newEntries]);
2428
+ await this.db.set(path3, [...existingEvents, ...newEntries]);
1864
2429
  }
1865
2430
  await this.triggerRulesForEvents(events);
1866
2431
  }
@@ -1872,8 +2437,8 @@ var PraxisDBStore = class {
1872
2437
  * @returns Array of event stream entries
1873
2438
  */
1874
2439
  async getEvents(eventTag, options) {
1875
- const path = getEventPath(eventTag);
1876
- const events = await this.db.get(path) ?? [];
2440
+ const path3 = getEventPath(eventTag);
2441
+ const events = await this.db.get(path3) ?? [];
1877
2442
  let result = events;
1878
2443
  if (options?.since !== void 0) {
1879
2444
  const sinceTimestamp = options.since;
@@ -1892,7 +2457,7 @@ var PraxisDBStore = class {
1892
2457
  * @returns Unsubscribe function
1893
2458
  */
1894
2459
  watchFacts(factTag, callback) {
1895
- const path = getFactPath(factTag);
2460
+ const path3 = getFactPath(factTag);
1896
2461
  if (!this.factWatchers.has(factTag)) {
1897
2462
  this.factWatchers.set(factTag, /* @__PURE__ */ new Set());
1898
2463
  }
@@ -1900,7 +2465,7 @@ var PraxisDBStore = class {
1900
2465
  if (watchers) {
1901
2466
  watchers.add(callback);
1902
2467
  }
1903
- const unsubscribe = this.db.watch(path, (fact) => {
2468
+ const unsubscribe = this.db.watch(path3, (fact) => {
1904
2469
  callback([fact]);
1905
2470
  });
1906
2471
  this.subscriptions.push(unsubscribe);
@@ -2024,13 +2589,13 @@ var PraxisSchemaRegistry = class {
2024
2589
  * @param schema The schema to register
2025
2590
  */
2026
2591
  async register(schema) {
2027
- const path = getSchemaPath(schema.name);
2592
+ const path3 = getSchemaPath(schema.name);
2028
2593
  const storedSchema = {
2029
2594
  schema,
2030
2595
  registeredAt: Date.now(),
2031
2596
  version: schema.version
2032
2597
  };
2033
- await this.db.set(path, storedSchema);
2598
+ await this.db.set(path3, storedSchema);
2034
2599
  }
2035
2600
  /**
2036
2601
  * Get a schema by name
@@ -2039,8 +2604,8 @@ var PraxisSchemaRegistry = class {
2039
2604
  * @returns The stored schema or undefined if not found
2040
2605
  */
2041
2606
  async get(schemaName) {
2042
- const path = getSchemaPath(schemaName);
2043
- return this.db.get(path);
2607
+ const path3 = getSchemaPath(schemaName);
2608
+ return this.db.get(path3);
2044
2609
  }
2045
2610
  /**
2046
2611
  * Check if a schema is registered
@@ -3348,29 +3913,29 @@ function createMockTauriBridge() {
3348
3913
  tauriVersion: "mock"
3349
3914
  },
3350
3915
  fs: {
3351
- async readFile(path) {
3352
- const data = storage.get(path);
3916
+ async readFile(path3) {
3917
+ const data = storage.get(path3);
3353
3918
  if (data instanceof Uint8Array) return data;
3354
- throw new Error(`File not found: ${path}`);
3919
+ throw new Error(`File not found: ${path3}`);
3355
3920
  },
3356
- async readTextFile(path) {
3357
- const data = storage.get(path);
3921
+ async readTextFile(path3) {
3922
+ const data = storage.get(path3);
3358
3923
  if (typeof data === "string") return data;
3359
- throw new Error(`File not found: ${path}`);
3924
+ throw new Error(`File not found: ${path3}`);
3360
3925
  },
3361
- async writeFile(path, data) {
3362
- storage.set(path, data);
3926
+ async writeFile(path3, data) {
3927
+ storage.set(path3, data);
3363
3928
  },
3364
- async writeTextFile(path, data) {
3365
- storage.set(path, data);
3929
+ async writeTextFile(path3, data) {
3930
+ storage.set(path3, data);
3366
3931
  },
3367
- async exists(path) {
3368
- return storage.has(path);
3932
+ async exists(path3) {
3933
+ return storage.has(path3);
3369
3934
  },
3370
3935
  async mkdir(_path, _options) {
3371
3936
  },
3372
- async remove(path, _options) {
3373
- storage.delete(path);
3937
+ async remove(path3, _options) {
3938
+ storage.delete(path3);
3374
3939
  },
3375
3940
  async rename(oldPath, newPath) {
3376
3941
  const data = storage.get(oldPath);
@@ -3690,7 +4255,14 @@ async function attachAllIntegrations(engine, registry, options = {}) {
3690
4255
  }
3691
4256
  // Annotate the CommonJS export names for ESM import in node:
3692
4257
  0 && (module.exports = {
4258
+ AcknowledgeContractGap,
3693
4259
  ActorManager,
4260
+ BehaviorLedger,
4261
+ ContractAdded,
4262
+ ContractGapAcknowledged,
4263
+ ContractMissing,
4264
+ ContractUpdated,
4265
+ ContractValidated,
3694
4266
  FrameworkAgnosticReactiveEngine,
3695
4267
  InMemoryPraxisDB,
3696
4268
  LogicEngine,
@@ -3705,6 +4277,7 @@ async function attachAllIntegrations(engine, registry, options = {}) {
3705
4277
  RegistryIntrospector,
3706
4278
  StateDocsGenerator,
3707
4279
  TerminalAdapter,
4280
+ ValidateContracts,
3708
4281
  attachAllIntegrations,
3709
4282
  attachTauriToEngine,
3710
4283
  attachToEngine,
@@ -3712,6 +4285,7 @@ async function attachAllIntegrations(engine, registry, options = {}) {
3712
4285
  canvasToMermaid,
3713
4286
  canvasToSchema,
3714
4287
  canvasToYaml,
4288
+ createBehaviorLedger,
3715
4289
  createCanvasEditor,
3716
4290
  createFrameworkAgnosticReactiveEngine,
3717
4291
  createInMemoryDB,
@@ -3723,6 +4297,7 @@ async function attachAllIntegrations(engine, registry, options = {}) {
3723
4297
  createPluresDBGenerator,
3724
4298
  createPraxisDBStore,
3725
4299
  createPraxisEngine,
4300
+ createPraxisLocalFirst,
3726
4301
  createReactiveEngine,
3727
4302
  createSchemaRegistry,
3728
4303
  createSchemaTemplate,
@@ -3733,6 +4308,7 @@ async function attachAllIntegrations(engine, registry, options = {}) {
3733
4308
  createUnifiedApp,
3734
4309
  createUnumAdapter,
3735
4310
  defineConstraint,
4311
+ defineContract,
3736
4312
  defineEvent,
3737
4313
  defineFact,
3738
4314
  defineModule,
@@ -3741,12 +4317,17 @@ async function attachAllIntegrations(engine, registry, options = {}) {
3741
4317
  filterFacts,
3742
4318
  findEvent,
3743
4319
  findFact,
4320
+ formatValidationReport,
4321
+ formatValidationReportJSON,
4322
+ formatValidationReportSARIF,
3744
4323
  generateDocs,
3745
4324
  generateId,
3746
4325
  generateTauriConfig,
4326
+ getContract,
3747
4327
  getEventPath,
3748
4328
  getFactPath,
3749
4329
  getSchemaPath,
4330
+ isContract,
3750
4331
  loadSchema,
3751
4332
  loadSchemaFromFile,
3752
4333
  loadSchemaFromJson,
@@ -3754,6 +4335,7 @@ async function attachAllIntegrations(engine, registry, options = {}) {
3754
4335
  registerSchema,
3755
4336
  runTerminalCommand,
3756
4337
  schemaToCanvas,
4338
+ validateContracts,
3757
4339
  validateForGeneration,
3758
4340
  validateSchema,
3759
4341
  validateWithGuardian