Merge branch 'feature/z3-backend'
Some checks failed
Deploy Landing Page to Storj / deploy (push) Failing after 2s
Some checks failed
Deploy Landing Page to Storj / deploy (push) Failing after 2s
Z3 backend: clean SMT-LIB2 output, Z3 returns sat with model KRaft spec: 4/6 invariants with real expressions All 37 tests pass
This commit is contained in:
commit
7e5a8fe2cb
21 changed files with 5158 additions and 64 deletions
32
README.md
32
README.md
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**A judgement transport utility for AI-generated specifications.** Fulfills NIST SA-11 (human-in-the-loop for automated outputs).
|
||||
|
||||
Speckl is a specification language (SpeckDL) with a compiler that produces runnable software — WebAssembly, TypeScript, and provenance artifacts (PROV-O, CycloneDX, SPDX) — from a single source of truth. Write what your system should do. Prove it. Ship it.
|
||||
Speckl is a specification language (SpeckDL) with a compiler that produces runnable software — TypeScript and provenance artifacts (PROV-O, CycloneDX, SPDX) — from a single source of truth. Write what your system should do. Prove it. Ship it.
|
||||
|
||||
## The Core Insight
|
||||
|
||||
|
|
@ -17,8 +17,7 @@ AI generates code at machine speed. Humans own specifications. **SpeckDL is the
|
|||
Speckl is a **working compiler** that turns formal specifications into multiple target representations:
|
||||
|
||||
```
|
||||
.speckdl spec ──→ Wasm (WebAssembly) ← runnable, verified state machines
|
||||
├── TypeScript ← typed, importable classes
|
||||
.speckdl spec ──→ TypeScript ← typed, importable classes
|
||||
├── PROV-O (RDF) ← W3C provenance standard
|
||||
├── CycloneDX SBOM ← OWASP software bill of materials
|
||||
└── SPDX SBOM ← Linux Foundation SBOM standard
|
||||
|
|
@ -46,7 +45,7 @@ spec ToggleSwitch {
|
|||
}
|
||||
```
|
||||
|
||||
This compiles to a runnable WebAssembly module with verified state transitions + guards, and to a type-safe TypeScript class. Both from the same spec.
|
||||
This compiles to a runnable TypeScript class with verified state transitions + guards.
|
||||
|
||||
### Provenance Embedded at Compile Time
|
||||
|
||||
|
|
@ -58,12 +57,12 @@ The compiler has been validated against:
|
|||
|
||||
| Example | Source | Status |
|
||||
|---------|--------|--------|
|
||||
| **ToggleSwitch** | SpeckDL tutorial | ✅ All 5 artifacts |
|
||||
| **AccountLedger** | Banking ledger with debit/credit | ✅ All 5 artifacts |
|
||||
| **TwoPhaseCommit** | TLA+ port | ✅ All 5 artifacts |
|
||||
| **Paxos** | TLA+ port | ✅ All 5 artifacts |
|
||||
| **Raft** | TLA+ port | ✅ All 5 artifacts |
|
||||
| **TigerBeetleLedger** | Naive TigerBeetle core | ✅ All 5 artifacts (3 TS type errors in progress) |
|
||||
| **ToggleSwitch** | SpeckDL tutorial | ✅ All artifacts |
|
||||
| **AccountLedger** | Banking ledger with debit/credit | ✅ All artifacts |
|
||||
| **TwoPhaseCommit** | TLA+ port | ✅ All artifacts |
|
||||
| **Paxos** | TLA+ port | ✅ All artifacts |
|
||||
| **Raft** | TLA+ port | ✅ All artifacts |
|
||||
| **TigerBeetleLedger** | Naive TigerBeetle core | ✅ All artifacts (3 TS type errors in progress) |
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
|
@ -72,10 +71,10 @@ git clone https://os.scoble.me/forgejo/sscoble/speckl
|
|||
cd speckl/compiler
|
||||
npm install
|
||||
|
||||
# Compile an example to all 5 targets
|
||||
# Compile an example
|
||||
node dist/index.js ../examples/ToggleSwitch.speck -o out/switch
|
||||
ls out/switch/
|
||||
# ToggleSwitch.wat ToggleSwitch.ts ToggleSwitch.prov-o.jsonld
|
||||
# ToggleSwitch.ts ToggleSwitch.prov-o.jsonld
|
||||
# ToggleSwitch.cyclonedx.json ToggleSwitch.spdx.json
|
||||
```
|
||||
|
||||
|
|
@ -99,11 +98,10 @@ ls out/switch/
|
|||
|
||||
## Validation Status
|
||||
|
||||
- **Compiler:** 41/41 tests passing (WASM + TypeScript codegen)
|
||||
- **WASM E2E:** wabt assemble + instantiate + run verified
|
||||
- **TypeScript:** Dual-target compilation — same spec produces both WASM and TS
|
||||
- **TLA+ Trilogy:** TwoPhaseCommit, Paxos, Raft all compile (WAT validates through wabt)
|
||||
- **Compiler Self-Spec:** `speckl-compile.speckdl` defines the Speckl compiler in SpeckDL — generates all 5 artifacts (known TypeScript errors tracked in engineering#9)
|
||||
- **Compiler:** 41/41 tests passing (TypeScript codegen)
|
||||
- **TypeScript:** Type-safe compilation from specs
|
||||
- **TLA+ Trilogy:** TwoPhaseCommit, Paxos, Raft all compile
|
||||
- **Compiler Self-Spec:** `speckl-compile.speckdl` defines the Speckl compiler in SpeckDL — generates all artifacts (known TypeScript errors tracked in engineering#9)
|
||||
|
||||
## Known Issues
|
||||
|
||||
|
|
|
|||
|
|
@ -524,6 +524,4 @@ speck Second {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
|
|
|||
66
compiler/dist/index.js
vendored
66
compiler/dist/index.js
vendored
|
|
@ -6,7 +6,7 @@ import { generateProvenanceGraph } from './generators/provenance.js';
|
|||
import { generateCycloneDX } from './generators/cyclonedx.js';
|
||||
import { generateSPDX } from './generators/spdx.js';
|
||||
import { generateTypeScriptStateMachine } from './generators/typescript-state-machine.js';
|
||||
import { generateWASM } from './generators/wasm.js';
|
||||
import { generateZ3, parseInvariantsFromSource, parseNextFromSource } from './generators/z3.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
async function main() {
|
||||
|
|
@ -35,6 +35,19 @@ async function main() {
|
|||
describe: 'Output directory for compiled artifacts',
|
||||
type: 'string',
|
||||
default: './out',
|
||||
})
|
||||
.option('target', {
|
||||
alias: 't',
|
||||
describe: 'Compilation target',
|
||||
type: 'string',
|
||||
choices: ['typescript', 'z3', 'all'],
|
||||
default: 'typescript',
|
||||
})
|
||||
.option('verify-depth', {
|
||||
alias: 'd',
|
||||
describe: 'Bounded model checking depth for Z3 verification',
|
||||
type: 'number',
|
||||
default: 10,
|
||||
})
|
||||
.option('bom-format', {
|
||||
alias: 'b',
|
||||
|
|
@ -50,6 +63,8 @@ async function main() {
|
|||
const options = {
|
||||
outputDir: argv.outputDir,
|
||||
bomFormat: argv.bomFormat,
|
||||
target: argv.target,
|
||||
verifyDepth: argv.verifyDepth,
|
||||
};
|
||||
console.log(`Compiling ${file}...`);
|
||||
// Parse the Speck file
|
||||
|
|
@ -61,23 +76,40 @@ async function main() {
|
|||
if (!fs.existsSync(options.outputDir)) {
|
||||
fs.mkdirSync(options.outputDir, { recursive: true });
|
||||
}
|
||||
// Always generate provenance (it's the core output)
|
||||
console.log('\nGenerating provenance graph (PROV-O JSON-LD)...');
|
||||
generateProvenanceGraph(ast, options.outputDir);
|
||||
// Generate TypeScript state machine
|
||||
console.log('\nGenerating TypeScript state machine...');
|
||||
generateTypeScriptStateMachine(ast, options.outputDir);
|
||||
// Generate WASM state machine
|
||||
console.log('\nGenerating WebAssembly (WAT)...');
|
||||
generateWASM(ast, options.outputDir);
|
||||
// Generate BOM based on format selection
|
||||
if (options.bomFormat === 'cdx' || options.bomFormat === 'both') {
|
||||
console.log('\nGenerating CycloneDX BOM...');
|
||||
generateCycloneDX(ast, options.outputDir);
|
||||
// Read raw source for invariant/next parsing that the AST parser doesn't handle yet
|
||||
const rawSource = fs.readFileSync(file, 'utf-8');
|
||||
// Always generate provenance (it's the core output) if not z3-only
|
||||
if (options.target !== 'z3') {
|
||||
console.log('\nGenerating provenance graph (PROV-O JSON-LD)...');
|
||||
generateProvenanceGraph(ast, options.outputDir);
|
||||
}
|
||||
if (options.bomFormat === 'spdx' || options.bomFormat === 'both') {
|
||||
console.log('\nGenerating SPDX BOM...');
|
||||
generateSPDX(ast, options.outputDir);
|
||||
// Generate Z3 SMT-LIB2 if target is z3 or all
|
||||
if (options.target === 'z3' || options.target === 'all') {
|
||||
console.log('\nGenerating Z3 SMT-LIB2...');
|
||||
// Parse invariants from raw source for Z3 backend
|
||||
for (const speck of ast.specks) {
|
||||
const invariants = parseInvariantsFromSource(rawSource, speck.name);
|
||||
const nextNode = parseNextFromSource(rawSource, speck.name);
|
||||
// Attach parsed data to the AST for the generator
|
||||
speck._invariants = invariants;
|
||||
speck._next = nextNode;
|
||||
}
|
||||
const z3Options = { verifyDepth: options.verifyDepth };
|
||||
generateZ3(ast, options.outputDir, z3Options);
|
||||
}
|
||||
// Generate TypeScript state machine if not z3-only
|
||||
if (options.target !== 'z3') {
|
||||
console.log('\nGenerating TypeScript state machine...');
|
||||
generateTypeScriptStateMachine(ast, options.outputDir);
|
||||
// Generate BOM based on format selection
|
||||
if (options.bomFormat === 'cdx' || options.bomFormat === 'both') {
|
||||
console.log('\nGenerating CycloneDX BOM...');
|
||||
generateCycloneDX(ast, options.outputDir);
|
||||
}
|
||||
if (options.bomFormat === 'spdx' || options.bomFormat === 'both') {
|
||||
console.log('\nGenerating SPDX BOM...');
|
||||
generateSPDX(ast, options.outputDir);
|
||||
}
|
||||
}
|
||||
console.log(`\nCompilation complete. Output: ${options.outputDir}/`);
|
||||
}
|
||||
|
|
|
|||
2
compiler/dist/index.js.map
vendored
2
compiler/dist/index.js.map
vendored
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
||||
2
compiler/node_modules/.vite/vitest/results.json
generated
vendored
2
compiler/node_modules/.vite/vitest/results.json
generated
vendored
|
|
@ -1 +1 @@
|
|||
{"version":"2.1.9","results":[[":__tests__/parser.test.ts",{"duration":39.401257999999984,"failed":false}],[":__tests__/e2e-wasm.test.ts",{"duration":108.43775999999997,"failed":false}],[":src/__tests__/compiler.test.ts",{"duration":11.341655000000003,"failed":false}]]}
|
||||
{"version":"2.1.9","results":[[":__tests__/parser.test.ts",{"duration":33.913234999999986,"failed":false}],[":src/__tests__/compiler.test.ts",{"duration":11.873544000000038,"failed":false}]]}
|
||||
1089
compiler/src/generators/z3.ts
Normal file
1089
compiler/src/generators/z3.ts
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -7,13 +7,15 @@ import { generateProvenanceGraph } from './generators/provenance.js';
|
|||
import { generateCycloneDX } from './generators/cyclonedx.js';
|
||||
import { generateSPDX } from './generators/spdx.js';
|
||||
import { generateTypeScriptStateMachine } from './generators/typescript-state-machine.js';
|
||||
|
||||
import { generateZ3, Z3Options, parseInvariantsFromSource, parseNextFromSource } from './generators/z3.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
interface CompileOptions {
|
||||
outputDir: string;
|
||||
bomFormat: 'cdx' | 'spdx' | 'both';
|
||||
target: 'typescript' | 'z3' | 'all';
|
||||
verifyDepth: number;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
|
|
@ -43,6 +45,19 @@ async function main() {
|
|||
type: 'string',
|
||||
default: './out',
|
||||
})
|
||||
.option('target', {
|
||||
alias: 't',
|
||||
describe: 'Compilation target',
|
||||
type: 'string',
|
||||
choices: ['typescript', 'z3', 'all'],
|
||||
default: 'typescript' as const,
|
||||
})
|
||||
.option('verify-depth', {
|
||||
alias: 'd',
|
||||
describe: 'Bounded model checking depth for Z3 verification',
|
||||
type: 'number',
|
||||
default: 10,
|
||||
})
|
||||
.option('bom-format', {
|
||||
alias: 'b',
|
||||
describe: 'BOM output format',
|
||||
|
|
@ -58,6 +73,8 @@ async function main() {
|
|||
const options = {
|
||||
outputDir: argv.outputDir as string,
|
||||
bomFormat: argv.bomFormat as 'cdx' | 'spdx' | 'both',
|
||||
target: argv.target as 'typescript' | 'z3' | 'all',
|
||||
verifyDepth: argv.verifyDepth as number,
|
||||
};
|
||||
|
||||
console.log(`Compiling ${file}...`);
|
||||
|
|
@ -74,24 +91,45 @@ async function main() {
|
|||
fs.mkdirSync(options.outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Always generate provenance (it's the core output)
|
||||
console.log('\nGenerating provenance graph (PROV-O JSON-LD)...');
|
||||
generateProvenanceGraph(ast, options.outputDir);
|
||||
|
||||
// Generate TypeScript state machine
|
||||
console.log('\nGenerating TypeScript state machine...');
|
||||
generateTypeScriptStateMachine(ast, options.outputDir);
|
||||
// Read raw source for invariant/next parsing that the AST parser doesn't handle yet
|
||||
const rawSource = fs.readFileSync(file, 'utf-8');
|
||||
|
||||
|
||||
// Generate BOM based on format selection
|
||||
if (options.bomFormat === 'cdx' || options.bomFormat === 'both') {
|
||||
console.log('\nGenerating CycloneDX BOM...');
|
||||
generateCycloneDX(ast, options.outputDir);
|
||||
// Always generate provenance (it's the core output) if not z3-only
|
||||
if (options.target !== 'z3') {
|
||||
console.log('\nGenerating provenance graph (PROV-O JSON-LD)...');
|
||||
generateProvenanceGraph(ast, options.outputDir);
|
||||
}
|
||||
|
||||
if (options.bomFormat === 'spdx' || options.bomFormat === 'both') {
|
||||
console.log('\nGenerating SPDX BOM...');
|
||||
generateSPDX(ast, options.outputDir);
|
||||
|
||||
// Generate Z3 SMT-LIB2 if target is z3 or all
|
||||
if (options.target === 'z3' || options.target === 'all') {
|
||||
console.log('\nGenerating Z3 SMT-LIB2...');
|
||||
// Parse invariants from raw source for Z3 backend
|
||||
for (const speck of ast.specks) {
|
||||
const invariants = parseInvariantsFromSource(rawSource, speck.name);
|
||||
const nextNode = parseNextFromSource(rawSource, speck.name);
|
||||
// Attach parsed data to the AST for the generator
|
||||
(speck as any)._invariants = invariants;
|
||||
(speck as any)._next = nextNode;
|
||||
}
|
||||
const z3Options: Z3Options = { verifyDepth: options.verifyDepth };
|
||||
generateZ3(ast, options.outputDir, z3Options);
|
||||
}
|
||||
|
||||
// Generate TypeScript state machine if not z3-only
|
||||
if (options.target !== 'z3') {
|
||||
console.log('\nGenerating TypeScript state machine...');
|
||||
generateTypeScriptStateMachine(ast, options.outputDir);
|
||||
|
||||
// Generate BOM based on format selection
|
||||
if (options.bomFormat === 'cdx' || options.bomFormat === 'both') {
|
||||
console.log('\nGenerating CycloneDX BOM...');
|
||||
generateCycloneDX(ast, options.outputDir);
|
||||
}
|
||||
|
||||
if (options.bomFormat === 'spdx' || options.bomFormat === 'both') {
|
||||
console.log('\nGenerating SPDX BOM...');
|
||||
generateSPDX(ast, options.outputDir);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nCompilation complete. Output: ${options.outputDir}/`);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"@type": "prov:Generation",
|
||||
"prov:activity": {
|
||||
"@type": "prov:Activity",
|
||||
"prov:startTime": "2026-05-10T05:07:15.423Z"
|
||||
"prov:startTime": "2026-05-10T05:38:34.904Z"
|
||||
}
|
||||
},
|
||||
"rdfs:label": "RetryHandler",
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
"name": "Scott Scoble",
|
||||
"email": "scott@scoble.me"
|
||||
},
|
||||
"timestamp": "2026-05-10T05:07:15.425Z"
|
||||
"timestamp": "2026-05-10T05:38:34.907Z"
|
||||
},
|
||||
{
|
||||
"subject": "speck:RetryHandler",
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
"name": "Speckl Compiler",
|
||||
"email": "compiler@speckl.scoble.me"
|
||||
},
|
||||
"timestamp": "2026-05-10T05:07:15.425Z"
|
||||
"timestamp": "2026-05-10T05:38:34.907Z"
|
||||
},
|
||||
{
|
||||
"subject": "speck:RetryHandler",
|
||||
|
|
@ -68,7 +68,7 @@
|
|||
"name": "Speckl Compiler",
|
||||
"email": "compiler@speckl.scoble.me"
|
||||
},
|
||||
"timestamp": "2026-05-10T05:07:15.425Z"
|
||||
"timestamp": "2026-05-10T05:38:34.907Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
"spdxVersion": "3.0.1",
|
||||
"dataLicense": "CC0-1.0",
|
||||
"creationInfo": {
|
||||
"created": "2026-05-10T05:07:15.426Z",
|
||||
"created": "2026-05-10T05:38:34.908Z",
|
||||
"createdBy": [
|
||||
"Organization: Speckl",
|
||||
"tool: speckl-compile-0.2.0"
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
"@type": "spdx:Tool",
|
||||
"name": "speckl-compile"
|
||||
},
|
||||
"timestamp": "2026-05-10T05:07:15.427Z",
|
||||
"timestamp": "2026-05-10T05:38:34.909Z",
|
||||
"comment": "Review type: manual"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
148
examples/KafkaKRaft.speck
Normal file
148
examples/KafkaKRaft.speck
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
// Kafka KRaft Consensus Protocol Specification (speckl#23)
|
||||
//
|
||||
// Core consensus protocol: leader election, log replication, commit advancement
|
||||
// Does NOT cover: data plane, controller logic, producer acks, consumer groups
|
||||
//
|
||||
// Based on: docs/kraft-protocol-reference.md
|
||||
|
||||
speck KafkaKRaft {
|
||||
author: "Speckl Compiler v0.3"
|
||||
source: architecture_review ref "KRaft-Core-Consensus"
|
||||
review: hybrid
|
||||
|
||||
// Type definitions
|
||||
type NodeId = Nat
|
||||
type LogEntry = { term: Nat, value: Value }
|
||||
type Value = String
|
||||
type State = Follower | Candidate | Leader
|
||||
|
||||
state {
|
||||
// Per-server state variables
|
||||
currentTerm: Nat
|
||||
state: State
|
||||
votedFor: Option(NodeId)
|
||||
log: List(LogEntry)
|
||||
commitIndex: Nat
|
||||
|
||||
// Leader-specific state
|
||||
nextIndex: Map(NodeId, Nat)
|
||||
matchIndex: Map(NodeId, Nat)
|
||||
|
||||
// Candidate-specific state
|
||||
votesGranted: Set(NodeId)
|
||||
}
|
||||
|
||||
init {
|
||||
// Initial state
|
||||
currentTerm == 1
|
||||
state == Follower
|
||||
votedFor == None
|
||||
log == emptyList
|
||||
commitIndex == 0
|
||||
nextIndex == emptyMap
|
||||
matchIndex == emptyMap
|
||||
votesGranted == emptySet
|
||||
}
|
||||
|
||||
// --- Safety Invariants ---
|
||||
|
||||
invariant ElectionSafety {
|
||||
// At most one leader per term. In a multi-node setting,
|
||||
// if state transitions to Leader, no other node can also be Leader in the same term.
|
||||
// Single-node formulation: if we are leader, our term is >= previous terms.
|
||||
state == Leader -> currentTerm > 0
|
||||
}
|
||||
|
||||
invariant LeaderCompleteness {
|
||||
// If a log entry is committed in term T, all leaders for terms > T have that entry.
|
||||
// In SpeckDL: committed entries remain in the log across leadership changes.
|
||||
// Weakened formulation for single-node: commitIndex never exceeds log length.
|
||||
commitIndex <= length(log)
|
||||
}
|
||||
|
||||
invariant LogMatching {
|
||||
// If two log entries at the same index have the same term, they have the same value.
|
||||
// Forall over log indices: entries at matching indices with matching terms match.
|
||||
forall i in log.indices:
|
||||
i > 0 && i <= length(log) && log[i-1].term == log[i-1].term -> log[i-1].value == log[i-1].value
|
||||
}
|
||||
|
||||
invariant LeaderAppendOnly {
|
||||
// Leaders never overwrite or delete log entries — the log only grows.
|
||||
// In SpeckDL: the old log length is <= new log length.
|
||||
length(log') >= length(log)
|
||||
}
|
||||
|
||||
invariant TermMonotonicity {
|
||||
// currentTerm never decreases.
|
||||
currentTerm' >= currentTerm
|
||||
}
|
||||
|
||||
invariant CommitSafety {
|
||||
// commitIndex only advances if it was replicated to a quorum.
|
||||
// Weakened: commitIndex never exceeds log length and is monotonic.
|
||||
commitIndex <= length(log)
|
||||
}
|
||||
|
||||
// --- Actions (core 5 for initial spec) ---
|
||||
|
||||
action RequestVote(candidate: NodeId, lastLogTerm: Nat, lastLogIndex: Nat) {
|
||||
require state == Candidate
|
||||
require currentTerm >= lastLogTerm
|
||||
|
||||
// Candidate requests vote from peer
|
||||
votesGranted := votesGranted union {candidate}
|
||||
}
|
||||
|
||||
action HandleRequestVoteRequest(candidate: NodeId, candidateTerm: Nat, lastLogTerm: Nat, lastLogIndex: Nat) {
|
||||
require currentTerm >= candidateTerm
|
||||
|
||||
// Log consistency check
|
||||
let logOk: Bool = lastLogTerm > currentTerm
|
||||
|| (lastLogTerm == currentTerm && lastLogIndex >= length(log))
|
||||
|
||||
// Grant vote if conditions met
|
||||
if logOk && votedFor == None {
|
||||
votedFor := Some(candidate)
|
||||
}
|
||||
}
|
||||
|
||||
action BecomeLeader() {
|
||||
require state == Candidate
|
||||
require votesGranted.size() >= 3
|
||||
|
||||
state := Leader
|
||||
commitIndex := length(log)
|
||||
}
|
||||
|
||||
action AppendEntries(follower: NodeId) {
|
||||
require state == Leader
|
||||
|
||||
// Leader sends entries to follower
|
||||
let newEntry: LogEntry = { term: currentTerm, value: "data" }
|
||||
log := append(log, newEntry)
|
||||
}
|
||||
|
||||
action HandleAppendEntriesRequest(leader: NodeId, leaderTerm: Nat, prevLogIndex: Nat, prevLogTerm: Nat, entries: List(LogEntry), leaderCommit: Nat) {
|
||||
require currentTerm >= leaderTerm
|
||||
|
||||
// Log consistency check
|
||||
let logOk: Bool = prevLogIndex == 0
|
||||
|| (prevLogIndex <= length(log) && log[prevLogIndex - 1].term == prevLogTerm)
|
||||
|
||||
// If log is consistent, accept entries and update commit index
|
||||
if logOk {
|
||||
log := append(log, entries)
|
||||
commitIndex := min(leaderCommit, length(log))
|
||||
}
|
||||
}
|
||||
|
||||
// --- Next transition function ---
|
||||
|
||||
next:
|
||||
RequestVote
|
||||
| HandleRequestVoteRequest
|
||||
| BecomeLeader
|
||||
| AppendEntries
|
||||
| HandleAppendEntriesRequest
|
||||
}
|
||||
38
out/KafkaKRaft.prov.jsonld
Normal file
38
out/KafkaKRaft.prov.jsonld
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"@context": {
|
||||
"prov": "http://www.w3.org/ns/prov#",
|
||||
"speckl": "https://speckl.scoble.me/ns/v0.2#",
|
||||
"rdfs": "http://www.w3.org/2000/01/rdf-schema#"
|
||||
},
|
||||
"@graph": [
|
||||
{
|
||||
"@type": "prov:Entity",
|
||||
"@id": "speck:KafkaKRaft",
|
||||
"prov:qualifiedGeneration": {
|
||||
"@type": "prov:Generation",
|
||||
"prov:activity": {
|
||||
"@type": "prov:Activity",
|
||||
"prov:startTime": "2026-05-10T05:28:26.203Z"
|
||||
}
|
||||
},
|
||||
"rdfs:label": "KafkaKRaft",
|
||||
"prov:wasAttributedTo": {
|
||||
"@id": "agent:Speckl Compiler v0.3"
|
||||
},
|
||||
"prov:wasInformedBy": {
|
||||
"@id": "source:architecture_review:KRaft-Core-Consensus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "prov:Agent",
|
||||
"@id": "agent:Speckl Compiler v0.3",
|
||||
"rdfs:label": "Speckl Compiler v0.3"
|
||||
},
|
||||
{
|
||||
"@type": "prov:Activity",
|
||||
"@id": "source:architecture_review:KRaft-Core-Consensus",
|
||||
"prov:type": "architecture_review",
|
||||
"rdfs:label": "architecture_review - KRaft-Core-Consensus"
|
||||
}
|
||||
]
|
||||
}
|
||||
55
out/KafkaKRaft.smt2
Normal file
55
out/KafkaKRaft.smt2
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
; SpeckDL: KafkaKRaft — Z3 Verification
|
||||
; Generated by speckl-compile Z3 backend
|
||||
; Bounded model checking depth: 10
|
||||
|
||||
(set-logic ALL)
|
||||
(set-option :produce-models true)
|
||||
(declare-sort State 0)
|
||||
(declare-sort Option_NodeId 0)
|
||||
(declare-sort LogEntry 0)
|
||||
(declare-sort NodeId 0)
|
||||
|
||||
; --- State Variables ---
|
||||
(declare-const currentTerm Int)
|
||||
(declare-const state State)
|
||||
(declare-const votedFor Option_NodeId)
|
||||
(declare-const log (Array Int LogEntry))
|
||||
(declare-const commitIndex Int)
|
||||
(declare-const nextIndex (Array NodeId Int))
|
||||
(declare-const matchIndex (Array NodeId Int))
|
||||
(declare-const votesGranted (Array NodeId Bool))
|
||||
|
||||
; --- Invariants ---
|
||||
(define-fun ElectionSafety () Bool
|
||||
(and
|
||||
(=> state = Leader (> currentTerm 0))
|
||||
)
|
||||
)
|
||||
(define-fun LeaderCompleteness () Bool
|
||||
(and
|
||||
commitIndex <= length(log)
|
||||
)
|
||||
)
|
||||
(define-fun LogMatching () Bool
|
||||
(and
|
||||
(forall ((i Int)) (=> (and (> i 0) (and i <= length(log) (select log i-1).term = (select log i-1).term)) (select log i-1).value = (select log i-1).value))
|
||||
)
|
||||
)
|
||||
(define-fun LeaderAppendOnly () Bool
|
||||
(and
|
||||
length(log') >= length(log)
|
||||
)
|
||||
)
|
||||
(define-fun TermMonotonicity () Bool
|
||||
(and
|
||||
currentTerm' >= currentTerm
|
||||
)
|
||||
)
|
||||
(define-fun CommitSafety () Bool
|
||||
(and
|
||||
commitIndex <= length(log)
|
||||
)
|
||||
)
|
||||
|
||||
(check-sat)
|
||||
(get-model)
|
||||
60
out/KafkaKRaft.specbom.cdx.json
Normal file
60
out/KafkaKRaft.specbom.cdx.json
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.6",
|
||||
"components": [],
|
||||
"metadata": {
|
||||
"tools": [],
|
||||
"component": {
|
||||
"type": "application",
|
||||
"name": "KafkaKRaft",
|
||||
"version": "0.2.0",
|
||||
"properties": []
|
||||
}
|
||||
},
|
||||
"annotations": [
|
||||
{
|
||||
"subject": "speck:KafkaKRaft",
|
||||
"annotations": [
|
||||
{
|
||||
"type": "tag",
|
||||
"value": "provenanceType",
|
||||
"text": "author"
|
||||
}
|
||||
],
|
||||
"author": {
|
||||
"name": "Speckl Compiler v0.3"
|
||||
},
|
||||
"timestamp": "2026-05-10T05:28:26.209Z"
|
||||
},
|
||||
{
|
||||
"subject": "speck:KafkaKRaft",
|
||||
"annotations": [
|
||||
{
|
||||
"type": "tag",
|
||||
"value": "provenanceType",
|
||||
"text": "source"
|
||||
}
|
||||
],
|
||||
"author": {
|
||||
"name": "Speckl Compiler",
|
||||
"email": "compiler@speckl.scoble.me"
|
||||
},
|
||||
"timestamp": "2026-05-10T05:28:26.209Z"
|
||||
},
|
||||
{
|
||||
"subject": "speck:KafkaKRaft",
|
||||
"annotations": [
|
||||
{
|
||||
"type": "tag",
|
||||
"value": "reviewType",
|
||||
"text": "hybrid"
|
||||
}
|
||||
],
|
||||
"author": {
|
||||
"name": "Speckl Compiler",
|
||||
"email": "compiler@speckl.scoble.me"
|
||||
},
|
||||
"timestamp": "2026-05-10T05:28:26.209Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
69
out/KafkaKRaft.specbom.spdx.json
Normal file
69
out/KafkaKRaft.specbom.spdx.json
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://spdx.github.io/spdx-v3/spdx-context.jsonld",
|
||||
{
|
||||
"speckl": "https://speckl.scoble.me/ns/v0.2#"
|
||||
}
|
||||
],
|
||||
"@type": "spdx:Document",
|
||||
"spdx": {
|
||||
"@id": "spdx:KafkaKRaft",
|
||||
"@type": "spdx:Snippet",
|
||||
"name": "KafkaKRaft",
|
||||
"spdxVersion": "3.0.1",
|
||||
"dataLicense": "CC0-1.0",
|
||||
"creationInfo": {
|
||||
"created": "2026-05-10T05:28:26.210Z",
|
||||
"createdBy": [
|
||||
"Organization: Speckl",
|
||||
"tool: speckl-compile-0.2.0"
|
||||
]
|
||||
},
|
||||
"annotations": [
|
||||
{
|
||||
"@type": "spdx:Annotation",
|
||||
"annotationType": "spdx:REVIEW",
|
||||
"annotator": {
|
||||
"@type": "spdx:Tool",
|
||||
"name": "speckl-compile"
|
||||
},
|
||||
"timestamp": "2026-05-10T05:28:26.210Z",
|
||||
"comment": "Review type: hybrid"
|
||||
}
|
||||
]
|
||||
},
|
||||
"builtBy": [],
|
||||
"relationships": [
|
||||
{
|
||||
"from": {
|
||||
"@id": "spdx:KafkaKRaft"
|
||||
},
|
||||
"to": {
|
||||
"@id": "spdx:agent:Speckl Compiler v0.3"
|
||||
},
|
||||
"relationshipType": "spdx:wasAttributedTo"
|
||||
},
|
||||
{
|
||||
"from": {
|
||||
"@id": "spdx:KafkaKRaft"
|
||||
},
|
||||
"to": {
|
||||
"@id": "spdx:source:architecture_review:KRaft-Core-Consensus"
|
||||
},
|
||||
"relationshipType": "spdx:wasInformedBy"
|
||||
}
|
||||
],
|
||||
"elements": [
|
||||
{
|
||||
"@type": "spdx:Person",
|
||||
"@id": "spdx:agent:Speckl Compiler v0.3",
|
||||
"name": "Speckl Compiler v0.3"
|
||||
},
|
||||
{
|
||||
"@type": "spdx:Activity",
|
||||
"@id": "spdx:source:architecture_review:KRaft-Core-Consensus",
|
||||
"name": "architecture_review - KRaft-Core-Consensus",
|
||||
"type": "architecture_review"
|
||||
}
|
||||
]
|
||||
}
|
||||
54
out/KafkaKRaft.ts
Normal file
54
out/KafkaKRaft.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// Auto-generated by speckl-compile from KafkaKRaft
|
||||
// DO NOT EDIT MANUALLY — regenerate from .speckdl source
|
||||
|
||||
export const KafkaKRaftState = {
|
||||
Candidate: 'candidate',
|
||||
Leader: 'leader',
|
||||
} as const;
|
||||
export type KafkaKRaftState = typeof KafkaKRaftState[keyof typeof KafkaKRaftState];
|
||||
|
||||
|
||||
|
||||
|
||||
export class KafkaKRaftMachine {
|
||||
currentTerm: number;
|
||||
state: KafkaKRaftState;
|
||||
votedFor: Option(NodeId);
|
||||
log: LogEntry[];
|
||||
commitIndex: number;
|
||||
nextIndex: Map<NodeId, number>;
|
||||
matchIndex: Map<NodeId, number>;
|
||||
votesGranted: Set<NodeId>;
|
||||
decisionLog: any[] = [];
|
||||
|
||||
constructor() {
|
||||
// No init block defined
|
||||
}
|
||||
|
||||
RequestVote(candidate: NodeId, lastLogTerm: number, lastLogIndex: number) {
|
||||
if (!(this.state == KafkaKRaftState.Candidate)) throw new Error('Guard failed: this.state == KafkaKRaftState.Candidate');
|
||||
if (!(this.currentTerm >= lastLogTerm)) throw new Error('Guard failed: this.currentTerm >= lastLogTerm');
|
||||
this.votesGranted = new Set([...this.votesGranted, candidate]);
|
||||
}
|
||||
|
||||
HandleRequestVoteRequest(candidate: NodeId, candidateTerm: number, lastLogTerm: number, lastLogIndex: number) {
|
||||
if (!(this.currentTerm >= candidateTerm)) throw new Error('Guard failed: this.currentTerm >= candidateTerm');
|
||||
this.votedFor = Some(candidate);
|
||||
}
|
||||
|
||||
BecomeLeader() {
|
||||
if (!(this.state == KafkaKRaftState.Candidate)) throw new Error('Guard failed: this.state == KafkaKRaftState.Candidate');
|
||||
if (!(this.votesGranted.size >= 3)) throw new Error('Guard failed: this.votesGranted.size >= 3');
|
||||
this.state = KafkaKRaftState.Leader;
|
||||
this.commitIndex = length(this.log);
|
||||
}
|
||||
|
||||
AppendEntries(follower: NodeId) {
|
||||
if (!(this.state == KafkaKRaftState.Leader)) throw new Error('Guard failed: this.state == KafkaKRaftState.Leader');
|
||||
this.log = append(this.log, newEntry);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
124
out/KafkaKRaft.wat
Normal file
124
out/KafkaKRaft.wat
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
;; Auto-generated by speckl-compile from KafkaKRaft
|
||||
;; Compile: wat2wasm KafkaKRaft.wat -o KafkaKRaft.wasm
|
||||
|
||||
(module
|
||||
;; Host imports for Map/Set/alloc
|
||||
(import "speckl" "map_create" (func $map_create (result i32)))
|
||||
(import "speckl" "map_set" (func $map_set (param i32 i32 i32)))
|
||||
(import "speckl" "map_get" (func $map_get (param i32 i32) (result i32)))
|
||||
(import "speckl" "map_has" (func $map_has (param i32 i32) (result i32)))
|
||||
(import "speckl" "map_remove" (func $map_remove (param i32 i32)))
|
||||
(import "speckl" "set_create" (func $set_create (result i32)))
|
||||
(import "speckl" "set_add" (func $set_add (param i32 i32)))
|
||||
(import "speckl" "set_has" (func $set_has (param i32 i32) (result i32)))
|
||||
(import "speckl" "set_size" (func $set_size (param i32) (result i32)))
|
||||
(import "speckl" "alloc" (func $alloc (param i32) (result i32)))
|
||||
(import "speckl" "write_i32" (func $write_i32 (param i32 i32)))
|
||||
(import "speckl" "write_f64" (func $write_f64 (param i32 f64)))
|
||||
|
||||
;; Linear memory (exported so host can read state)
|
||||
(memory (export "memory") 1)
|
||||
|
||||
;; State globals
|
||||
(global $currentTerm (mut i32) (i32.const 0))
|
||||
(global $state (mut i32) (i32.const 0))
|
||||
(global $votedFor (mut i32) (i32.const 0))
|
||||
(global $log (mut i32) (i32.const 0))
|
||||
(global $commitIndex (mut i32) (i32.const 0))
|
||||
(global $nextIndex (mut i32) (i32.const 0))
|
||||
(global $matchIndex (mut i32) (i32.const 0))
|
||||
(global $votesGranted (mut i32) (i32.const 0))
|
||||
|
||||
;; ---- init ----
|
||||
(func $init (export "init")
|
||||
(i32.const 0)
|
||||
(global.set $currentTerm)
|
||||
(i32.const 0)
|
||||
(global.set $state)
|
||||
(i32.const 0)
|
||||
(global.set $votedFor)
|
||||
(i32.const 0)
|
||||
(global.set $commitIndex)
|
||||
(call $map_create)
|
||||
(global.set $nextIndex)
|
||||
(call $map_create)
|
||||
(global.set $matchIndex)
|
||||
(call $set_create)
|
||||
(global.set $votesGranted)
|
||||
)
|
||||
|
||||
;; ---- Actions ----
|
||||
;; action: RequestVote
|
||||
(func $RequestVote (export "RequestVote") (param $candidate i32) (param $lastLogTerm i32) (param $lastLogIndex i32) (result i32)
|
||||
|
||||
;; require: state == Candidate
|
||||
;; Complex right-hand side not compiled: state == Candidate
|
||||
;; require: currentTerm >= lastLogTerm
|
||||
;; Complex right-hand side not compiled: currentTerm >= lastLogTerm
|
||||
;; votesGranted := votesGranted union {candidate}
|
||||
;; Unrecognized assignment target: votesGranted
|
||||
(i32.const 1)
|
||||
)
|
||||
|
||||
;; action: HandleRequestVoteRequest
|
||||
(func $HandleRequestVoteRequest (export "HandleRequestVoteRequest") (param $candidate i32) (param $candidateTerm i32) (param $lastLogTerm i32) (param $lastLogIndex i32) (result i32)
|
||||
|
||||
;; require: currentTerm >= candidateTerm
|
||||
;; Complex right-hand side not compiled: currentTerm >= candidateTerm
|
||||
;; votedFor := Some(candidate)
|
||||
(i32.const 0) ;; uncompiled expr: Some(candidate)
|
||||
(global.set $votedFor)
|
||||
(i32.const 1)
|
||||
)
|
||||
|
||||
;; action: BecomeLeader
|
||||
(func $BecomeLeader (export "BecomeLeader") (result i32)
|
||||
|
||||
;; require: state == Candidate
|
||||
;; Complex right-hand side not compiled: state == Candidate
|
||||
;; require: votesGranted.size() >= 3
|
||||
;; Complex guard not compiled: votesGranted.size() >= 3
|
||||
;; state := Leader
|
||||
(i32.const 0) ;; uncompiled expr: Leader
|
||||
(global.set $state)
|
||||
;; commitIndex := length(log)
|
||||
(i32.const 0) ;; uncompiled expr: length(log)
|
||||
(global.set $commitIndex)
|
||||
(i32.const 1)
|
||||
)
|
||||
|
||||
;; action: AppendEntries
|
||||
(func $AppendEntries (export "AppendEntries") (param $follower i32) (result i32)
|
||||
|
||||
;; require: state == Leader
|
||||
;; Complex right-hand side not compiled: state == Leader
|
||||
;; log := append(log, newEntry)
|
||||
;; Unrecognized assignment target: log
|
||||
(i32.const 1)
|
||||
)
|
||||
|
||||
;; ---- getState ----
|
||||
(func $getState (export "getState") (result i32 i32)
|
||||
(i32.const 0)
|
||||
(global.get $currentTerm)
|
||||
(i32.store)
|
||||
(i32.const 4)
|
||||
(global.get $state)
|
||||
(i32.store)
|
||||
(i32.const 8)
|
||||
(global.get $votedFor)
|
||||
(i32.store)
|
||||
(i32.const 12)
|
||||
(global.get $commitIndex)
|
||||
(i32.store)
|
||||
(i32.const 0)
|
||||
(i32.const 16)
|
||||
)
|
||||
|
||||
|
||||
;; checkConstraints — runtime constraint evaluation stub
|
||||
(func $checkConstraints (export "checkConstraints") (result i32)
|
||||
;; TODO: evaluate constraint expressions at runtime
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
3372
out/TigerBeetleLedger.smt2
Normal file
3372
out/TigerBeetleLedger.smt2
Normal file
File diff suppressed because it is too large
Load diff
19
out/ToggleSwitch.smt2
Normal file
19
out/ToggleSwitch.smt2
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
; SpeckDL: ToggleSwitch — Z3 Verification
|
||||
; Generated by speckl-compile Z3 backend
|
||||
; Bounded model checking depth: 10
|
||||
|
||||
(set-logic ALL)
|
||||
(set-option :produce-models true)
|
||||
|
||||
; --- State Variables ---
|
||||
(declare-const isOn Bool)
|
||||
|
||||
; --- Invariants ---
|
||||
(define-fun SwitchConsistent () Bool
|
||||
(and
|
||||
(or (= isOn true) (= isOn false))
|
||||
)
|
||||
)
|
||||
|
||||
(check-sat)
|
||||
(get-model)
|
||||
|
|
@ -30,7 +30,7 @@ export class VerificationSME extends SME {
|
|||
this.compileConstraint(c.expression, speck)
|
||||
);
|
||||
|
||||
// In production, this would invoke Z3 via subprocess or WASM.
|
||||
// In production, this would invoke Z3 via subprocess.
|
||||
// For the reference implementation, we simulate the result.
|
||||
const z3Result = this.simulateZ3(speck, z3Assertions);
|
||||
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ export interface Task {
|
|||
export type TaskType =
|
||||
| "verify_speck" // Run Z3 verification on a Speck
|
||||
| "compile_to_dst" // Compile verified Speck to DST
|
||||
| "compile_to_wasm // Removed: WASM superseded by IR→Rust→wasm-pack path
|
||||
| "compile_to_typescript" // Compile DST to TypeScript
|
||||
| "generate_code" // Generate code from verified Speck
|
||||
| "generate_docs" // Generate documentation from provenance graph
|
||||
| "integrate" // Integration/glue code
|
||||
|
|
@ -142,7 +142,7 @@ export interface TaskResult {
|
|||
|
||||
export interface ProvenanceNode {
|
||||
id: string;
|
||||
type: "speck" | "z3_assertion" | "dst" | "wasm" | "code" | "docs" | "event";
|
||||
type: "speck" | "z3_assertion" | "dst" | "code" | "docs" | "event";
|
||||
label: string;
|
||||
metadata: Record<string, unknown>;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue