@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.
- package/README.md +93 -96
- package/dist/browser/{adapter-TM4IS5KT.js → adapter-CIMBGDC7.js} +5 -3
- package/dist/browser/{chunk-LE2ZJYFC.js → chunk-K377RW4V.js} +76 -0
- package/dist/{node/chunk-JQ64KMLN.js → browser/chunk-MBVHLOU2.js} +12 -1
- package/dist/browser/index.d.ts +32 -5
- package/dist/browser/index.js +15 -7
- package/dist/browser/integrations/svelte.d.ts +2 -2
- package/dist/browser/integrations/svelte.js +1 -1
- package/dist/browser/{reactive-engine.svelte-C9OpcTHf.d.ts → reactive-engine.svelte-9aS0kTa8.d.ts} +136 -1
- package/dist/node/{adapter-K6DOX6XS.js → adapter-75ISSMWD.js} +5 -3
- package/dist/node/chunk-5RH7UAQC.js +486 -0
- package/dist/{browser/chunk-JQ64KMLN.js → node/chunk-MBVHLOU2.js} +12 -1
- package/dist/node/{chunk-LE2ZJYFC.js → chunk-PRPQO6R5.js} +3 -72
- package/dist/node/chunk-R2PSBPKQ.js +150 -0
- package/dist/node/chunk-WZ6B3LZ6.js +638 -0
- package/dist/node/cli/index.cjs +2316 -832
- package/dist/node/cli/index.js +18 -0
- package/dist/node/components/index.d.cts +3 -2
- package/dist/node/components/index.d.ts +3 -2
- package/dist/node/index.cjs +620 -38
- package/dist/node/index.d.cts +259 -5
- package/dist/node/index.d.ts +259 -5
- package/dist/node/index.js +55 -65
- package/dist/node/integrations/svelte.cjs +76 -0
- package/dist/node/integrations/svelte.d.cts +2 -2
- package/dist/node/integrations/svelte.d.ts +2 -2
- package/dist/node/integrations/svelte.js +2 -1
- package/dist/node/{reactive-engine.svelte-1M4m_C_v.d.cts → reactive-engine.svelte-BFIZfawz.d.cts} +199 -1
- package/dist/node/{reactive-engine.svelte-ChNFn4Hj.d.ts → reactive-engine.svelte-CRNqHlbv.d.ts} +199 -1
- package/dist/node/reverse-W7THPV45.js +193 -0
- package/dist/node/{terminal-adapter-CWka-yL8.d.ts → terminal-adapter-B-UK_Vdz.d.ts} +28 -3
- package/dist/node/{terminal-adapter-CDzxoLKR.d.cts → terminal-adapter-BQSIF5bf.d.cts} +28 -3
- package/dist/node/validate-CNHUULQE.js +180 -0
- package/docs/core/pluresdb-integration.md +15 -15
- package/docs/decision-ledger/BEHAVIOR_LEDGER.md +225 -0
- package/docs/decision-ledger/DecisionLedger.tla +180 -0
- package/docs/decision-ledger/IMPLEMENTATION_SUMMARY.md +217 -0
- package/docs/decision-ledger/LATEST.md +166 -0
- package/docs/guides/cicd-pipeline.md +142 -0
- package/package.json +2 -2
- package/src/__tests__/cli-validate.test.ts +197 -0
- package/src/__tests__/decision-ledger.test.ts +485 -0
- package/src/__tests__/reverse-generator.test.ts +189 -0
- package/src/__tests__/scanner.test.ts +215 -0
- package/src/cli/commands/reverse.ts +289 -0
- package/src/cli/commands/validate.ts +264 -0
- package/src/cli/index.ts +47 -0
- package/src/core/pluresdb/adapter.ts +45 -2
- package/src/core/rules.ts +133 -0
- package/src/decision-ledger/README.md +400 -0
- package/src/decision-ledger/REVERSE_ENGINEERING.md +484 -0
- package/src/decision-ledger/facts-events.ts +121 -0
- package/src/decision-ledger/index.ts +70 -0
- package/src/decision-ledger/ledger.ts +246 -0
- package/src/decision-ledger/logic-ledger.ts +158 -0
- package/src/decision-ledger/reverse-generator.ts +426 -0
- package/src/decision-ledger/scanner.ts +506 -0
- package/src/decision-ledger/types.ts +247 -0
- package/src/decision-ledger/validation.ts +336 -0
- package/src/dsl/index.ts +13 -2
- package/src/index.browser.ts +2 -0
- package/src/index.ts +36 -0
- 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
|
-
|
|
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
|
-
|
|
60
|
-
name: 'my-app-db',
|
|
61
|
+
const db = createPluresDB(new PluresNode({ autoStart: true }));
|
|
62
|
+
```
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
**Local-first (auto-detect):**
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { createPraxisLocalFirst } from '@plures/praxis';
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|