@nforma.ai/nforma 0.2.1
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/LICENSE +22 -0
- package/README.md +1024 -0
- package/agents/qgsd-codebase-mapper.md +764 -0
- package/agents/qgsd-debugger.md +1201 -0
- package/agents/qgsd-executor.md +472 -0
- package/agents/qgsd-integration-checker.md +443 -0
- package/agents/qgsd-phase-researcher.md +502 -0
- package/agents/qgsd-plan-checker.md +643 -0
- package/agents/qgsd-planner.md +1182 -0
- package/agents/qgsd-project-researcher.md +621 -0
- package/agents/qgsd-quorum-orchestrator.md +628 -0
- package/agents/qgsd-quorum-slot-worker.md +41 -0
- package/agents/qgsd-quorum-synthesizer.md +133 -0
- package/agents/qgsd-quorum-test-worker.md +37 -0
- package/agents/qgsd-quorum-worker.md +161 -0
- package/agents/qgsd-research-synthesizer.md +239 -0
- package/agents/qgsd-roadmapper.md +660 -0
- package/agents/qgsd-verifier.md +628 -0
- package/bin/accept-debug-invariant.cjs +165 -0
- package/bin/account-manager.cjs +719 -0
- package/bin/aggregate-requirements.cjs +466 -0
- package/bin/analyze-assumptions.cjs +757 -0
- package/bin/analyze-state-space.cjs +921 -0
- package/bin/attribute-trace-divergence.cjs +150 -0
- package/bin/auth-drivers/gh-cli.cjs +93 -0
- package/bin/auth-drivers/index.cjs +46 -0
- package/bin/auth-drivers/pool.cjs +67 -0
- package/bin/auth-drivers/simple.cjs +95 -0
- package/bin/autoClosePtoF.cjs +110 -0
- package/bin/blessed-terminal.cjs +350 -0
- package/bin/build-phase-index.cjs +472 -0
- package/bin/call-quorum-slot.cjs +541 -0
- package/bin/ccr-secure-config.cjs +99 -0
- package/bin/ccr-secure-start.cjs +83 -0
- package/bin/check-bundled-sdks.cjs +177 -0
- package/bin/check-coverage-guard.cjs +112 -0
- package/bin/check-liveness-fairness.cjs +95 -0
- package/bin/check-mcp-health.cjs +123 -0
- package/bin/check-provider-health.cjs +395 -0
- package/bin/check-results-exit.cjs +24 -0
- package/bin/check-spec-sync.cjs +360 -0
- package/bin/check-trace-redaction.cjs +271 -0
- package/bin/check-trace-schema-drift.cjs +99 -0
- package/bin/compareDrift.cjs +21 -0
- package/bin/conformance-schema.cjs +12 -0
- package/bin/count-scenarios.cjs +420 -0
- package/bin/debt-dedup.cjs +144 -0
- package/bin/debt-ledger.cjs +61 -0
- package/bin/debt-retention.cjs +76 -0
- package/bin/debt-state-machine.cjs +80 -0
- package/bin/detect-coverage-gaps.cjs +204 -0
- package/bin/detect-project-intent.cjs +362 -0
- package/bin/export-prism-constants.cjs +164 -0
- package/bin/extract-annotations.cjs +633 -0
- package/bin/extractFormalExpected.cjs +104 -0
- package/bin/fingerprint-drift.cjs +24 -0
- package/bin/fingerprint-issue.cjs +46 -0
- package/bin/formal-core.cjs +519 -0
- package/bin/formal-ref-linker.cjs +141 -0
- package/bin/formal-test-sync.cjs +788 -0
- package/bin/generate-formal-specs.cjs +588 -0
- package/bin/generate-petri-net.cjs +397 -0
- package/bin/generate-phase-spec.cjs +249 -0
- package/bin/generate-proposed-changes.cjs +194 -0
- package/bin/generate-tla-cfg.cjs +122 -0
- package/bin/generate-traceability-matrix.cjs +701 -0
- package/bin/generate-triage-bundle.cjs +300 -0
- package/bin/gh-account-rotate.cjs +34 -0
- package/bin/initialize-model-registry.cjs +105 -0
- package/bin/install-formal-tools.cjs +382 -0
- package/bin/install.js +2424 -0
- package/bin/isNumericThreshold.cjs +34 -0
- package/bin/issue-classifier.cjs +151 -0
- package/bin/levenshtein.cjs +74 -0
- package/bin/lint-formal-models.cjs +580 -0
- package/bin/load-baseline-requirements.cjs +275 -0
- package/bin/manage-agents-core.cjs +815 -0
- package/bin/migrate-formal-dir.cjs +172 -0
- package/bin/migrate-planning.cjs +206 -0
- package/bin/migrate-to-slots.cjs +255 -0
- package/bin/nForma.cjs +2726 -0
- package/bin/observe-config.cjs +353 -0
- package/bin/observe-debt-writer.cjs +140 -0
- package/bin/observe-handler-grafana.cjs +128 -0
- package/bin/observe-handler-internal.cjs +301 -0
- package/bin/observe-handler-logstash.cjs +153 -0
- package/bin/observe-handler-prometheus.cjs +185 -0
- package/bin/observe-handlers.cjs +436 -0
- package/bin/observe-registry.cjs +131 -0
- package/bin/observe-render.cjs +168 -0
- package/bin/planning-paths.cjs +167 -0
- package/bin/polyrepo.cjs +560 -0
- package/bin/prism-priority.cjs +153 -0
- package/bin/probe-quorum-slots.cjs +167 -0
- package/bin/promote-model.cjs +225 -0
- package/bin/propose-debug-invariants.cjs +165 -0
- package/bin/providers.json +392 -0
- package/bin/pty-proxy.py +129 -0
- package/bin/qgsd-solve.cjs +2477 -0
- package/bin/quorum-consensus-gate.cjs +238 -0
- package/bin/quorum-formal-context.cjs +183 -0
- package/bin/quorum-slot-dispatch.cjs +934 -0
- package/bin/read-policy.cjs +60 -0
- package/bin/requirement-map.cjs +63 -0
- package/bin/requirements-core.cjs +247 -0
- package/bin/resolve-cli.cjs +101 -0
- package/bin/review-mcp-logs.cjs +294 -0
- package/bin/run-account-manager-tlc.cjs +188 -0
- package/bin/run-account-pool-alloy.cjs +158 -0
- package/bin/run-alloy.cjs +153 -0
- package/bin/run-audit-alloy.cjs +187 -0
- package/bin/run-breaker-tlc.cjs +181 -0
- package/bin/run-formal-check.cjs +395 -0
- package/bin/run-formal-verify.cjs +701 -0
- package/bin/run-installer-alloy.cjs +188 -0
- package/bin/run-oauth-rotation-prism.cjs +132 -0
- package/bin/run-oscillation-tlc.cjs +202 -0
- package/bin/run-phase-tlc.cjs +228 -0
- package/bin/run-prism.cjs +446 -0
- package/bin/run-protocol-tlc.cjs +201 -0
- package/bin/run-quorum-composition-alloy.cjs +155 -0
- package/bin/run-sensitivity-sweep.cjs +231 -0
- package/bin/run-stop-hook-tlc.cjs +188 -0
- package/bin/run-tlc.cjs +467 -0
- package/bin/run-transcript-alloy.cjs +173 -0
- package/bin/run-uppaal.cjs +264 -0
- package/bin/secrets.cjs +134 -0
- package/bin/sensitivity-report.cjs +219 -0
- package/bin/sensitivity-sweep-feedback.cjs +194 -0
- package/bin/set-secret.cjs +29 -0
- package/bin/setup-telemetry-cron.sh +36 -0
- package/bin/sweepPtoF.cjs +63 -0
- package/bin/sync-baseline-requirements.cjs +290 -0
- package/bin/task-envelope.cjs +360 -0
- package/bin/telemetry-collector.cjs +229 -0
- package/bin/unified-mcp-server.mjs +735 -0
- package/bin/update-agents.cjs +369 -0
- package/bin/update-scoreboard.cjs +1134 -0
- package/bin/validate-debt-entry.cjs +207 -0
- package/bin/validate-invariant.cjs +419 -0
- package/bin/validate-memory.cjs +389 -0
- package/bin/validate-requirements-haiku.cjs +435 -0
- package/bin/validate-traces.cjs +438 -0
- package/bin/verify-formal-results.cjs +124 -0
- package/bin/verify-quorum-health.cjs +273 -0
- package/bin/write-check-result.cjs +106 -0
- package/bin/xstate-to-tla.cjs +483 -0
- package/bin/xstate-trace-walker.cjs +205 -0
- package/commands/qgsd/add-phase.md +43 -0
- package/commands/qgsd/add-requirement.md +24 -0
- package/commands/qgsd/add-todo.md +47 -0
- package/commands/qgsd/audit-milestone.md +37 -0
- package/commands/qgsd/check-todos.md +45 -0
- package/commands/qgsd/cleanup.md +18 -0
- package/commands/qgsd/close-formal-gaps.md +33 -0
- package/commands/qgsd/complete-milestone.md +136 -0
- package/commands/qgsd/debug.md +166 -0
- package/commands/qgsd/discuss-phase.md +83 -0
- package/commands/qgsd/execute-phase.md +117 -0
- package/commands/qgsd/fix-tests.md +27 -0
- package/commands/qgsd/formal-test-sync.md +32 -0
- package/commands/qgsd/health.md +22 -0
- package/commands/qgsd/help.md +22 -0
- package/commands/qgsd/insert-phase.md +32 -0
- package/commands/qgsd/join-discord.md +18 -0
- package/commands/qgsd/list-phase-assumptions.md +46 -0
- package/commands/qgsd/map-codebase.md +71 -0
- package/commands/qgsd/map-requirements.md +20 -0
- package/commands/qgsd/mcp-restart.md +176 -0
- package/commands/qgsd/mcp-set-model.md +134 -0
- package/commands/qgsd/mcp-setup.md +1371 -0
- package/commands/qgsd/mcp-status.md +274 -0
- package/commands/qgsd/mcp-update.md +238 -0
- package/commands/qgsd/new-milestone.md +44 -0
- package/commands/qgsd/new-project.md +42 -0
- package/commands/qgsd/observe.md +260 -0
- package/commands/qgsd/pause-work.md +38 -0
- package/commands/qgsd/plan-milestone-gaps.md +34 -0
- package/commands/qgsd/plan-phase.md +44 -0
- package/commands/qgsd/polyrepo.md +50 -0
- package/commands/qgsd/progress.md +24 -0
- package/commands/qgsd/queue.md +54 -0
- package/commands/qgsd/quick.md +133 -0
- package/commands/qgsd/quorum-test.md +275 -0
- package/commands/qgsd/quorum.md +707 -0
- package/commands/qgsd/reapply-patches.md +110 -0
- package/commands/qgsd/remove-phase.md +31 -0
- package/commands/qgsd/research-phase.md +189 -0
- package/commands/qgsd/resume-work.md +40 -0
- package/commands/qgsd/set-profile.md +34 -0
- package/commands/qgsd/settings.md +39 -0
- package/commands/qgsd/solve.md +565 -0
- package/commands/qgsd/sync-baselines.md +119 -0
- package/commands/qgsd/triage.md +233 -0
- package/commands/qgsd/update.md +37 -0
- package/commands/qgsd/verify-work.md +38 -0
- package/hooks/dist/config-loader.js +297 -0
- package/hooks/dist/conformance-schema.cjs +12 -0
- package/hooks/dist/gsd-context-monitor.js +64 -0
- package/hooks/dist/qgsd-check-update.js +62 -0
- package/hooks/dist/qgsd-circuit-breaker.js +682 -0
- package/hooks/dist/qgsd-precompact.js +156 -0
- package/hooks/dist/qgsd-prompt.js +653 -0
- package/hooks/dist/qgsd-session-start.js +122 -0
- package/hooks/dist/qgsd-slot-correlator.js +58 -0
- package/hooks/dist/qgsd-spec-regen.js +86 -0
- package/hooks/dist/qgsd-statusline.js +91 -0
- package/hooks/dist/qgsd-stop.js +553 -0
- package/hooks/dist/qgsd-token-collector.js +133 -0
- package/hooks/dist/unified-mcp-server.mjs +669 -0
- package/package.json +95 -0
- package/scripts/build-hooks.js +46 -0
- package/scripts/postinstall.js +48 -0
- package/scripts/secret-audit.sh +45 -0
- package/templates/qgsd.json +49 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// bin/generate-proposed-changes.cjs
|
|
4
|
+
// PLAN-01: Reads must_haves: truths: from a single PLAN.md file and generates
|
|
5
|
+
// a proposed-changes.tla scratch spec with INVARIANT/PROPERTY stubs in {phaseDir}/.formal/.
|
|
6
|
+
// Also generates MCProposedChanges.cfg for TLC invocation.
|
|
7
|
+
//
|
|
8
|
+
// Reuses parsePlanFrontmatter and classifyTruth from generate-phase-spec.cjs.
|
|
9
|
+
//
|
|
10
|
+
// Usage:
|
|
11
|
+
// node bin/generate-proposed-changes.cjs <path-to-PLAN.md> [--dry-run]
|
|
12
|
+
//
|
|
13
|
+
// Output: {phaseDir}/.formal/proposed-changes.tla + MCProposedChanges.cfg
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const { parsePlanFrontmatter, classifyTruth } = require('./generate-phase-spec.cjs');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generate a TLA+ proposed-changes spec from a single PLAN.md file.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} planFilePath - Absolute path to a PLAN.md file
|
|
24
|
+
* @returns {{ generated: boolean, reason?: string, specPath?: string, truthCount?: number, classifications?: Array<{truth: string, kind: string, propName: string}> }}
|
|
25
|
+
*/
|
|
26
|
+
function generateProposedChanges(planFilePath) {
|
|
27
|
+
const content = fs.readFileSync(planFilePath, 'utf8');
|
|
28
|
+
const fm = parsePlanFrontmatter(content);
|
|
29
|
+
const phase = fm.phase || 'unknown';
|
|
30
|
+
const truths = fm.truths || [];
|
|
31
|
+
|
|
32
|
+
if (truths.length === 0) {
|
|
33
|
+
return { generated: false, reason: 'no truths' };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const classifications = [];
|
|
37
|
+
const timestamp = new Date().toISOString();
|
|
38
|
+
|
|
39
|
+
// Build TLA+ module
|
|
40
|
+
let spec = `---- MODULE ProposedChanges ----
|
|
41
|
+
\\* Source: must_haves: truths: from PLAN.md YAML frontmatter
|
|
42
|
+
\\* Generated by bin/generate-proposed-changes.cjs -- PLAN-01
|
|
43
|
+
\\* Phase: ${phase}
|
|
44
|
+
\\* Source file: ${planFilePath}
|
|
45
|
+
\\* Generated: ${timestamp}
|
|
46
|
+
|
|
47
|
+
EXTENDS Naturals, FiniteSets, TLC
|
|
48
|
+
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
// ReqNN-to-Truth index mapping block
|
|
52
|
+
spec += `\\* === ReqNN-to-Truth Mapping ===\n`;
|
|
53
|
+
truths.forEach((truth, idx) => {
|
|
54
|
+
const propName = `Req${String(idx + 1).padStart(2, '0')}`;
|
|
55
|
+
spec += `\\* ${propName} -> Truth[${idx}]: "${truth}"\n`;
|
|
56
|
+
});
|
|
57
|
+
spec += `\\* ================================\n\n`;
|
|
58
|
+
|
|
59
|
+
// Minimal state machine
|
|
60
|
+
spec += `VARIABLES state
|
|
61
|
+
|
|
62
|
+
Init == state = "INIT"
|
|
63
|
+
|
|
64
|
+
Next == state' = state
|
|
65
|
+
|
|
66
|
+
Spec == Init /\\ [][Next]_<<state>>
|
|
67
|
+
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
// Generate stubs for each truth
|
|
71
|
+
truths.forEach((truth, idx) => {
|
|
72
|
+
const kind = classifyTruth(truth);
|
|
73
|
+
const propName = `Req${String(idx + 1).padStart(2, '0')}`;
|
|
74
|
+
classifications.push({ truth, kind, propName });
|
|
75
|
+
|
|
76
|
+
// Escape for TLA+ comment
|
|
77
|
+
const label = truth.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
78
|
+
spec += `${propName} == TRUE \\* "${label}"\n\n`;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
spec += `====`;
|
|
82
|
+
|
|
83
|
+
// Determine output directory: phaseDir/.formal/
|
|
84
|
+
const phaseDir = path.dirname(planFilePath);
|
|
85
|
+
const formalDir = path.join(phaseDir, '.formal');
|
|
86
|
+
fs.mkdirSync(formalDir, { recursive: true });
|
|
87
|
+
|
|
88
|
+
const specPath = path.join(formalDir, 'ProposedChanges.tla');
|
|
89
|
+
fs.writeFileSync(specPath, spec, 'utf8');
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
generated: true,
|
|
93
|
+
specPath,
|
|
94
|
+
truthCount: truths.length,
|
|
95
|
+
classifications,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Generate MCProposedChanges.cfg from a proposed-changes.tla file.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} specPath - Absolute path to proposed-changes.tla
|
|
103
|
+
* @returns {{ cfgPath: string }}
|
|
104
|
+
*/
|
|
105
|
+
function generateTlaCfg(specPath) {
|
|
106
|
+
const specContent = fs.readFileSync(specPath, 'utf8');
|
|
107
|
+
const lines = specContent.split('\n');
|
|
108
|
+
|
|
109
|
+
let cfg = 'SPECIFICATION Spec\n';
|
|
110
|
+
|
|
111
|
+
// Parse each ReqNN line to determine if it was classified as INVARIANT or PROPERTY
|
|
112
|
+
// We read the classifications from the mapping block comments
|
|
113
|
+
const mappingRegex = /^\\?\*\s+(Req\d{2})\s+->\s+Truth\[(\d+)\]:\s+"(.+)"/;
|
|
114
|
+
const propNames = [];
|
|
115
|
+
|
|
116
|
+
for (const line of lines) {
|
|
117
|
+
const match = line.match(mappingRegex);
|
|
118
|
+
if (match) {
|
|
119
|
+
propNames.push({ propName: match[1], truth: match[3] });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Classify each truth to determine INVARIANT vs PROPERTY for the .cfg
|
|
124
|
+
for (const { propName, truth } of propNames) {
|
|
125
|
+
const kind = classifyTruth(truth);
|
|
126
|
+
if (kind === 'INVARIANT') {
|
|
127
|
+
cfg += `INVARIANT ${propName}\n`;
|
|
128
|
+
} else {
|
|
129
|
+
cfg += `PROPERTY ${propName}\n`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const cfgPath = path.join(path.dirname(specPath), 'MCProposedChanges.cfg');
|
|
134
|
+
fs.writeFileSync(cfgPath, cfg, 'utf8');
|
|
135
|
+
|
|
136
|
+
return { cfgPath };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ── CLI entrypoint ────────────────────────────────────────────────────────────
|
|
140
|
+
if (require.main === module) {
|
|
141
|
+
const args = process.argv.slice(2).filter(a => !a.startsWith('--'));
|
|
142
|
+
const flags = process.argv.slice(2).filter(a => a.startsWith('--'));
|
|
143
|
+
const isDryRun = flags.includes('--dry-run');
|
|
144
|
+
|
|
145
|
+
if (args.length === 0) {
|
|
146
|
+
process.stderr.write('[generate-proposed-changes] Usage: node bin/generate-proposed-changes.cjs <path-to-PLAN.md> [--dry-run]\n');
|
|
147
|
+
process.stderr.write('[generate-proposed-changes] Generates proposed-changes.tla + MCProposedChanges.cfg in {phaseDir}/.formal/\n');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const planFilePath = path.resolve(args[0]);
|
|
152
|
+
if (!fs.existsSync(planFilePath)) {
|
|
153
|
+
process.stderr.write('[generate-proposed-changes] Error: file not found: ' + planFilePath + '\n');
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (isDryRun) {
|
|
158
|
+
// Read and parse without writing
|
|
159
|
+
const content = fs.readFileSync(planFilePath, 'utf8');
|
|
160
|
+
const fm = parsePlanFrontmatter(content);
|
|
161
|
+
const truths = fm.truths || [];
|
|
162
|
+
|
|
163
|
+
if (truths.length === 0) {
|
|
164
|
+
process.stderr.write('[generate-proposed-changes] No truths found in ' + planFilePath + '\n');
|
|
165
|
+
process.exit(0);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
process.stdout.write('[generate-proposed-changes] DRY-RUN\n');
|
|
169
|
+
process.stdout.write('[generate-proposed-changes] Plan: ' + planFilePath + '\n');
|
|
170
|
+
process.stdout.write('[generate-proposed-changes] Truths: ' + truths.length + '\n');
|
|
171
|
+
truths.forEach((t, i) => {
|
|
172
|
+
const kind = classifyTruth(t);
|
|
173
|
+
process.stdout.write(' Req' + String(i + 1).padStart(2, '0') + ' [' + kind + ']: ' + t + '\n');
|
|
174
|
+
});
|
|
175
|
+
} else {
|
|
176
|
+
const result = generateProposedChanges(planFilePath);
|
|
177
|
+
|
|
178
|
+
if (!result.generated) {
|
|
179
|
+
process.stderr.write('[generate-proposed-changes] ' + result.reason + ' in ' + planFilePath + '\n');
|
|
180
|
+
process.exit(0);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const { cfgPath } = generateTlaCfg(result.specPath);
|
|
184
|
+
|
|
185
|
+
process.stdout.write('[generate-proposed-changes] Spec: ' + result.specPath + '\n');
|
|
186
|
+
process.stdout.write('[generate-proposed-changes] Config: ' + cfgPath + '\n');
|
|
187
|
+
process.stdout.write('[generate-proposed-changes] Truths: ' + result.truthCount + '\n');
|
|
188
|
+
result.classifications.forEach(c => {
|
|
189
|
+
process.stdout.write(' ' + c.propName + ' [' + c.kind + ']: ' + c.truth + '\n');
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = { generateProposedChanges, generateTlaCfg };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// bin/generate-tla-cfg.cjs
|
|
4
|
+
// Generates TLA+ model configuration files from the XState machine.
|
|
5
|
+
//
|
|
6
|
+
// The XState machine (src/machines/qgsd-workflow.machine.ts) is the SOURCE OF TRUTH.
|
|
7
|
+
// MCsafety.cfg and MCliveness.cfg are generated artifacts — do not edit them by hand.
|
|
8
|
+
// To change MaxDeliberation, update the XState machine context default instead.
|
|
9
|
+
//
|
|
10
|
+
// Usage:
|
|
11
|
+
// node bin/generate-tla-cfg.cjs # writes MCsafety.cfg + MCliveness.cfg
|
|
12
|
+
// node bin/generate-tla-cfg.cjs --dry # print output without writing
|
|
13
|
+
//
|
|
14
|
+
// Why agent counts are hardcoded here (not derived from code):
|
|
15
|
+
// SAFETY_AGENTS=5 and LIVENESS_AGENTS=3 are model-checking parameters, not
|
|
16
|
+
// runtime constants. 5 matches the quorum slot count; 3 keeps the liveness
|
|
17
|
+
// state space tractable (liveness + symmetry = incompatible in TLC).
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
const ROOT = path.join(__dirname, '..');
|
|
23
|
+
const DRY = process.argv.includes('--dry');
|
|
24
|
+
|
|
25
|
+
// ── Model-checking parameters (stable, not derived from code) ─────────────────
|
|
26
|
+
const SAFETY_AGENTS = 5; // N for MCsafety — matches QGSD quorum slot count
|
|
27
|
+
const LIVENESS_AGENTS = 3; // N for MCliveness — smaller for tractable liveness
|
|
28
|
+
|
|
29
|
+
// ── Extract MaxDeliberation from the XState machine ───────────────────────────
|
|
30
|
+
const machineFile = path.join(ROOT, 'src', 'machines', 'qgsd-workflow.machine.ts');
|
|
31
|
+
if (!fs.existsSync(machineFile)) {
|
|
32
|
+
process.stderr.write('[generate-tla-cfg] XState machine not found: ' + machineFile + '\n');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
const machineSrc = fs.readFileSync(machineFile, 'utf8');
|
|
36
|
+
|
|
37
|
+
const maxDelibMatch = machineSrc.match(/maxDeliberation:\s*(\d+)/);
|
|
38
|
+
if (!maxDelibMatch) {
|
|
39
|
+
process.stderr.write(
|
|
40
|
+
'[generate-tla-cfg] Could not find maxDeliberation in XState context defaults.\n' +
|
|
41
|
+
'[generate-tla-cfg] Expected: maxDeliberation: <number>\n'
|
|
42
|
+
);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
const maxDeliberation = parseInt(maxDelibMatch[1], 10);
|
|
46
|
+
process.stdout.write('[generate-tla-cfg] Extracted maxDeliberation=' + maxDeliberation + ' from XState machine\n');
|
|
47
|
+
|
|
48
|
+
// ── Generate agent declarations ───────────────────────────────────────────────
|
|
49
|
+
function agentDecls(n) {
|
|
50
|
+
const lines = [];
|
|
51
|
+
for (let i = 1; i <= n; i++) {
|
|
52
|
+
lines.push(' a' + i + ' = a' + i);
|
|
53
|
+
}
|
|
54
|
+
return lines.join('\n');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function agentsSet(n) {
|
|
58
|
+
const names = [];
|
|
59
|
+
for (let i = 1; i <= n; i++) names.push('a' + i);
|
|
60
|
+
return '{' + names.join(', ') + '}';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Build MCsafety.cfg ────────────────────────────────────────────────────────
|
|
64
|
+
const safetyCfg = [
|
|
65
|
+
'\\* .planning/formal/tla/MCsafety.cfg',
|
|
66
|
+
'\\* GENERATED — do not edit by hand.',
|
|
67
|
+
'\\* Source of truth: src/machines/qgsd-workflow.machine.ts',
|
|
68
|
+
'\\* Regenerate: node bin/generate-tla-cfg.cjs',
|
|
69
|
+
'\\*',
|
|
70
|
+
'\\* TLC safety model: N=' + SAFETY_AGENTS + ' agents, symmetry reduction, no liveness check.',
|
|
71
|
+
'\\* Run: node bin/run-tlc.cjs MCsafety (requires Java >=17 and tla2tools.jar)',
|
|
72
|
+
'\\* NOTE: SYMMETRY requires model values — agents declared as model values (a1=a1 etc.)',
|
|
73
|
+
'SPECIFICATION Spec',
|
|
74
|
+
'CONSTANTS',
|
|
75
|
+
agentDecls(SAFETY_AGENTS),
|
|
76
|
+
' Agents = ' + agentsSet(SAFETY_AGENTS),
|
|
77
|
+
' MaxDeliberation = ' + maxDeliberation,
|
|
78
|
+
'SYMMETRY AgentSymmetry',
|
|
79
|
+
'INVARIANT TypeOK',
|
|
80
|
+
'INVARIANT MinQuorumMet',
|
|
81
|
+
'PROPERTY NoInvalidTransition',
|
|
82
|
+
'CHECK_DEADLOCK FALSE',
|
|
83
|
+
'',
|
|
84
|
+
].join('\n');
|
|
85
|
+
|
|
86
|
+
// ── Build MCliveness.cfg ──────────────────────────────────────────────────────
|
|
87
|
+
const livenessCfg = [
|
|
88
|
+
'\\* .planning/formal/tla/MCliveness.cfg',
|
|
89
|
+
'\\* GENERATED — do not edit by hand.',
|
|
90
|
+
'\\* Source of truth: src/machines/qgsd-workflow.machine.ts',
|
|
91
|
+
'\\* Regenerate: node bin/generate-tla-cfg.cjs',
|
|
92
|
+
'\\*',
|
|
93
|
+
'\\* TLC liveness model: N=' + LIVENESS_AGENTS + ' agents, NO symmetry (incompatible with liveness), PROPERTY only.',
|
|
94
|
+
'\\* Use -workers 1 for liveness (defensive against older TLC multi-worker liveness bugs).',
|
|
95
|
+
'\\* Run: node bin/run-tlc.cjs MCliveness (requires Java >=17 and tla2tools.jar)',
|
|
96
|
+
'\\* NOTE: Agents set to ' + LIVENESS_AGENTS + ' model values for tractable liveness checking.',
|
|
97
|
+
'SPECIFICATION Spec',
|
|
98
|
+
'CONSTANTS',
|
|
99
|
+
agentDecls(LIVENESS_AGENTS),
|
|
100
|
+
' Agents = ' + agentsSet(LIVENESS_AGENTS),
|
|
101
|
+
' MaxDeliberation = ' + maxDeliberation,
|
|
102
|
+
'PROPERTY EventualConsensus',
|
|
103
|
+
'CHECK_DEADLOCK FALSE',
|
|
104
|
+
'',
|
|
105
|
+
].join('\n');
|
|
106
|
+
|
|
107
|
+
// ── Write or print ────────────────────────────────────────────────────────────
|
|
108
|
+
const safetyPath = path.join(ROOT, '.planning', 'formal', 'tla', 'MCsafety.cfg');
|
|
109
|
+
const livenessPath = path.join(ROOT, '.planning', 'formal', 'tla', 'MCliveness.cfg');
|
|
110
|
+
|
|
111
|
+
if (DRY) {
|
|
112
|
+
process.stdout.write('\n--- MCsafety.cfg ---\n' + safetyCfg);
|
|
113
|
+
process.stdout.write('\n--- MCliveness.cfg ---\n' + livenessCfg);
|
|
114
|
+
} else {
|
|
115
|
+
fs.writeFileSync(safetyPath, safetyCfg, 'utf8');
|
|
116
|
+
fs.writeFileSync(livenessPath, livenessCfg, 'utf8');
|
|
117
|
+
process.stdout.write('[generate-tla-cfg] Written: .planning/formal/tla/MCsafety.cfg\n');
|
|
118
|
+
process.stdout.write('[generate-tla-cfg] Written: .planning/formal/tla/MCliveness.cfg\n');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
process.stdout.write('[generate-tla-cfg] MaxDeliberation=' + maxDeliberation +
|
|
122
|
+
' SafetyAgents=' + SAFETY_AGENTS + ' LivenessAgents=' + LIVENESS_AGENTS + '\n');
|