@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,166 @@
1
+ # Decision Ledger Integration - LATEST Behavior Snapshot
2
+
3
+ **Generated From**: BEHAVIOR_LEDGER.md Entry 1 (`decision-ledger-v1`)
4
+ **Generated At**: 2025-01-26
5
+ **Status**: Non-Authoritative (derived from ledger)
6
+
7
+ ---
8
+
9
+ ## Current Behavior Summary
10
+
11
+ The Decision Ledger Integration extends Praxis with contract-based rule and constraint validation, enabling teams to document, test, and enforce behavioral contracts for their logic.
12
+
13
+ ### Core Capabilities
14
+
15
+ 1. **Contract Definition**
16
+ - Attach contracts to rules and constraints via the `meta.contract` field
17
+ - Define canonical behavior, examples, invariants, assumptions, and references
18
+ - Contracts are JSON-serializable and language-neutral
19
+
20
+ 2. **Build-time Validation**
21
+ - `praxis validate` CLI command scans registered rules and constraints
22
+ - Reports contract coverage and identifies gaps
23
+ - Outputs structured diagnostics (JSON, console, SARIF)
24
+
25
+ 3. **Runtime Validation**
26
+ - Optional strict mode for contract enforcement during rule registration
27
+ - Emits `ContractMissing` facts for rules without complete contracts
28
+ - Policy rules can enforce contract requirements
29
+
30
+ 4. **Contract Gap Management**
31
+ - Acknowledge known gaps with `ACKNOWLEDGE_CONTRACT_GAP` event
32
+ - Track technical debt and expiration dates
33
+ - Audit trail of contract coverage over time
34
+
35
+ ### Active Assumptions
36
+
37
+ All assumptions from Entry 1 are active:
38
+ - A1: Contracts stored in `meta` field
39
+ - A2: All contract data is JSON-serializable
40
+ - A3: CLI follows Commander.js pattern
41
+ - A4: Tests use Vitest framework
42
+ - A5: Contracts are optional by default
43
+
44
+ ### Quick Start Example
45
+
46
+ ```typescript
47
+ import { defineRule, defineContract } from '@plures/praxis';
48
+
49
+ // Define a contract
50
+ const loginContract = defineContract({
51
+ ruleId: 'auth.login',
52
+ behavior: 'Process login events and create user session facts',
53
+ examples: [
54
+ {
55
+ given: 'User provides valid credentials',
56
+ when: 'LOGIN event is received',
57
+ then: 'UserSessionCreated fact is emitted'
58
+ }
59
+ ],
60
+ invariants: ['Session must have unique ID']
61
+ });
62
+
63
+ // Attach contract to rule
64
+ const loginRule = defineRule({
65
+ id: 'auth.login',
66
+ description: 'Process login events',
67
+ impl: (state, events) => { /* ... */ },
68
+ meta: { contract: loginContract }
69
+ });
70
+
71
+ // Validate at build time
72
+ // $ praxis validate
73
+ ```
74
+
75
+ ### API Reference
76
+
77
+ #### Contract Types
78
+
79
+ ```typescript
80
+ interface Contract {
81
+ ruleId: string;
82
+ behavior: string;
83
+ examples: Array<{
84
+ given: string;
85
+ when: string;
86
+ then: string;
87
+ }>;
88
+ invariants: string[];
89
+ assumptions?: Assumption[];
90
+ references?: Reference[];
91
+ }
92
+
93
+ interface Assumption {
94
+ id: string;
95
+ statement: string;
96
+ confidence: number; // 0.0 to 1.0
97
+ justification: string;
98
+ derivedFrom?: string;
99
+ impacts: Array<'spec' | 'tests' | 'code'>;
100
+ status: 'active' | 'revised' | 'invalidated';
101
+ }
102
+
103
+ interface Reference {
104
+ type: string;
105
+ url?: string;
106
+ description?: string;
107
+ }
108
+ ```
109
+
110
+ #### Validation API
111
+
112
+ ```typescript
113
+ // CLI
114
+ praxis validate [options]
115
+ --output <format> Output format: json, console, sarif (default: console)
116
+ --strict Exit with error if contracts are missing
117
+ --config <file> Path to validation config file
118
+
119
+ // Programmatic
120
+ import { validateContracts } from '@plures/praxis/decision-ledger';
121
+
122
+ const report = validateContracts(registry, options);
123
+ // report.complete: Contract[]
124
+ // report.incomplete: ContractGap[]
125
+ // report.missing: string[] (rule IDs)
126
+ ```
127
+
128
+ #### Facts and Events
129
+
130
+ ```typescript
131
+ // ContractMissing fact
132
+ const ContractMissing = defineFact<'ContractMissing', {
133
+ ruleId: string;
134
+ missing: Array<'behavior' | 'examples' | 'invariants' | 'tests' | 'spec'>;
135
+ severity: 'warning' | 'error';
136
+ }>('ContractMissing');
137
+
138
+ // ACKNOWLEDGE_CONTRACT_GAP event
139
+ const AcknowledgeContractGap = defineEvent<'ACKNOWLEDGE_CONTRACT_GAP', {
140
+ ruleId: string;
141
+ missing: string[];
142
+ justification: string;
143
+ expiresAt?: string;
144
+ }>('ACKNOWLEDGE_CONTRACT_GAP');
145
+ ```
146
+
147
+ ### Configuration
148
+
149
+ ```typescript
150
+ // praxis.config.ts
151
+ export default {
152
+ decisionLedger: {
153
+ strict: false, // Require contracts for all rules
154
+ validateOnRegister: true, // Validate at runtime
155
+ outputFormat: 'console', // 'json' | 'console' | 'sarif'
156
+ requiredFields: [ // Required contract fields
157
+ 'behavior',
158
+ 'examples'
159
+ ]
160
+ }
161
+ };
162
+ ```
163
+
164
+ ---
165
+
166
+ **Note**: This is a non-authoritative snapshot derived from the behavior ledger. The source of truth is BEHAVIOR_LEDGER.md.
@@ -0,0 +1,142 @@
1
+ # CI/CD Pipeline
2
+
3
+ This document describes the automated continuous integration and deployment pipeline for the Praxis framework.
4
+
5
+ ## Overview
6
+
7
+ The Praxis CI/CD pipeline is fully automated, ensuring that code merged into the `main` branch flows automatically through version bumping, tagging, release creation, and publishing to all package repositories in parallel.
8
+
9
+ ## Pipeline Flow
10
+
11
+ ```
12
+ PR Merged to main
13
+
14
+ Auto Version Bump (based on semver labels)
15
+
16
+ Create Git Tag (v*.*.*)
17
+
18
+ Run Tests & Build
19
+
20
+ Create GitHub Release
21
+
22
+ Publish to Repositories (Parallel)
23
+ ├─→ NPM
24
+ ├─→ JSR (JavaScript Registry)
25
+ └─→ NuGet
26
+ ```
27
+
28
+ ## Workflow Details
29
+
30
+ ### 1. Auto Version Bump (`auto-version-bump.yml`)
31
+
32
+ **Trigger**: When a PR is merged to `main`
33
+
34
+ **Process**:
35
+ - Determines version bump level based on PR labels:
36
+ - `semver:major` → Major version bump (e.g., 1.0.0 → 2.0.0)
37
+ - `semver:minor` → Minor version bump (e.g., 1.0.0 → 1.1.0)
38
+ - `semver:patch` → Patch version bump (e.g., 1.0.0 → 1.0.1) - **default**
39
+ - Updates `package.json` version
40
+ - Creates and pushes a git tag (e.g., `v1.2.3`)
41
+
42
+ **Requirements**: Add one of the semver labels to your PR to control the version bump.
43
+
44
+ ### 2. Release Creation (`release.yml`)
45
+
46
+ **Trigger**: When a tag matching `v*.*.*` is pushed (automatically triggered by auto-version-bump)
47
+
48
+ **Process**:
49
+ - Checks out code at the tagged version
50
+ - Runs full test suite
51
+ - Builds the project
52
+ - Creates a GitHub Release with changelog
53
+ - Automatically triggers the publish workflow
54
+
55
+ ### 3. Publishing (`publish.yml`)
56
+
57
+ **Trigger**: Called automatically by `release.yml` after release creation
58
+
59
+ **Process**: Three parallel publishing jobs run simultaneously:
60
+
61
+ #### NPM Publishing
62
+ - Builds the TypeScript/Node.js package
63
+ - Publishes to npm registry as `@plures/praxis`
64
+ - Requires `NPM_TOKEN` secret
65
+
66
+ #### JSR Publishing
67
+ - Publishes to JavaScript Registry (JSR) using Deno
68
+ - Publishes as `@plures/praxis`
69
+ - Uses OIDC authentication
70
+ - Note: Uses `--allow-dirty` flag to allow publishing from clean checkout state
71
+
72
+ #### NuGet Publishing
73
+ - Extracts version from the most recent git tag
74
+ - Updates version in `.csproj` file before building
75
+ - Builds the C# package
76
+ - Runs C# tests
77
+ - Publishes to NuGet.org as `Praxis`
78
+ - Requires `NUGET_API_KEY` secret
79
+
80
+ **Note**: When the publish workflow is called from the release workflow, it extracts version information from the git tags instead of relying on GitHub context, ensuring consistent versioning across all published packages.
81
+
82
+ ## Additional Workflows
83
+
84
+ ### CI (`ci.yml`)
85
+
86
+ **Trigger**: On push to `main` or pull request
87
+
88
+ **Purpose**: Validates code quality before merging
89
+ - Runs tests on multiple Node.js versions (18.x, 20.x)
90
+ - Runs C# tests on .NET 8
91
+ - Performs Deno compatibility checks
92
+
93
+ ### C# Build Check (`publish-nuget.yml`)
94
+
95
+ **Trigger**: On push to `main` or pull request
96
+
97
+ **Purpose**: Validates C# code builds and tests pass
98
+ - Ensures C# codebase is always in a buildable state
99
+ - Runs independently of publishing workflow
100
+
101
+ ## Secrets Required
102
+
103
+ The following secrets must be configured in the GitHub repository:
104
+
105
+ - `NPM_TOKEN`: NPM authentication token for publishing packages
106
+ - `NUGET_API_KEY`: NuGet API key for publishing packages
107
+
108
+ ## Manual Triggers
109
+
110
+ The main pipeline workflows support `workflow_dispatch`, allowing manual triggering when needed:
111
+
112
+ - **Release Workflow**: Manually create a release and publish for the current state
113
+ - **Publish Workflow**: Re-publish to all repositories without creating a new release
114
+ - **C# Build Check**: Manually validate C# code builds and tests (separate from publishing)
115
+
116
+ ## Best Practices
117
+
118
+ 1. **Always label your PRs**: Use semver labels (`semver:major`, `semver:minor`, or `semver:patch`) to control version bumping
119
+ 2. **Review before merging**: Once merged to `main`, the entire pipeline runs automatically
120
+ 3. **Monitor releases**: Check the Actions tab to ensure all publishing jobs complete successfully
121
+ 4. **Breaking changes**: Use `semver:major` for breaking changes
122
+ 5. **New features**: Use `semver:minor` for new features
123
+ 6. **Bug fixes**: Use `semver:patch` for bug fixes and patches
124
+
125
+ ## Troubleshooting
126
+
127
+ ### Publishing failures
128
+
129
+ If any publishing job fails:
130
+ 1. Check the Actions tab for detailed error logs
131
+ 2. Verify all required secrets are configured
132
+ 3. The workflow can be manually re-triggered using `workflow_dispatch`
133
+
134
+ ### Version conflicts
135
+
136
+ If version bumping fails:
137
+ 1. Ensure no manual version changes conflict with automated bumping
138
+ 2. Check that the main branch is up to date
139
+
140
+ ### Parallel publishing
141
+
142
+ All three repositories (NPM, JSR, NuGet) publish in parallel. If one fails, the others will continue. Check individual job logs for failures.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plures/praxis",
3
- "version": "1.1.3",
3
+ "version": "1.2.10",
4
4
  "description": "The Full Plures Application Framework - declarative schemas, logic engine, component generation, and local-first data",
