@plures/praxis 1.2.0 → 1.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +10 -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
@@ -0,0 +1,180 @@
1
+ import {
2
+ ContractMissing,
3
+ formatValidationReport,
4
+ formatValidationReportJSON,
5
+ formatValidationReportSARIF,
6
+ validateContracts
7
+ } from "./chunk-5RH7UAQC.js";
8
+ import {
9
+ writeLogicLedgerEntry
10
+ } from "./chunk-WZ6B3LZ6.js";
11
+ import {
12
+ PraxisRegistry
13
+ } from "./chunk-R2PSBPKQ.js";
14
+ import "./chunk-QGM4M3NI.js";
15
+
16
+ // src/cli/commands/validate.ts
17
+ async function validateCommand(options) {
18
+ const outputFormat = options.output || "console";
19
+ const strict = options.strict || false;
20
+ const registry = await loadRegistry(options.registry);
21
+ const artifactIndex = await buildArtifactIndex(registry, {
22
+ includeTests: options.tests ?? true,
23
+ includeSpec: options.spec ?? true
24
+ });
25
+ const report = validateContracts(registry, {
26
+ strict,
27
+ requiredFields: ["behavior", "examples", "invariants"],
28
+ missingSeverity: strict ? "error" : "warning",
29
+ artifactIndex
30
+ });
31
+ if (options.emitFacts) {
32
+ const facts = gapsToFacts(report.incomplete);
33
+ const events = gapsToEvents(report.incomplete);
34
+ await emitGapArtifacts({ facts, events, gapOutput: options.gapOutput });
35
+ }
36
+ if (options.ledger) {
37
+ await writeLedgerSnapshots(registry, {
38
+ rootDir: options.ledger,
39
+ author: options.author ?? "system",
40
+ artifactIndex
41
+ });
42
+ }
43
+ switch (outputFormat) {
44
+ case "json":
45
+ console.log(formatValidationReportJSON(report));
46
+ break;
47
+ case "sarif":
48
+ console.log(formatValidationReportSARIF(report));
49
+ break;
50
+ case "console":
51
+ default:
52
+ console.log(formatValidationReport(report));
53
+ break;
54
+ }
55
+ if (strict && (report.incomplete.length > 0 || report.missing.length > 0)) {
56
+ const incompleteErrors = report.incomplete.filter((gap) => gap.severity === "error").length;
57
+ const totalErrors = incompleteErrors + report.missing.length;
58
+ if (totalErrors > 0) {
59
+ console.error(`
60
+ \u274C Validation failed: ${totalErrors} error(s) found`);
61
+ process.exit(1);
62
+ }
63
+ }
64
+ if (outputFormat === "console") {
65
+ if (report.incomplete.length === 0 && report.missing.length === 0) {
66
+ console.log("\n\u2705 All contracts validated successfully!");
67
+ } else {
68
+ const warningCount = report.incomplete.filter((gap) => gap.severity === "warning").length;
69
+ if (warningCount > 0) {
70
+ console.log(`
71
+ \u26A0\uFE0F ${warningCount} warning(s) found`);
72
+ }
73
+ }
74
+ }
75
+ }
76
+ async function loadRegistry(registryPath) {
77
+ const registry = new PraxisRegistry();
78
+ if (registryPath) {
79
+ try {
80
+ const module = await import(resolveRegistryPath(registryPath));
81
+ const candidate = module.registry ?? module.default ?? module.createRegistry?.();
82
+ if (candidate && candidate instanceof PraxisRegistry) {
83
+ return candidate;
84
+ }
85
+ throw new Error("Registry module did not export a PraxisRegistry instance");
86
+ } catch (error) {
87
+ console.warn(`Warning: Could not load registry from ${registryPath}:`, error);
88
+ }
89
+ }
90
+ return registry;
91
+ }
92
+ function resolveRegistryPath(registryPath) {
93
+ if (registryPath.startsWith(".") || registryPath.startsWith("/")) {
94
+ return new URL(registryPath, `file://${process.cwd()}/`).href;
95
+ }
96
+ return registryPath;
97
+ }
98
+ async function buildArtifactIndex(registry, options) {
99
+ const index = {};
100
+ const ruleIds = new Set(registry.getRuleIds().concat(registry.getConstraintIds()));
101
+ if (options.includeTests) {
102
+ index.tests = /* @__PURE__ */ new Set();
103
+ for (const id of ruleIds) {
104
+ if (await hasArtifactFile("tests", id)) {
105
+ index.tests.add(id);
106
+ }
107
+ }
108
+ }
109
+ if (options.includeSpec) {
110
+ index.spec = /* @__PURE__ */ new Set();
111
+ for (const id of ruleIds) {
112
+ if (await hasArtifactFile("spec", id)) {
113
+ index.spec.add(id);
114
+ }
115
+ }
116
+ }
117
+ return index;
118
+ }
119
+ async function writeLedgerSnapshots(registry, options) {
120
+ const { rootDir, author, artifactIndex } = options;
121
+ const processDescriptor = async (descriptor) => {
122
+ if (!descriptor.contract && !descriptor.meta?.contract) {
123
+ return;
124
+ }
125
+ const contract = descriptor.contract ?? descriptor.meta?.contract;
126
+ await writeLogicLedgerEntry(contract, {
127
+ rootDir,
128
+ author,
129
+ testsPresent: artifactIndex?.tests?.has(contract.ruleId) ?? false,
130
+ specPresent: artifactIndex?.spec?.has(contract.ruleId) ?? false
131
+ });
132
+ };
133
+ for (const descriptor of registry.getAllRules()) {
134
+ await processDescriptor(descriptor);
135
+ }
136
+ for (const descriptor of registry.getAllConstraints()) {
137
+ await processDescriptor(descriptor);
138
+ }
139
+ }
140
+ async function hasArtifactFile(type, ruleId) {
141
+ const fs = await import("fs/promises");
142
+ const path = await import("path");
143
+ const candidateDirs = type === "tests" ? ["src/__tests__", "tests", "test"] : ["spec", "specs"];
144
+ const sanitized = ruleId.replace(/[^a-zA-Z0-9_-]/g, "_");
145
+ for (const dir of candidateDirs) {
146
+ const fullDir = path.resolve(process.cwd(), dir);
147
+ try {
148
+ const entries = await fs.readdir(fullDir);
149
+ if (entries.some((file) => file.includes(sanitized))) {
150
+ return true;
151
+ }
152
+ } catch {
153
+ }
154
+ }
155
+ return false;
156
+ }
157
+ function gapsToFacts(gaps) {
158
+ return gaps.map(
159
+ (gap) => ContractMissing.create({
160
+ ruleId: gap.ruleId,
161
+ missing: gap.missing,
162
+ severity: gap.severity,
163
+ message: gap.message
164
+ })
165
+ );
166
+ }
167
+ function gapsToEvents(_gaps) {
168
+ return [];
169
+ }
170
+ async function emitGapArtifacts(payload) {
171
+ if (payload.gapOutput) {
172
+ const fs = await import("fs/promises");
173
+ await fs.writeFile(payload.gapOutput, JSON.stringify(payload, null, 2));
174
+ } else {
175
+ console.log(JSON.stringify(payload, null, 2));
176
+ }
177
+ }
178
+ export {
179
+ validateCommand
180
+ };
@@ -50,28 +50,28 @@ npm install @plures/praxis
50
50
 
51
51
  ### Configuration
52
52
 
53
- Configure PluresDB in your application:
53
+ You can choose network-only (previous default) or the new local-first unified API (auto-detects WASM/Tauri/IPC/network).
54
+
55
+ **Network (unchanged):**
54
56
 
55
57
  ```typescript
56
58
  import { createPluresDB } from '@plures/praxis';
59
+ import { PluresNode } from '@plures/pluresdb';
57
60
 
58
- const db = createPluresDB({
59
- // Database name (stored in IndexedDB)
60
- name: 'my-app-db',
61
+ const db = createPluresDB(new PluresNode({ autoStart: true }));
62
+ ```
61
63
 
62
- // Schema version (increment to migrate)
63
- version: 1,
64
+ **Local-first (auto-detect):**
65
+
66
+ ```typescript
67
+ import { createPraxisLocalFirst } from '@plures/praxis';
64
68
 
65
- // Collections to create
66
- collections: ['users', 'posts', 'comments'],
69
+ // Auto mode picks the best backend (WASM in browser, Tauri/IPC on desktop, network fallback)
70
+ const db = await createPraxisLocalFirst({ mode: 'auto' });
67
71
 
68
- // Sync configuration (optional)
69
- sync: {
70
- enabled: true,
71
- endpoint: 'https://your-sync-server.com',
72
- interval: 5000, // ms
73
- },
74
- });
72
+ // Optional: override
73
+ // const db = await createPraxisLocalFirst({ mode: 'wasm', dbName: 'my-app' });
74
+ // const db = await createPraxisLocalFirst({ mode: 'ipc', channelName: 'my-channel' });
75
75
  ```
76
76
 
77
77
  ### From Schema
@@ -0,0 +1,225 @@
1
+ # Decision Ledger Integration - Behavior Ledger
2
+
3
+ **Status**: Active
4
+ **Version**: 1.0.0
5
+ **Created**: 2025-01-26
6
+ **Last Updated**: 2025-01-26
7
+
8
+ ---
9
+
10
+ ## Entry 1: Initial Decision Ledger Integration
11
+
12
+ **ID**: `decision-ledger-v1`
13
+ **Timestamp**: 2025-01-26T00:00:00Z
14
+ **Status**: Active
15
+ **Author**: System
16
+
17
+ ### Canonical Behavior
18
+
19
+ The Decision Ledger Integration provides a mechanism for Praxis applications to:
20
+
21
+ 1. **Define Contracts for Rules/Constraints** with:
22
+ - Canonical behavior description
23
+ - Given/When/Then examples that become test vectors
24
+ - TLA+-friendly invariants or Praxis-level invariants
25
+ - Explicit assumptions with confidence levels
26
+ - References to documentation, tickets, and external links
27
+
28
+ 2. **Validate Compliance** at build-time and runtime:
29
+ - Build-time: `praxis validate` CLI scans registry modules
30
+ - Runtime: Lightweight validation when registering rules
31
+ - Structured diagnostics (JSON, console, optional SARIF)
32
+
33
+ 3. **Track Missing Artifacts** as first-class facts:
34
+ - Fact: `ContractMissing` with ruleId, missing artifacts, severity
35
+ - Event: `ACKNOWLEDGE_CONTRACT_GAP` for explicit acknowledgment
36
+ - Rule: Policy enforcement for contract debt
37
+
38
+ 4. **Maintain Immutable History**:
39
+ - Append-only ledger of behavior changes
40
+ - Assumption invalidation tracking
41
+ - Versioned behavior snapshots
42
+
43
+ ### Examples (Given/When/Then)
44
+
45
+ #### Example 1: Defining a Contract for a Rule
46
+
47
+ **Given**: A rule `auth.login` that processes login events
48
+ **When**: Developer defines the contract with behavior, examples, and invariants
49
+ **Then**: The contract is registered and can be validated
50
+
51
+ ```typescript
52
+ const loginContract = defineContract({
53
+ ruleId: 'auth.login',
54
+ behavior: 'Process login events and create user session facts',
55
+ examples: [
56
+ {
57
+ given: 'User provides valid credentials',
58
+ when: 'LOGIN event is received',
59
+ then: 'UserSessionCreated fact is emitted'
60
+ }
61
+ ],
62
+ invariants: [
63
+ 'Session must have unique ID',
64
+ 'Session must have timestamp'
65
+ ],
66
+ assumptions: [
67
+ {
68
+ id: 'assume-unique-username',
69
+ statement: 'Usernames are unique across the system',
70
+ confidence: 0.9,
71
+ justification: 'Standard practice in authentication systems',
72
+ impacts: ['spec', 'tests']
73
+ }
74
+ ],
75
+ references: [
76
+ { type: 'doc', url: 'https://docs.example.com/auth' }
77
+ ]
78
+ });
79
+ ```
80
+
81
+ #### Example 2: Build-time Validation
82
+
83
+ **Given**: A registry with rules and their contracts
84
+ **When**: `praxis validate` command is run
85
+ **Then**: Validation report shows contract coverage and gaps
86
+
87
+ ```bash
88
+ $ praxis validate
89
+ ✓ auth.login - Contract complete (behavior, examples, tests)
90
+ ✗ cart.addItem - Missing: tests
91
+ ✗ order.process - Missing: contract
92
+ ```
93
+
94
+ #### Example 3: Runtime Validation
95
+
96
+ **Given**: A rule without a complete contract
97
+ **When**: Rule is registered at runtime with `strictContracts: true`
98
+ **Then**: ContractMissing fact is created and policy can enforce action
99
+
100
+ ```typescript
101
+ const registry = createRegistry({ strictContracts: true });
102
+ registry.registerRule(ruleWithoutContract);
103
+ // ContractMissing fact is emitted
104
+ // Policy may warn, error, or allow based on configuration
105
+ ```
106
+
107
+ #### Example 4: Contract Gap Acknowledgment
108
+
109
+ **Given**: A missing contract has been identified
110
+ **When**: Developer acknowledges the gap with justification
111
+ **Then**: ACKNOWLEDGE_CONTRACT_GAP event is recorded
112
+
113
+ ```typescript
114
+ acknowledgeContractGap({
115
+ ruleId: 'legacy.process',
116
+ missing: ['spec', 'tests'],
117
+ justification: 'Legacy rule to be deprecated in v2.0',
118
+ expiresAt: '2025-12-31'
119
+ });
120
+ ```
121
+
122
+ ### Invariants
123
+
124
+ 1. **Contract Immutability**: Once a contract version is published, its core behavior description cannot be changed; only new versions can be created
125
+ 2. **Ledger Append-Only**: The behavior ledger is append-only; entries can be superseded but never deleted
126
+ 3. **Assumption Traceability**: Every assumption must have a stable ID and impacts declaration
127
+ 4. **Example Completeness**: Every contract must have at least one Given/When/Then example
128
+ 5. **Validation Determinism**: Running `praxis validate` multiple times on the same codebase produces identical results
129
+
130
+ ### Assumptions
131
+
132
+ #### A1: Contract Storage Location
133
+ - **ID**: `assume-contract-in-meta`
134
+ - **Statement**: Contracts are stored in the `meta` field of RuleDescriptor and ConstraintDescriptor
135
+ - **Confidence**: 0.95
136
+ - **Justification**: The existing `meta` field is designed for extensibility and already exists in the Praxis architecture
137
+ - **Derived From**: Analysis of src/core/rules.ts and src/dsl/index.ts
138
+ - **Impacts**: Implementation (contract storage), Tests (contract retrieval)
139
+ - **Status**: Active
140
+
141
+ #### A2: JSON Serialization
142
+ - **ID**: `assume-json-serializable`
143
+ - **Statement**: All contract data must be JSON-serializable for cross-language compatibility
144
+ - **Confidence**: 1.0
145
+ - **Justification**: Praxis protocol requirement for all data structures
146
+ - **Derived From**: src/core/protocol.ts PRAXIS_PROTOCOL_VERSION documentation
147
+ - **Impacts**: Spec (type definitions), Implementation (contract types)
148
+ - **Status**: Active
149
+
150
+ #### A3: CLI Extension Pattern
151
+ - **ID**: `assume-commander-pattern`
152
+ - **Statement**: New CLI commands follow the Commander.js pattern established in src/cli/index.ts
153
+ - **Confidence**: 1.0
154
+ - **Justification**: Existing CLI uses Commander.js for all commands
155
+ - **Derived From**: src/cli/index.ts lines 9-12
156
+ - **Impacts**: Implementation (validate command)
157
+ - **Status**: Active
158
+
159
+ #### A4: Test Framework
160
+ - **ID**: `assume-vitest`
161
+ - **Statement**: Tests use Vitest framework with describe/it/expect pattern
162
+ - **Confidence**: 1.0
163
+ - **Justification**: All existing tests use Vitest
164
+ - **Derived From**: src/__tests__/dsl.test.ts, package.json scripts
165
+ - **Impacts**: Tests (testing approach)
166
+ - **Status**: Active
167
+
168
+ #### A5: Contract Optional by Default
169
+ - **ID**: `assume-contract-optional`
170
+ - **Statement**: Contracts are optional by default; strict enforcement requires opt-in configuration
171
+ - **Confidence**: 0.85
172
+ - **Justification**: Backward compatibility with existing Praxis code that doesn't have contracts
173
+ - **Derived From**: User requirements for "integration" rather than breaking change
174
+ - **Impacts**: Implementation (validation logic), Tests (default behavior)
175
+ - **Status**: Active
176
+
177
+ ### References
178
+
179
+ - **Design Doc**: Problem statement provided by user
180
+ - **Praxis Core**: src/core/rules.ts - Rule and Constraint descriptors
181
+ - **Praxis DSL**: src/dsl/index.ts - Helper functions for defining rules
182
+ - **Praxis Protocol**: src/core/protocol.ts - Core protocol versioning and types
183
+ - **CLI Pattern**: src/cli/index.ts - Command structure
184
+
185
+ ### Changes from Previous Version
186
+
187
+ N/A - Initial version
188
+
189
+ ---
190
+
191
+ ## Schema
192
+
193
+ **Ledger Entry Schema**:
194
+ ```typescript
195
+ interface LedgerEntry {
196
+ id: string;
197
+ timestamp: string;
198
+ status: 'active' | 'superseded' | 'deprecated';
199
+ author: string;
200
+ behavior: {
201
+ canonical: string;
202
+ examples: Array<{
203
+ given: string;
204
+ when: string;
205
+ then: string;
206
+ }>;
207
+ invariants: string[];
208
+ };
209
+ assumptions: Array<{
210
+ id: string;
211
+ statement: string;
212
+ confidence: number;
213
+ justification: string;
214
+ derivedFrom: string;
215
+ impacts: Array<'spec' | 'tests' | 'code'>;
216
+ status: 'active' | 'revised' | 'invalidated';
217
+ }>;
218
+ references: Array<{
219
+ type: string;
220
+ url?: string;
221
+ description?: string;
222
+ }>;
223
+ supersedes?: string;
224
+ }
225
+ ```
@@ -0,0 +1,180 @@
1
+ --------------------------- MODULE DecisionLedger ---------------------------
2
+ (*
3
+ * TLA+ Specification for Praxis Decision Ledger Integration
4
+ *
5
+ * This spec models the core invariants and behaviors of the Decision Ledger,
6
+ * ensuring correctness of contract validation and ledger immutability.
7
+ *)
8
+
9
+ EXTENDS Naturals, Sequences, FiniteSets, TLC
10
+
11
+ CONSTANTS
12
+ RuleIds, \* Set of all possible rule IDs
13
+ MaxLedgerSize \* Maximum ledger entries for model checking
14
+
15
+ VARIABLES
16
+ ledger, \* Append-only sequence of ledger entries
17
+ contracts, \* Map: RuleId -> Contract
18
+ facts, \* Set of current facts (including ContractMissing)
19
+ acknowledged \* Set of acknowledged contract gaps
20
+
21
+ vars == <<ledger, contracts, facts, acknowledged>>
22
+
23
+ (*--algorithm DecisionLedger
24
+
25
+ variables
26
+ ledger = <<>>;
27
+ contracts = [r \in {} |-> {}];
28
+ facts = {};
29
+ acknowledged = {};
30
+
31
+ define
32
+ \* Type invariants
33
+ TypeOK ==
34
+ /\ ledger \in Seq([
35
+ id: STRING,
36
+ ruleId: RuleIds,
37
+ timestamp: Nat,
38
+ status: {"active", "superseded", "deprecated"}
39
+ ])
40
+ /\ contracts \in [RuleIds -> [
41
+ behavior: STRING,
42
+ examples: SUBSET [given: STRING, when: STRING, then: STRING],
43
+ invariants: SUBSET STRING
44
+ ]]
45
+ /\ facts \in SUBSET [
46
+ type: STRING,
47
+ ruleId: RuleIds,
48
+ data: [missing: SUBSET STRING, severity: STRING]
49
+ ]
50
+ /\ acknowledged \in SUBSET [ruleId: RuleIds, justification: STRING]
51
+
52
+ \* Ledger is append-only (monotonic growth and immutability)
53
+ LedgerAppendOnly ==
54
+ /\ Len(ledger') >= Len(ledger)
55
+ /\ \A i \in 1..Len(ledger) :
56
+ \* Once an entry is in the ledger, it never changes in any field
57
+ ledger'[i] = ledger[i]
58
+
59
+ \* No duplicate ledger entry IDs
60
+ LedgerUnique ==
61
+ \A i, j \in 1..Len(ledger) :
62
+ i # j => ledger[i].id # ledger[j].id
63
+
64
+ \* Every contract must have at least one example
65
+ ContractExampleCompleteness ==
66
+ \A r \in DOMAIN contracts :
67
+ contracts[r].examples # {}
68
+
69
+ \* ContractMissing facts are accurate
70
+ ContractMissingAccuracy ==
71
+ \A f \in facts :
72
+ f.type = "ContractMissing" =>
73
+ /\ f.ruleId \notin DOMAIN contracts
74
+ \/ contracts[f.ruleId].examples = {}
75
+ \/ contracts[f.ruleId].behavior = ""
76
+
77
+ \* Acknowledged gaps are recorded
78
+ AcknowledgedGapsRecorded ==
79
+ \A a \in acknowledged :
80
+ \E f \in facts :
81
+ /\ f.type = "ContractMissing"
82
+ /\ f.ruleId = a.ruleId
83
+
84
+ \* Validation is deterministic
85
+ ValidationDeterministic ==
86
+ \* Given the same contracts, validation produces the same facts
87
+ \A r \in RuleIds :
88
+ (r \in DOMAIN contracts /\ contracts[r].behavior # "" /\ contracts[r].examples # {})
89
+ => ~\E f \in facts : f.type = "ContractMissing" /\ f.ruleId = r
90
+
91
+ end define;
92
+
93
+ \* Add a contract for a rule
94
+ procedure AddContract(ruleId, behavior, examples, invariants)
95
+ begin
96
+ AddContract:
97
+ if ruleId \notin DOMAIN contracts then
98
+ contracts := contracts @@ (ruleId :> [
99
+ behavior |-> behavior,
100
+ examples |-> examples,
101
+ invariants |-> invariants
102
+ ]);
103
+ \* Remove ContractMissing fact if it exists
104
+ facts := {f \in facts : ~(f.type = "ContractMissing" /\ f.ruleId = ruleId)};
105
+ end if;
106
+ return;
107
+ end procedure;
108
+
109
+ \* Register a rule and validate contract
110
+ procedure RegisterRule(ruleId, hasContract)
111
+ begin
112
+ RegisterRule:
113
+ if ~hasContract /\ ruleId \notin DOMAIN contracts then
114
+ \* Create ContractMissing fact
115
+ facts := facts \union {[
116
+ type |-> "ContractMissing",
117
+ ruleId |-> ruleId,
118
+ data |-> [missing |-> {"behavior", "examples"}, severity |-> "warning"]
119
+ ]};
120
+ end if;
121
+ return;
122
+ end procedure;
123
+
124
+ \* Acknowledge a contract gap
125
+ procedure AcknowledgeGap(ruleId, justification)
126
+ begin
127
+ AcknowledgeGap:
128
+ acknowledged := acknowledged \union {[
129
+ ruleId |-> ruleId,
130
+ justification |-> justification
131
+ ]};
132
+ return;
133
+ end procedure;
134
+
135
+ \* Append to ledger (immutable)
136
+ procedure AppendLedger(entryId, ruleId, timestamp, status)
137
+ begin
138
+ AppendLedger:
139
+ if Len(ledger) < MaxLedgerSize then
140
+ ledger := Append(ledger, [
141
+ id |-> entryId,
142
+ ruleId |-> ruleId,
143
+ timestamp |-> timestamp,
144
+ status |-> status
145
+ ]);
146
+ end if;
147
+ return;
148
+ end procedure;
149
+
150
+ end algorithm; *)
151
+
152
+ =============================================================================
153
+
154
+ \* Key Invariants (for model checking):
155
+ \*
156
+ \* INVARIANT LedgerAppendOnly
157
+ \* The ledger is append-only; existing entries are never modified or deleted
158
+ \*
159
+ \* INVARIANT LedgerUnique
160
+ \* No two ledger entries have the same ID
161
+ \*
162
+ \* INVARIANT ContractExampleCompleteness
163
+ \* Every contract has at least one example
164
+ \*
165
+ \* INVARIANT ContractMissingAccuracy
166
+ \* ContractMissing facts accurately reflect missing contracts
167
+ \*
168
+ \* INVARIANT ValidationDeterministic
169
+ \* Validation produces deterministic results given the same inputs
170
+ \*
171
+ \* PROPERTY Safety
172
+ \* []TypeOK /\ []LedgerAppendOnly /\ []LedgerUnique
173
+ \*
174
+ \* PROPERTY Liveness
175
+ \* Eventually all rules have contracts: <>(DOMAIN contracts = RuleIds)
176
+ \*
177
+ \* Model Checking Notes:
178
+ \* - Set MaxLedgerSize = 5 for tractable model checking
179
+ \* - Set RuleIds = {"rule1", "rule2", "rule3"} for small test cases
180
+ \* - Check temporal properties with TLC