@nforma.ai/nforma 0.2.1 → 0.29.0
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 +2 -2
- package/agents/{qgsd-codebase-mapper.md → nf-codebase-mapper.md} +1 -1
- package/agents/{qgsd-debugger.md → nf-debugger.md} +3 -3
- package/agents/{qgsd-executor.md → nf-executor.md} +14 -14
- package/agents/{qgsd-integration-checker.md → nf-integration-checker.md} +1 -1
- package/agents/{qgsd-phase-researcher.md → nf-phase-researcher.md} +6 -6
- package/agents/{qgsd-plan-checker.md → nf-plan-checker.md} +9 -9
- package/agents/{qgsd-planner.md → nf-planner.md} +9 -9
- package/agents/{qgsd-project-researcher.md → nf-project-researcher.md} +2 -2
- package/agents/{qgsd-quorum-orchestrator.md → nf-quorum-orchestrator.md} +33 -33
- package/agents/{qgsd-quorum-slot-worker.md → nf-quorum-slot-worker.md} +3 -3
- package/agents/{qgsd-quorum-synthesizer.md → nf-quorum-synthesizer.md} +3 -3
- package/agents/{qgsd-quorum-test-worker.md → nf-quorum-test-worker.md} +1 -1
- package/agents/{qgsd-quorum-worker.md → nf-quorum-worker.md} +6 -6
- package/agents/{qgsd-research-synthesizer.md → nf-research-synthesizer.md} +5 -5
- package/agents/{qgsd-roadmapper.md → nf-roadmapper.md} +3 -3
- package/agents/{qgsd-verifier.md → nf-verifier.md} +8 -8
- package/bin/accept-debug-invariant.cjs +2 -2
- package/bin/account-manager.cjs +10 -10
- package/bin/aggregate-requirements.cjs +1 -1
- package/bin/analyze-assumptions.cjs +3 -3
- package/bin/analyze-state-space.cjs +14 -14
- package/bin/assumption-register.cjs +146 -0
- package/bin/attribute-trace-divergence.cjs +1 -1
- package/bin/auth-drivers/gh-cli.cjs +1 -1
- package/bin/auth-drivers/pool.cjs +1 -1
- package/bin/autoClosePtoF.cjs +3 -3
- package/bin/budget-tracker.cjs +77 -0
- package/bin/build-layer-manifest.cjs +153 -0
- package/bin/call-quorum-slot.cjs +3 -3
- package/bin/ccr-secure-config.cjs +5 -5
- package/bin/check-bundled-sdks.cjs +1 -1
- package/bin/check-mcp-health.cjs +1 -1
- package/bin/check-provider-health.cjs +6 -6
- package/bin/check-spec-sync.cjs +26 -26
- package/bin/check-trace-schema-drift.cjs +5 -5
- package/bin/conformance-schema.cjs +2 -2
- package/bin/cross-layer-dashboard.cjs +297 -0
- package/bin/design-impact.cjs +377 -0
- package/bin/detect-coverage-gaps.cjs +7 -7
- package/bin/failure-mode-catalog.cjs +227 -0
- package/bin/failure-taxonomy.cjs +177 -0
- package/bin/formal-scope-scan.cjs +179 -0
- package/bin/gate-a-grounding.cjs +334 -0
- package/bin/gate-b-abstraction.cjs +243 -0
- package/bin/gate-c-validation.cjs +166 -0
- package/bin/generate-formal-specs.cjs +17 -17
- package/bin/generate-petri-net.cjs +3 -3
- package/bin/generate-tla-cfg.cjs +5 -5
- package/bin/git-heatmap.cjs +571 -0
- package/bin/harness-diagnostic.cjs +326 -0
- package/bin/hazard-model.cjs +261 -0
- package/bin/install-formal-tools.cjs +1 -1
- package/bin/install.js +184 -139
- package/bin/instrumentation-map.cjs +178 -0
- package/bin/invariant-catalog.cjs +437 -0
- package/bin/issue-classifier.cjs +2 -2
- package/bin/load-baseline-requirements.cjs +4 -4
- package/bin/manage-agents-core.cjs +32 -32
- package/bin/migrate-to-slots.cjs +39 -39
- package/bin/mismatch-register.cjs +217 -0
- package/bin/nForma.cjs +176 -81
- package/bin/{qgsd-solve.cjs → nf-solve.cjs} +327 -14
- package/bin/observe-config.cjs +8 -0
- package/bin/observe-debt-writer.cjs +1 -1
- package/bin/observe-handler-deps.cjs +356 -0
- package/bin/observe-handler-grafana.cjs +2 -17
- package/bin/observe-handler-internal.cjs +5 -5
- package/bin/observe-handler-logstash.cjs +2 -17
- package/bin/observe-handler-prometheus.cjs +2 -17
- package/bin/observe-handler-upstream.cjs +251 -0
- package/bin/observe-handlers.cjs +12 -33
- package/bin/observe-render.cjs +68 -22
- package/bin/observe-utils.cjs +37 -0
- package/bin/observed-fsm.cjs +324 -0
- package/bin/planning-paths.cjs +6 -0
- package/bin/polyrepo.cjs +1 -1
- package/bin/probe-quorum-slots.cjs +1 -1
- package/bin/promote-gate-maturity.cjs +274 -0
- package/bin/promote-model.cjs +1 -1
- package/bin/propose-debug-invariants.cjs +1 -1
- package/bin/quorum-cache.cjs +144 -0
- package/bin/quorum-consensus-gate.cjs +1 -1
- package/bin/quorum-preflight.cjs +89 -0
- package/bin/quorum-slot-dispatch.cjs +6 -6
- package/bin/requirements-core.cjs +1 -1
- package/bin/review-mcp-logs.cjs +1 -1
- package/bin/risk-heatmap.cjs +151 -0
- package/bin/run-account-manager-tlc.cjs +4 -4
- package/bin/run-account-pool-alloy.cjs +2 -2
- package/bin/run-alloy.cjs +2 -2
- package/bin/run-audit-alloy.cjs +2 -2
- package/bin/run-breaker-tlc.cjs +3 -3
- package/bin/run-formal-check.cjs +9 -9
- package/bin/run-formal-verify.cjs +30 -9
- package/bin/run-installer-alloy.cjs +2 -2
- package/bin/run-oscillation-tlc.cjs +4 -4
- package/bin/run-phase-tlc.cjs +1 -1
- package/bin/run-protocol-tlc.cjs +4 -4
- package/bin/run-quorum-composition-alloy.cjs +2 -2
- package/bin/run-sensitivity-sweep.cjs +2 -2
- package/bin/run-stop-hook-tlc.cjs +3 -3
- package/bin/run-tlc.cjs +21 -21
- package/bin/run-transcript-alloy.cjs +2 -2
- package/bin/secrets.cjs +5 -5
- package/bin/security-sweep.cjs +238 -0
- package/bin/sensitivity-report.cjs +3 -3
- package/bin/set-secret.cjs +5 -5
- package/bin/setup-telemetry-cron.sh +3 -3
- package/bin/stall-detector.cjs +126 -0
- package/bin/state-candidates.cjs +206 -0
- package/bin/sync-baseline-requirements.cjs +1 -1
- package/bin/telemetry-collector.cjs +1 -1
- package/bin/test-changed.cjs +111 -0
- package/bin/test-recipe-gen.cjs +250 -0
- package/bin/trace-corpus-stats.cjs +211 -0
- package/bin/unified-mcp-server.mjs +3 -3
- package/bin/update-scoreboard.cjs +1 -1
- package/bin/validate-memory.cjs +2 -2
- package/bin/validate-traces.cjs +10 -10
- package/bin/verify-quorum-health.cjs +66 -5
- package/bin/xstate-to-tla.cjs +4 -4
- package/bin/xstate-trace-walker.cjs +3 -3
- package/commands/{qgsd → nf}/add-phase.md +3 -3
- package/commands/{qgsd → nf}/add-requirement.md +3 -3
- package/commands/{qgsd → nf}/add-todo.md +3 -3
- package/commands/{qgsd → nf}/audit-milestone.md +4 -4
- package/commands/{qgsd → nf}/check-todos.md +3 -3
- package/commands/{qgsd → nf}/cleanup.md +3 -3
- package/commands/{qgsd → nf}/close-formal-gaps.md +2 -2
- package/commands/{qgsd → nf}/complete-milestone.md +9 -9
- package/commands/{qgsd → nf}/debug.md +9 -9
- package/commands/{qgsd → nf}/discuss-phase.md +3 -3
- package/commands/{qgsd → nf}/execute-phase.md +15 -15
- package/commands/{qgsd → nf}/fix-tests.md +3 -3
- package/commands/{qgsd → nf}/formal-test-sync.md +1 -1
- package/commands/{qgsd → nf}/health.md +3 -3
- package/commands/{qgsd → nf}/help.md +3 -3
- package/commands/{qgsd → nf}/insert-phase.md +3 -3
- package/commands/nf/join-discord.md +18 -0
- package/commands/{qgsd → nf}/list-phase-assumptions.md +2 -2
- package/commands/{qgsd → nf}/map-codebase.md +7 -7
- package/commands/{qgsd → nf}/map-requirements.md +3 -3
- package/commands/{qgsd → nf}/mcp-restart.md +3 -3
- package/commands/{qgsd → nf}/mcp-set-model.md +8 -8
- package/commands/{qgsd → nf}/mcp-setup.md +63 -63
- package/commands/{qgsd → nf}/mcp-status.md +3 -3
- package/commands/{qgsd → nf}/mcp-update.md +7 -7
- package/commands/{qgsd → nf}/new-milestone.md +8 -8
- package/commands/{qgsd → nf}/new-project.md +8 -8
- package/commands/{qgsd → nf}/observe.md +49 -16
- package/commands/{qgsd → nf}/pause-work.md +3 -3
- package/commands/{qgsd → nf}/plan-milestone-gaps.md +5 -5
- package/commands/{qgsd → nf}/plan-phase.md +6 -6
- package/commands/{qgsd → nf}/polyrepo.md +2 -2
- package/commands/{qgsd → nf}/progress.md +3 -3
- package/commands/{qgsd → nf}/queue.md +2 -2
- package/commands/{qgsd → nf}/quick.md +8 -8
- package/commands/{qgsd → nf}/quorum-test.md +10 -10
- package/commands/{qgsd → nf}/quorum.md +36 -86
- package/commands/{qgsd → nf}/reapply-patches.md +2 -2
- package/commands/{qgsd → nf}/remove-phase.md +3 -3
- package/commands/{qgsd → nf}/research-phase.md +12 -12
- package/commands/{qgsd → nf}/resume-work.md +3 -3
- package/commands/nf/review-requirements.md +31 -0
- package/commands/{qgsd → nf}/set-profile.md +3 -3
- package/commands/{qgsd → nf}/settings.md +6 -6
- package/commands/{qgsd → nf}/solve.md +35 -35
- package/commands/{qgsd → nf}/sync-baselines.md +4 -4
- package/commands/{qgsd → nf}/triage.md +10 -10
- package/commands/{qgsd → nf}/update.md +3 -3
- package/commands/{qgsd → nf}/verify-work.md +5 -5
- package/hooks/dist/config-loader.js +188 -32
- package/hooks/dist/conformance-schema.cjs +2 -2
- package/hooks/dist/gsd-context-monitor.js +118 -13
- package/hooks/dist/{qgsd-check-update.js → nf-check-update.js} +5 -5
- package/hooks/dist/{qgsd-circuit-breaker.js → nf-circuit-breaker.js} +35 -24
- package/hooks/dist/{qgsd-precompact.js → nf-precompact.js} +13 -13
- package/hooks/dist/{qgsd-prompt.js → nf-prompt.js} +110 -33
- package/hooks/dist/nf-session-start.js +185 -0
- package/hooks/dist/{qgsd-slot-correlator.js → nf-slot-correlator.js} +13 -5
- package/hooks/dist/{qgsd-spec-regen.js → nf-spec-regen.js} +17 -8
- package/hooks/dist/{qgsd-statusline.js → nf-statusline.js} +12 -3
- package/hooks/dist/{qgsd-stop.js → nf-stop.js} +152 -18
- package/hooks/dist/{qgsd-token-collector.js → nf-token-collector.js} +12 -4
- package/hooks/dist/unified-mcp-server.mjs +2 -2
- package/package.json +6 -4
- package/scripts/build-hooks.js +13 -6
- package/scripts/secret-audit.sh +1 -1
- package/scripts/verify-hooks-sync.cjs +90 -0
- package/templates/{qgsd.json → nf.json} +4 -4
- package/commands/qgsd/join-discord.md +0 -18
- package/hooks/dist/qgsd-session-start.js +0 -122
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* gate-c-validation.cjs — Gate C L3-to-test-recipe coverage verification.
|
|
6
|
+
*
|
|
7
|
+
* Verifies every L3 failure mode maps to at least one test recipe.
|
|
8
|
+
* Reports gate_c_score and gap list for uncovered failure modes.
|
|
9
|
+
*
|
|
10
|
+
* Requirements: GATE-03
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node bin/gate-c-validation.cjs # print summary to stdout
|
|
14
|
+
* node bin/gate-c-validation.cjs --json # print full results JSON to stdout
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const ROOT = process.env.PROJECT_ROOT
|
|
21
|
+
|| (process.argv.find(a => a.startsWith('--project-root=')) || '').replace('--project-root=', '')
|
|
22
|
+
|| path.join(__dirname, '..');
|
|
23
|
+
const FORMAL = path.join(ROOT, '.planning', 'formal');
|
|
24
|
+
const REASONING_DIR = path.join(FORMAL, 'reasoning');
|
|
25
|
+
const RECIPES_DIR = path.join(FORMAL, 'test-recipes');
|
|
26
|
+
const GATES_DIR = path.join(FORMAL, 'gates');
|
|
27
|
+
const OUT_FILE = path.join(GATES_DIR, 'gate-c-validation.json');
|
|
28
|
+
|
|
29
|
+
const JSON_FLAG = process.argv.includes('--json');
|
|
30
|
+
|
|
31
|
+
// ── Gate C computation ───────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
function computeGateC(failureModes, recipes) {
|
|
34
|
+
if (failureModes.length === 0) {
|
|
35
|
+
return {
|
|
36
|
+
schema_version: '1',
|
|
37
|
+
generated: new Date().toISOString(),
|
|
38
|
+
gate_c_score: 0,
|
|
39
|
+
total_entries: 0,
|
|
40
|
+
validated_entries: 0,
|
|
41
|
+
unvalidated_entries: 0,
|
|
42
|
+
gaps: [],
|
|
43
|
+
target: 0.8,
|
|
44
|
+
target_met: false,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Build lookup: failure_mode_id -> recipe[]
|
|
49
|
+
const recipeMap = new Map();
|
|
50
|
+
for (const r of recipes) {
|
|
51
|
+
const fmId = r.failure_mode_id;
|
|
52
|
+
if (!recipeMap.has(fmId)) recipeMap.set(fmId, []);
|
|
53
|
+
recipeMap.get(fmId).push(r);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let validated = 0;
|
|
57
|
+
const gaps = [];
|
|
58
|
+
|
|
59
|
+
for (const fm of failureModes) {
|
|
60
|
+
if (recipeMap.has(fm.id) && recipeMap.get(fm.id).length > 0) {
|
|
61
|
+
validated++;
|
|
62
|
+
} else {
|
|
63
|
+
gaps.push({
|
|
64
|
+
failure_mode_id: fm.id,
|
|
65
|
+
state: fm.state,
|
|
66
|
+
event: fm.event,
|
|
67
|
+
failure_mode: fm.failure_mode,
|
|
68
|
+
severity_class: fm.severity_class,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const score = validated / failureModes.length;
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
schema_version: '1',
|
|
77
|
+
generated: new Date().toISOString(),
|
|
78
|
+
gate_c_score: Math.round(score * 10000) / 10000,
|
|
79
|
+
total_entries: failureModes.length,
|
|
80
|
+
validated_entries: validated,
|
|
81
|
+
unvalidated_entries: gaps.length,
|
|
82
|
+
gaps,
|
|
83
|
+
target: 0.8,
|
|
84
|
+
target_met: score >= 0.8,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Entry point ──────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
function main() {
|
|
91
|
+
const startMs = Date.now();
|
|
92
|
+
|
|
93
|
+
// Load failure-mode-catalog
|
|
94
|
+
const fmPath = path.join(REASONING_DIR, 'failure-mode-catalog.json');
|
|
95
|
+
if (!fs.existsSync(fmPath)) {
|
|
96
|
+
console.error('ERROR: failure-mode-catalog.json not found at', fmPath);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
const fmData = JSON.parse(fs.readFileSync(fmPath, 'utf8'));
|
|
100
|
+
const failureModes = fmData.failure_modes || [];
|
|
101
|
+
|
|
102
|
+
// Load test-recipes
|
|
103
|
+
const trPath = path.join(RECIPES_DIR, 'test-recipes.json');
|
|
104
|
+
if (!fs.existsSync(trPath)) {
|
|
105
|
+
console.error('ERROR: test-recipes.json not found at', trPath);
|
|
106
|
+
console.error('Run bin/test-recipe-gen.cjs first to generate recipes.');
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
const trData = JSON.parse(fs.readFileSync(trPath, 'utf8'));
|
|
110
|
+
const recipes = trData.recipes || [];
|
|
111
|
+
|
|
112
|
+
const result = computeGateC(failureModes, recipes);
|
|
113
|
+
const runtimeMs = Date.now() - startMs;
|
|
114
|
+
|
|
115
|
+
// Write output
|
|
116
|
+
fs.mkdirSync(GATES_DIR, { recursive: true });
|
|
117
|
+
fs.writeFileSync(OUT_FILE, JSON.stringify(result, null, 2) + '\n');
|
|
118
|
+
|
|
119
|
+
// Emit check result
|
|
120
|
+
try {
|
|
121
|
+
const { writeCheckResult } = require(path.join(__dirname, 'write-check-result.cjs'));
|
|
122
|
+
writeCheckResult({
|
|
123
|
+
tool: 'gate-c-validation',
|
|
124
|
+
formalism: 'trace',
|
|
125
|
+
result: result.target_met ? 'pass' : 'fail',
|
|
126
|
+
check_id: 'gate-c:recipe-coverage',
|
|
127
|
+
surface: 'trace',
|
|
128
|
+
property: `Gate C test recipe coverage: ${(result.gate_c_score * 100).toFixed(1)}% (target: 80%)`,
|
|
129
|
+
runtime_ms: runtimeMs,
|
|
130
|
+
summary: `${result.target_met ? 'pass' : 'fail'}: coverage ${(result.gate_c_score * 100).toFixed(1)}%, ${result.validated_entries}/${result.total_entries} validated`,
|
|
131
|
+
requirement_ids: ['GATE-03'],
|
|
132
|
+
metadata: {
|
|
133
|
+
gate_c_score: result.gate_c_score,
|
|
134
|
+
target: result.target,
|
|
135
|
+
target_met: result.target_met,
|
|
136
|
+
gaps_count: result.unvalidated_entries,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
} catch (e) {
|
|
140
|
+
// write-check-result.cjs not available; skip
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (JSON_FLAG) {
|
|
144
|
+
process.stdout.write(JSON.stringify(result));
|
|
145
|
+
} else {
|
|
146
|
+
console.log('Gate C: L3-to-Test-Recipe Coverage');
|
|
147
|
+
console.log(` Score: ${(result.gate_c_score * 100).toFixed(1)}%`);
|
|
148
|
+
console.log(` Total entries: ${result.total_entries}`);
|
|
149
|
+
console.log(` Validated: ${result.validated_entries}`);
|
|
150
|
+
console.log(` Unvalidated: ${result.unvalidated_entries}`);
|
|
151
|
+
console.log(` Target met: ${result.target_met}`);
|
|
152
|
+
if (result.gaps.length > 0) {
|
|
153
|
+
console.log(' Gaps:');
|
|
154
|
+
for (const g of result.gaps) {
|
|
155
|
+
console.log(` - ${g.failure_mode_id} (${g.failure_mode}/${g.severity_class})`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
console.log(` Output: ${OUT_FILE}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
process.exit(result.target_met ? 0 : 1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (require.main === module) main();
|
|
165
|
+
|
|
166
|
+
module.exports = { computeGateC, main };
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
// bin/generate-formal-specs.cjs
|
|
4
4
|
// Generates ALL formal verification artifacts from the XState machine.
|
|
5
5
|
//
|
|
6
|
-
// The XState machine (src/machines/
|
|
6
|
+
// The XState machine (src/machines/nf-workflow.machine.ts) is the SINGLE SOURCE OF TRUTH.
|
|
7
7
|
// All formal specs are generated artifacts — do not edit them by hand.
|
|
8
8
|
//
|
|
9
9
|
// Generates:
|
|
10
|
-
// .planning/formal/tla/
|
|
10
|
+
// .planning/formal/tla/NFQuorum.tla — TLA+ spec (states, transitions, invariants)
|
|
11
11
|
// .planning/formal/tla/MCsafety.cfg — TLC safety model config (N=5, symmetry)
|
|
12
12
|
// .planning/formal/tla/MCliveness.cfg — TLC liveness model config (N=3)
|
|
13
13
|
// .planning/formal/alloy/quorum-votes.als — Alloy vote-counting model
|
|
@@ -68,7 +68,7 @@ function updateModelRegistry(absPath) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// ── Parse XState machine ──────────────────────────────────────────────────────
|
|
71
|
-
const machineFile = path.join(ROOT, 'src', 'machines', '
|
|
71
|
+
const machineFile = path.join(ROOT, 'src', 'machines', 'nf-workflow.machine.ts');
|
|
72
72
|
if (!fs.existsSync(machineFile)) {
|
|
73
73
|
process.stderr.write('[generate-formal-specs] XState machine not found at ' + machineFile + ' — skipping (not required for external projects)\n');
|
|
74
74
|
process.exit(0);
|
|
@@ -114,7 +114,7 @@ const ts = new Date().toISOString().split('T')[0];
|
|
|
114
114
|
const GENERATED_HEADER = (comment, file) =>
|
|
115
115
|
comment + ' ' + file + '\n' +
|
|
116
116
|
comment + ' GENERATED — do not edit by hand.\n' +
|
|
117
|
-
comment + ' Source of truth: src/machines/
|
|
117
|
+
comment + ' Source of truth: src/machines/nf-workflow.machine.ts\n' +
|
|
118
118
|
comment + ' Regenerate: node bin/generate-formal-specs.cjs\n' +
|
|
119
119
|
comment + ' Generated: ' + ts + '\n';
|
|
120
120
|
|
|
@@ -148,17 +148,17 @@ const GUARD_REGISTRY = {
|
|
|
148
148
|
},
|
|
149
149
|
};
|
|
150
150
|
|
|
151
|
-
// ── 1.
|
|
151
|
+
// ── 1. NFQuorum.tla ─────────────────────────────────────────────────────────
|
|
152
152
|
// Intermediate states = all states except initial and final
|
|
153
153
|
const collectingState = 'COLLECTING_VOTES';
|
|
154
154
|
const deliberatingState = 'DELIBERATING';
|
|
155
155
|
const phaseSet = stateNames.map(s => '"' + s + '"').join(', ');
|
|
156
156
|
|
|
157
157
|
const tlaSpec = [
|
|
158
|
-
'---- MODULE
|
|
158
|
+
'---- MODULE NFQuorum ----',
|
|
159
159
|
'(*',
|
|
160
|
-
GENERATED_HEADER(' *', '.planning/formal/tla/
|
|
161
|
-
' * Models the quorum workflow defined in src/machines/
|
|
160
|
+
GENERATED_HEADER(' *', '.planning/formal/tla/NFQuorum.tla'),
|
|
161
|
+
' * Models the quorum workflow defined in src/machines/nf-workflow.machine.ts.',
|
|
162
162
|
' * Guard translations (from GUARD_REGISTRY in bin/generate-formal-specs.cjs):',
|
|
163
163
|
' * unanimityMet (' + GUARD_REGISTRY.unanimityMet.ts + '): ' + GUARD_REGISTRY.unanimityMet.tla,
|
|
164
164
|
' * noInfiniteDeliberation (' + GUARD_REGISTRY.noInfiniteDeliberation.ts + '): ' + GUARD_REGISTRY.noInfiniteDeliberation.tla,
|
|
@@ -335,7 +335,7 @@ function agentsSet(n) {
|
|
|
335
335
|
const tlaCfgHeader = (file, desc) => [
|
|
336
336
|
'\\* ' + file,
|
|
337
337
|
'\\* GENERATED — do not edit by hand.',
|
|
338
|
-
'\\* Source of truth: src/machines/
|
|
338
|
+
'\\* Source of truth: src/machines/nf-workflow.machine.ts',
|
|
339
339
|
'\\* Regenerate: node bin/generate-formal-specs.cjs',
|
|
340
340
|
'\\* Generated: ' + ts,
|
|
341
341
|
'\\*',
|
|
@@ -379,10 +379,10 @@ const livenessCfg = tlaCfgHeader('.planning/formal/tla/MCliveness.cfg',
|
|
|
379
379
|
// Alloy vote-counting model — derived from unanimityMet guard in XState machine
|
|
380
380
|
const alloySpec = [
|
|
381
381
|
GENERATED_HEADER('--', '.planning/formal/alloy/quorum-votes.als'),
|
|
382
|
-
'--
|
|
382
|
+
'-- nForma Quorum Vote-Counting Model (Alloy 6)',
|
|
383
383
|
'-- Requirements: ALY-01',
|
|
384
384
|
'--',
|
|
385
|
-
'-- Models the unanimityMet guard from src/machines/
|
|
385
|
+
'-- Models the unanimityMet guard from src/machines/nf-workflow.machine.ts:',
|
|
386
386
|
'-- ' + GUARD_REGISTRY.unanimityMet.ts,
|
|
387
387
|
'-- ≡ ' + GUARD_REGISTRY.unanimityMet.alloy + ' (all polled agents approved)',
|
|
388
388
|
'--',
|
|
@@ -390,11 +390,11 @@ const alloySpec = [
|
|
|
390
390
|
'-- ' + GUARD_REGISTRY.unanimityMet.desc,
|
|
391
391
|
'--',
|
|
392
392
|
'-- Checks that no round reaches ' + finalState + ' without satisfying the unanimity predicate.',
|
|
393
|
-
'-- Scope: ' + SAFETY_AGENTS + ' agents (
|
|
393
|
+
'-- Scope: ' + SAFETY_AGENTS + ' agents (nForma quorum slot count), 5 vote rounds.',
|
|
394
394
|
'',
|
|
395
395
|
'module quorum_votes',
|
|
396
396
|
'',
|
|
397
|
-
'-- Fix agent count to ' + SAFETY_AGENTS + ' (
|
|
397
|
+
'-- Fix agent count to ' + SAFETY_AGENTS + ' (nForma quorum slot count).',
|
|
398
398
|
'-- This makes the numeric threshold assertions below concrete and verifiable.',
|
|
399
399
|
'fact AgentCount { #Agent = ' + SAFETY_AGENTS + ' }',
|
|
400
400
|
'',
|
|
@@ -458,7 +458,7 @@ const alloySpec = [
|
|
|
458
458
|
// State numbering: 0=collecting, 1=decided, 2=deliberating (absorbing at 1)
|
|
459
459
|
const prismSpec = [
|
|
460
460
|
GENERATED_HEADER('//', '.planning/formal/prism/quorum.pm'),
|
|
461
|
-
'//
|
|
461
|
+
'// nForma Quorum Convergence — DTMC Model',
|
|
462
462
|
'// Requirements: PRM-01',
|
|
463
463
|
'//',
|
|
464
464
|
'// Discrete-Time Markov Chain modeling quorum state transitions.',
|
|
@@ -467,7 +467,7 @@ const prismSpec = [
|
|
|
467
467
|
'// s=1 : ' + finalState + ' (absorbing)',
|
|
468
468
|
'// s=2 : ' + deliberatingState + ' (retry)',
|
|
469
469
|
'//',
|
|
470
|
-
'// Derived from src/machines/
|
|
470
|
+
'// Derived from src/machines/nf-workflow.machine.ts:',
|
|
471
471
|
'// ' + stateNames.join(', '),
|
|
472
472
|
'//',
|
|
473
473
|
'// Guard translations (from GUARD_REGISTRY):',
|
|
@@ -528,7 +528,7 @@ const prismSpec = [
|
|
|
528
528
|
const prismProps = [
|
|
529
529
|
'// .planning/formal/prism/quorum.props',
|
|
530
530
|
'// GENERATED — do not edit by hand.',
|
|
531
|
-
'// Source of truth: src/machines/
|
|
531
|
+
'// Source of truth: src/machines/nf-workflow.machine.ts',
|
|
532
532
|
'// Regenerate: node bin/generate-formal-specs.cjs',
|
|
533
533
|
'// Generated: ' + ts,
|
|
534
534
|
'//',
|
|
@@ -558,7 +558,7 @@ const prismProps = [
|
|
|
558
558
|
|
|
559
559
|
// ── Write or print ────────────────────────────────────────────────────────────
|
|
560
560
|
const outputs = [
|
|
561
|
-
{ rel: '.planning/formal/tla/
|
|
561
|
+
{ rel: '.planning/formal/tla/NFQuorum.tla', content: tlaSpec },
|
|
562
562
|
{ rel: '.planning/formal/tla/MCsafety.cfg', content: safetyCfg },
|
|
563
563
|
{ rel: '.planning/formal/tla/MCliveness.cfg', content: livenessCfg },
|
|
564
564
|
{ rel: '.planning/formal/alloy/quorum-votes.als', content: alloySpec },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
// bin/generate-petri-net.cjs
|
|
4
|
-
// Generates a Graphviz DOT + SVG Petri Net for the
|
|
4
|
+
// Generates a Graphviz DOT + SVG Petri Net for the nForma quorum token-passing model.
|
|
5
5
|
// Requirements: PET-01, PET-02, PET-03
|
|
6
6
|
//
|
|
7
7
|
// Usage:
|
|
@@ -44,7 +44,7 @@ function buildDot(slots, minQuorum) {
|
|
|
44
44
|
return [
|
|
45
45
|
'digraph quorum_petri_net {',
|
|
46
46
|
' rankdir=LR;',
|
|
47
|
-
' label="
|
|
47
|
+
' label="nForma Quorum Petri Net (min_quorum=' + minQuorum + '/' + slots.length + ')";',
|
|
48
48
|
' node [fontname="Helvetica"];',
|
|
49
49
|
'',
|
|
50
50
|
' // Places (circles)',
|
|
@@ -158,7 +158,7 @@ function buildRoadmapDot(phases) {
|
|
|
158
158
|
const lines = [
|
|
159
159
|
'digraph roadmap_petri_net {',
|
|
160
160
|
' rankdir=LR;',
|
|
161
|
-
' label="
|
|
161
|
+
' label="nForma Roadmap Petri Net (' + phases.length + ' phases)";',
|
|
162
162
|
' node [fontname="Helvetica"];',
|
|
163
163
|
'',
|
|
164
164
|
];
|
package/bin/generate-tla-cfg.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// bin/generate-tla-cfg.cjs
|
|
4
4
|
// Generates TLA+ model configuration files from the XState machine.
|
|
5
5
|
//
|
|
6
|
-
// The XState machine (src/machines/
|
|
6
|
+
// The XState machine (src/machines/nf-workflow.machine.ts) is the SOURCE OF TRUTH.
|
|
7
7
|
// MCsafety.cfg and MCliveness.cfg are generated artifacts — do not edit them by hand.
|
|
8
8
|
// To change MaxDeliberation, update the XState machine context default instead.
|
|
9
9
|
//
|
|
@@ -23,11 +23,11 @@ const ROOT = path.join(__dirname, '..');
|
|
|
23
23
|
const DRY = process.argv.includes('--dry');
|
|
24
24
|
|
|
25
25
|
// ── Model-checking parameters (stable, not derived from code) ─────────────────
|
|
26
|
-
const SAFETY_AGENTS = 5; // N for MCsafety — matches
|
|
26
|
+
const SAFETY_AGENTS = 5; // N for MCsafety — matches nForma quorum slot count
|
|
27
27
|
const LIVENESS_AGENTS = 3; // N for MCliveness — smaller for tractable liveness
|
|
28
28
|
|
|
29
29
|
// ── Extract MaxDeliberation from the XState machine ───────────────────────────
|
|
30
|
-
const machineFile = path.join(ROOT, 'src', 'machines', '
|
|
30
|
+
const machineFile = path.join(ROOT, 'src', 'machines', 'nf-workflow.machine.ts');
|
|
31
31
|
if (!fs.existsSync(machineFile)) {
|
|
32
32
|
process.stderr.write('[generate-tla-cfg] XState machine not found: ' + machineFile + '\n');
|
|
33
33
|
process.exit(1);
|
|
@@ -64,7 +64,7 @@ function agentsSet(n) {
|
|
|
64
64
|
const safetyCfg = [
|
|
65
65
|
'\\* .planning/formal/tla/MCsafety.cfg',
|
|
66
66
|
'\\* GENERATED — do not edit by hand.',
|
|
67
|
-
'\\* Source of truth: src/machines/
|
|
67
|
+
'\\* Source of truth: src/machines/nf-workflow.machine.ts',
|
|
68
68
|
'\\* Regenerate: node bin/generate-tla-cfg.cjs',
|
|
69
69
|
'\\*',
|
|
70
70
|
'\\* TLC safety model: N=' + SAFETY_AGENTS + ' agents, symmetry reduction, no liveness check.',
|
|
@@ -87,7 +87,7 @@ const safetyCfg = [
|
|
|
87
87
|
const livenessCfg = [
|
|
88
88
|
'\\* .planning/formal/tla/MCliveness.cfg',
|
|
89
89
|
'\\* GENERATED — do not edit by hand.',
|
|
90
|
-
'\\* Source of truth: src/machines/
|
|
90
|
+
'\\* Source of truth: src/machines/nf-workflow.machine.ts',
|
|
91
91
|
'\\* Regenerate: node bin/generate-tla-cfg.cjs',
|
|
92
92
|
'\\*',
|
|
93
93
|
'\\* TLC liveness model: N=' + LIVENESS_AGENTS + ' agents, NO symmetry (incompatible with liveness), PROPERTY only.',
|