5
5
  "type": "module",
6
6
  "main": "./dist/node/index.js",
@@ -117,7 +117,7 @@
117
117
  "dependencies": {
118
118
  "commander": "^14.0.2",
119
119
  "js-yaml": "^4.1.1",
120
- "pluresdb": "^1.0.1"
120
+ "@plures/pluresdb": "^1.6.10"
121
121
  },
122
122
  "peerDependencies": {
123
123
  "svelte": "^5.46.0"
@@ -0,0 +1,197 @@
1
+ /**
2
+ * CLI Validate Command - Integration Tests
3
+ *
4
+ * Tests for the praxis validate command with decision ledger features.
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
8
+ import { promises as fs } from 'node:fs';
9
+ import { tmpdir } from 'node:os';
10
+ import path from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { exec } from 'node:child_process';
13
+ import { promisify } from 'node:util';
14
+
15
+ const execAsync = promisify(exec);
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = path.dirname(__filename);
18
+ // Go up from src/__tests__ to project root
19
+ const projectRoot = path.resolve(__dirname, '..', '..');
20
+ const cliPath = path.join(projectRoot, 'dist/node/cli/index.js');
21
+ const sampleRegistryPath = path.join(projectRoot, 'examples/sample-registry.js');
22
+
23
+ describe('CLI Validate Command', () => {
24
+ let tempDir: string;
25
+
26
+ beforeEach(async () => {
27
+ tempDir = path.join(tmpdir(), `praxis-test-${Date.now()}`);
28
+ await fs.mkdir(tempDir, { recursive: true });
29
+ });
30
+
31
+ afterEach(async () => {
32
+ try {
33
+ await fs.rm(tempDir, { recursive: true, force: true });
34
+ } catch (error) {
35
+ // Ignore cleanup errors
36
+ }
37
+ });
38
+
39
+ it('should validate registry and output to console', async () => {
40
+ const { stdout, stderr } = await execAsync(
41
+ `node ${cliPath} validate --registry ${sampleRegistryPath}`
42
+ );
43
+
44
+ expect(stdout).toContain('Contract Validation Report');
45
+ expect(stdout).toContain('Total: 5');
46
+ expect(stdout).toContain('auth.login');
47
+ expect(stderr).toContain('[Praxis][WARN]');
48
+ });
49
+
50
+ it('should output validation report as JSON', async () => {
51
+ const { stdout } = await execAsync(
52
+ `node ${cliPath} validate --registry ${sampleRegistryPath} --output json 2>/dev/null`
53
+ );
54
+
55
+ const report = JSON.parse(stdout);
56
+
57
+ expect(report).toHaveProperty('complete');
58
+ expect(report).toHaveProperty('incomplete');
59
+ expect(report).toHaveProperty('missing');
60
+ expect(report).toHaveProperty('total');
61
+ expect(report.total).toBe(5);
62
+ });
63
+
64
+ it('should output validation report as SARIF', async () => {
65
+ const { stdout } = await execAsync(
66
+ `node ${cliPath} validate --registry ${sampleRegistryPath} --output sarif 2>/dev/null`
67
+ );
68
+
69
+ const sarif = JSON.parse(stdout);
70
+
71
+ expect(sarif).toHaveProperty('version', '2.1.0');
72
+ expect(sarif).toHaveProperty('runs');
73
+ expect(sarif.runs).toHaveLength(1);
74
+ expect(sarif.runs[0].tool.driver.name).toBe('Praxis Decision Ledger');
75
+ });
76
+
77
+ it('should create logic ledger snapshots with --ledger option', async () => {
78
+ const ledgerDir = path.join(tempDir, 'ledger');
79
+
80
+ await execAsync(
81
+ `node ${cliPath} validate --registry ${sampleRegistryPath} --ledger ${ledgerDir} --author "test-user" 2>&1`
82
+ );
83
+
84
+ // Check that ledger directory was created
85
+ const ledgerPath = path.join(ledgerDir, 'logic-ledger');
86
+ const exists = await fs.access(ledgerPath).then(() => true).catch(() => false);
87
+ expect(exists).toBe(true);
88
+
89
+ // Check that index.json was created
90
+ const indexPath = path.join(ledgerPath, 'index.json');
91
+ const indexContent = await fs.readFile(indexPath, 'utf-8');
92
+ const index = JSON.parse(indexContent);
93
+ expect(index).toHaveProperty('byRuleId');
94
+ expect(index.byRuleId).toHaveProperty('auth.login');
95
+
96
+ // Check that a versioned snapshot was created
97
+ // The path in byRuleId is relative to ledgerDir, not ledgerPath
98
+ const ruleDirRelative = index.byRuleId['auth.login'];
99
+ const ruleDir = path.join(ledgerDir, ruleDirRelative);
100
+ const latestPath = path.join(ruleDir, 'LATEST.json');
101
+ const latestContent = await fs.readFile(latestPath, 'utf-8');
102
+ const latest = JSON.parse(latestContent);
103
+ expect(latest).toHaveProperty('ruleId', 'auth.login');
104
+ expect(latest).toHaveProperty('version', 1);
105
+ expect(latest).toHaveProperty('canonicalBehavior');
106
+ expect(latest).toHaveProperty('assumptions');
107
+ expect(latest).toHaveProperty('artifacts');
108
+ expect(latest).toHaveProperty('drift');
109
+ });
110
+
111
+ it('should emit contract gaps as facts with --emit-facts option', async () => {
112
+ const gapFile = path.join(tempDir, 'gaps.json');
113
+
114
+ await execAsync(
115
+ `node ${cliPath} validate --registry ${sampleRegistryPath} --emit-facts --gap-output ${gapFile}`
116
+ );
117
+
118
+ // Check that gap file was created
119
+ const gapContent = await fs.readFile(gapFile, 'utf-8');
120
+ const gaps = JSON.parse(gapContent);
121
+
122
+ expect(gaps).toHaveProperty('facts');
123
+ expect(gaps).toHaveProperty('events');
124
+ expect(Array.isArray(gaps.facts)).toBe(true);
125
+ expect(gaps.facts.length).toBeGreaterThan(0);
126
+
127
+ // Check that facts have correct structure
128
+ const firstFact = gaps.facts[0];
129
+ expect(firstFact).toHaveProperty('tag', 'ContractMissing');
130
+ expect(firstFact).toHaveProperty('payload');
131
+ expect(firstFact.payload).toHaveProperty('ruleId');
132
+ expect(firstFact.payload).toHaveProperty('missing');
133
+ expect(firstFact.payload).toHaveProperty('severity');
134
+ });
135
+
136
+ it('should exit with error code in strict mode if contracts missing', async () => {
137
+ try {
138
+ await execAsync(
139
+ `node ${cliPath} validate --registry ${sampleRegistryPath} --strict 2>&1`
140
+ );
141
+ // Should not reach here - strict mode should exit with error
142
+ throw new Error('Expected command to fail in strict mode');
143
+ } catch (error: any) {
144
+ // Expect non-zero exit code
145
+ expect(error.code).toBe(1);
146
+ // Check that stderr contains error message
147
+ const combinedOutput = error.stdout || error.stderr || '';
148
+ expect(combinedOutput).toContain('Validation failed');
149
+ }
150
+ });
151
+
152
+ it('should handle missing registry gracefully', async () => {
153
+ const { stdout } = await execAsync(
154
+ `node ${cliPath} validate --registry ./non-existent-registry.js`
155
+ );
156
+
157
+ expect(stdout).toContain('Contract Validation Report');
158
+ expect(stdout).toContain('Total: 0');
159
+ expect(stdout).toContain('All contracts validated successfully');
160
+ });
161
+
162
+ it('should track drift when updating contracts', async () => {
163
+ const ledgerDir = path.join(tempDir, 'ledger');
164
+
165
+ // First validation
166
+ await execAsync(
167
+ `node ${cliPath} validate --registry ${sampleRegistryPath} --ledger ${ledgerDir} --author "test-user" 2>&1`
168
+ );
169
+
170
+ // Second validation (simulates contract update)
171
+ await execAsync(
172
+ `node ${cliPath} validate --registry ${sampleRegistryPath} --ledger ${ledgerDir} --author "test-user" 2>&1`
173
+ );
174
+
175
+ // Check that version was incremented
176
+ const indexPath = path.join(ledgerDir, 'logic-ledger', 'index.json');
177
+ const indexContent = await fs.readFile(indexPath, 'utf-8');
178
+ const index = JSON.parse(indexContent);
179
+
180
+ const ruleDirRelative = index.byRuleId['auth.login'];
181
+ const ruleDir = path.join(ledgerDir, ruleDirRelative);
182
+ const latestPath = path.join(ruleDir, 'LATEST.json');
183
+ const latestContent = await fs.readFile(latestPath, 'utf-8');
184
+ const latest = JSON.parse(latestContent);
185
+
186
+ // Second run should have version 2
187
+ expect(latest.version).toBe(2);
188
+
189
+ // Check that v0001.json and v0002.json exist
190
+ const v1Path = path.join(ruleDir, 'v0001.json');
191
+ const v2Path = path.join(ruleDir, 'v0002.json');
192
+ const v1Exists = await fs.access(v1Path).then(() => true).catch(() => false);
193
+ const v2Exists = await fs.access(v2Path).then(() => true).catch(() => false);
194
+ expect(v1Exists).toBe(true);
195
+ expect(v2Exists).toBe(true);
196
+ });
197
+ });