@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.
Files changed (215) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +1024 -0
  3. package/agents/qgsd-codebase-mapper.md +764 -0
  4. package/agents/qgsd-debugger.md +1201 -0
  5. package/agents/qgsd-executor.md +472 -0
  6. package/agents/qgsd-integration-checker.md +443 -0
  7. package/agents/qgsd-phase-researcher.md +502 -0
  8. package/agents/qgsd-plan-checker.md +643 -0
  9. package/agents/qgsd-planner.md +1182 -0
  10. package/agents/qgsd-project-researcher.md +621 -0
  11. package/agents/qgsd-quorum-orchestrator.md +628 -0
  12. package/agents/qgsd-quorum-slot-worker.md +41 -0
  13. package/agents/qgsd-quorum-synthesizer.md +133 -0
  14. package/agents/qgsd-quorum-test-worker.md +37 -0
  15. package/agents/qgsd-quorum-worker.md +161 -0
  16. package/agents/qgsd-research-synthesizer.md +239 -0
  17. package/agents/qgsd-roadmapper.md +660 -0
  18. package/agents/qgsd-verifier.md +628 -0
  19. package/bin/accept-debug-invariant.cjs +165 -0
  20. package/bin/account-manager.cjs +719 -0
  21. package/bin/aggregate-requirements.cjs +466 -0
  22. package/bin/analyze-assumptions.cjs +757 -0
  23. package/bin/analyze-state-space.cjs +921 -0
  24. package/bin/attribute-trace-divergence.cjs +150 -0
  25. package/bin/auth-drivers/gh-cli.cjs +93 -0
  26. package/bin/auth-drivers/index.cjs +46 -0
  27. package/bin/auth-drivers/pool.cjs +67 -0
  28. package/bin/auth-drivers/simple.cjs +95 -0
  29. package/bin/autoClosePtoF.cjs +110 -0
  30. package/bin/blessed-terminal.cjs +350 -0
  31. package/bin/build-phase-index.cjs +472 -0
  32. package/bin/call-quorum-slot.cjs +541 -0
  33. package/bin/ccr-secure-config.cjs +99 -0
  34. package/bin/ccr-secure-start.cjs +83 -0
  35. package/bin/check-bundled-sdks.cjs +177 -0
  36. package/bin/check-coverage-guard.cjs +112 -0
  37. package/bin/check-liveness-fairness.cjs +95 -0
  38. package/bin/check-mcp-health.cjs +123 -0
  39. package/bin/check-provider-health.cjs +395 -0
  40. package/bin/check-results-exit.cjs +24 -0
  41. package/bin/check-spec-sync.cjs +360 -0
  42. package/bin/check-trace-redaction.cjs +271 -0
  43. package/bin/check-trace-schema-drift.cjs +99 -0
  44. package/bin/compareDrift.cjs +21 -0
  45. package/bin/conformance-schema.cjs +12 -0
  46. package/bin/count-scenarios.cjs +420 -0
  47. package/bin/debt-dedup.cjs +144 -0
  48. package/bin/debt-ledger.cjs +61 -0
  49. package/bin/debt-retention.cjs +76 -0
  50. package/bin/debt-state-machine.cjs +80 -0
  51. package/bin/detect-coverage-gaps.cjs +204 -0
  52. package/bin/detect-project-intent.cjs +362 -0
  53. package/bin/export-prism-constants.cjs +164 -0
  54. package/bin/extract-annotations.cjs +633 -0
  55. package/bin/extractFormalExpected.cjs +104 -0
  56. package/bin/fingerprint-drift.cjs +24 -0
  57. package/bin/fingerprint-issue.cjs +46 -0
  58. package/bin/formal-core.cjs +519 -0
  59. package/bin/formal-ref-linker.cjs +141 -0
  60. package/bin/formal-test-sync.cjs +788 -0
  61. package/bin/generate-formal-specs.cjs +588 -0
  62. package/bin/generate-petri-net.cjs +397 -0
  63. package/bin/generate-phase-spec.cjs +249 -0
  64. package/bin/generate-proposed-changes.cjs +194 -0
  65. package/bin/generate-tla-cfg.cjs +122 -0
  66. package/bin/generate-traceability-matrix.cjs +701 -0
  67. package/bin/generate-triage-bundle.cjs +300 -0
  68. package/bin/gh-account-rotate.cjs +34 -0
  69. package/bin/initialize-model-registry.cjs +105 -0
  70. package/bin/install-formal-tools.cjs +382 -0
  71. package/bin/install.js +2424 -0
  72. package/bin/isNumericThreshold.cjs +34 -0
  73. package/bin/issue-classifier.cjs +151 -0
  74. package/bin/levenshtein.cjs +74 -0
  75. package/bin/lint-formal-models.cjs +580 -0
  76. package/bin/load-baseline-requirements.cjs +275 -0
  77. package/bin/manage-agents-core.cjs +815 -0
  78. package/bin/migrate-formal-dir.cjs +172 -0
  79. package/bin/migrate-planning.cjs +206 -0
  80. package/bin/migrate-to-slots.cjs +255 -0
  81. package/bin/nForma.cjs +2726 -0
  82. package/bin/observe-config.cjs +353 -0
  83. package/bin/observe-debt-writer.cjs +140 -0
  84. package/bin/observe-handler-grafana.cjs +128 -0
  85. package/bin/observe-handler-internal.cjs +301 -0
  86. package/bin/observe-handler-logstash.cjs +153 -0
  87. package/bin/observe-handler-prometheus.cjs +185 -0
  88. package/bin/observe-handlers.cjs +436 -0
  89. package/bin/observe-registry.cjs +131 -0
  90. package/bin/observe-render.cjs +168 -0
  91. package/bin/planning-paths.cjs +167 -0
  92. package/bin/polyrepo.cjs +560 -0
  93. package/bin/prism-priority.cjs +153 -0
  94. package/bin/probe-quorum-slots.cjs +167 -0
  95. package/bin/promote-model.cjs +225 -0
  96. package/bin/propose-debug-invariants.cjs +165 -0
  97. package/bin/providers.json +392 -0
  98. package/bin/pty-proxy.py +129 -0
  99. package/bin/qgsd-solve.cjs +2477 -0
  100. package/bin/quorum-consensus-gate.cjs +238 -0
  101. package/bin/quorum-formal-context.cjs +183 -0
  102. package/bin/quorum-slot-dispatch.cjs +934 -0
  103. package/bin/read-policy.cjs +60 -0
  104. package/bin/requirement-map.cjs +63 -0
  105. package/bin/requirements-core.cjs +247 -0
  106. package/bin/resolve-cli.cjs +101 -0
  107. package/bin/review-mcp-logs.cjs +294 -0
  108. package/bin/run-account-manager-tlc.cjs +188 -0
  109. package/bin/run-account-pool-alloy.cjs +158 -0
  110. package/bin/run-alloy.cjs +153 -0
  111. package/bin/run-audit-alloy.cjs +187 -0
  112. package/bin/run-breaker-tlc.cjs +181 -0
  113. package/bin/run-formal-check.cjs +395 -0
  114. package/bin/run-formal-verify.cjs +701 -0
  115. package/bin/run-installer-alloy.cjs +188 -0
  116. package/bin/run-oauth-rotation-prism.cjs +132 -0
  117. package/bin/run-oscillation-tlc.cjs +202 -0
  118. package/bin/run-phase-tlc.cjs +228 -0
  119. package/bin/run-prism.cjs +446 -0
  120. package/bin/run-protocol-tlc.cjs +201 -0
  121. package/bin/run-quorum-composition-alloy.cjs +155 -0
  122. package/bin/run-sensitivity-sweep.cjs +231 -0
  123. package/bin/run-stop-hook-tlc.cjs +188 -0
  124. package/bin/run-tlc.cjs +467 -0
  125. package/bin/run-transcript-alloy.cjs +173 -0
  126. package/bin/run-uppaal.cjs +264 -0
  127. package/bin/secrets.cjs +134 -0
  128. package/bin/sensitivity-report.cjs +219 -0
  129. package/bin/sensitivity-sweep-feedback.cjs +194 -0
  130. package/bin/set-secret.cjs +29 -0
  131. package/bin/setup-telemetry-cron.sh +36 -0
  132. package/bin/sweepPtoF.cjs +63 -0
  133. package/bin/sync-baseline-requirements.cjs +290 -0
  134. package/bin/task-envelope.cjs +360 -0
  135. package/bin/telemetry-collector.cjs +229 -0
  136. package/bin/unified-mcp-server.mjs +735 -0
  137. package/bin/update-agents.cjs +369 -0
  138. package/bin/update-scoreboard.cjs +1134 -0
  139. package/bin/validate-debt-entry.cjs +207 -0
  140. package/bin/validate-invariant.cjs +419 -0
  141. package/bin/validate-memory.cjs +389 -0
  142. package/bin/validate-requirements-haiku.cjs +435 -0
  143. package/bin/validate-traces.cjs +438 -0
  144. package/bin/verify-formal-results.cjs +124 -0
  145. package/bin/verify-quorum-health.cjs +273 -0
  146. package/bin/write-check-result.cjs +106 -0
  147. package/bin/xstate-to-tla.cjs +483 -0
  148. package/bin/xstate-trace-walker.cjs +205 -0
  149. package/commands/qgsd/add-phase.md +43 -0
  150. package/commands/qgsd/add-requirement.md +24 -0
  151. package/commands/qgsd/add-todo.md +47 -0
  152. package/commands/qgsd/audit-milestone.md +37 -0
  153. package/commands/qgsd/check-todos.md +45 -0
  154. package/commands/qgsd/cleanup.md +18 -0
  155. package/commands/qgsd/close-formal-gaps.md +33 -0
  156. package/commands/qgsd/complete-milestone.md +136 -0
  157. package/commands/qgsd/debug.md +166 -0
  158. package/commands/qgsd/discuss-phase.md +83 -0
  159. package/commands/qgsd/execute-phase.md +117 -0
  160. package/commands/qgsd/fix-tests.md +27 -0
  161. package/commands/qgsd/formal-test-sync.md +32 -0
  162. package/commands/qgsd/health.md +22 -0
  163. package/commands/qgsd/help.md +22 -0
  164. package/commands/qgsd/insert-phase.md +32 -0
  165. package/commands/qgsd/join-discord.md +18 -0
  166. package/commands/qgsd/list-phase-assumptions.md +46 -0
  167. package/commands/qgsd/map-codebase.md +71 -0
  168. package/commands/qgsd/map-requirements.md +20 -0
  169. package/commands/qgsd/mcp-restart.md +176 -0
  170. package/commands/qgsd/mcp-set-model.md +134 -0
  171. package/commands/qgsd/mcp-setup.md +1371 -0
  172. package/commands/qgsd/mcp-status.md +274 -0
  173. package/commands/qgsd/mcp-update.md +238 -0
  174. package/commands/qgsd/new-milestone.md +44 -0
  175. package/commands/qgsd/new-project.md +42 -0
  176. package/commands/qgsd/observe.md +260 -0
  177. package/commands/qgsd/pause-work.md +38 -0
  178. package/commands/qgsd/plan-milestone-gaps.md +34 -0
  179. package/commands/qgsd/plan-phase.md +44 -0
  180. package/commands/qgsd/polyrepo.md +50 -0
  181. package/commands/qgsd/progress.md +24 -0
  182. package/commands/qgsd/queue.md +54 -0
  183. package/commands/qgsd/quick.md +133 -0
  184. package/commands/qgsd/quorum-test.md +275 -0
  185. package/commands/qgsd/quorum.md +707 -0
  186. package/commands/qgsd/reapply-patches.md +110 -0
  187. package/commands/qgsd/remove-phase.md +31 -0
  188. package/commands/qgsd/research-phase.md +189 -0
  189. package/commands/qgsd/resume-work.md +40 -0
  190. package/commands/qgsd/set-profile.md +34 -0
  191. package/commands/qgsd/settings.md +39 -0
  192. package/commands/qgsd/solve.md +565 -0
  193. package/commands/qgsd/sync-baselines.md +119 -0
  194. package/commands/qgsd/triage.md +233 -0
  195. package/commands/qgsd/update.md +37 -0
  196. package/commands/qgsd/verify-work.md +38 -0
  197. package/hooks/dist/config-loader.js +297 -0
  198. package/hooks/dist/conformance-schema.cjs +12 -0
  199. package/hooks/dist/gsd-context-monitor.js +64 -0
  200. package/hooks/dist/qgsd-check-update.js +62 -0
  201. package/hooks/dist/qgsd-circuit-breaker.js +682 -0
  202. package/hooks/dist/qgsd-precompact.js +156 -0
  203. package/hooks/dist/qgsd-prompt.js +653 -0
  204. package/hooks/dist/qgsd-session-start.js +122 -0
  205. package/hooks/dist/qgsd-slot-correlator.js +58 -0
  206. package/hooks/dist/qgsd-spec-regen.js +86 -0
  207. package/hooks/dist/qgsd-statusline.js +91 -0
  208. package/hooks/dist/qgsd-stop.js +553 -0
  209. package/hooks/dist/qgsd-token-collector.js +133 -0
  210. package/hooks/dist/unified-mcp-server.mjs +669 -0
  211. package/package.json +95 -0
  212. package/scripts/build-hooks.js +46 -0
  213. package/scripts/postinstall.js +48 -0
  214. package/scripts/secret-audit.sh +45 -0
  215. package/templates/qgsd.json +49 -0
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // bin/run-installer-alloy.cjs
4
+ // Invokes Alloy 6 JAR headless for QGSD installer and taxonomy specs (GAP-7, GAP-8).
5
+ // Requirements: GAP-7, GAP-8
6
+ //
7
+ // Usage:
8
+ // node bin/run-installer-alloy.cjs [--spec=<name>]
9
+ //
10
+ // Valid spec names:
11
+ // install-scope (default) — GAP-7: installer rollback soundness + config sync completeness
12
+ // taxonomy-safety — GAP-8: Haiku taxonomy injection safety + closed/open consistency
13
+ //
14
+ // Prerequisites:
15
+ // - Java >=17 (https://adoptium.net/)
16
+ // - .planning/formal/alloy/org.alloytools.alloy.dist.jar (see VERIFICATION_TOOLS.md for download)
17
+
18
+ const { spawnSync } = require('child_process');
19
+ const JAVA_HEAP_MAX = process.env.QGSD_JAVA_HEAP_MAX || '512m';
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+ const { writeCheckResult } = require('./write-check-result.cjs');
23
+ const { getRequirementIds } = require('./requirement-map.cjs');
24
+
25
+ // ── Resolve project root (--project-root= overrides __dirname-relative) ─────
26
+ let ROOT = path.join(__dirname, '..');
27
+ for (const arg of process.argv) {
28
+ if (arg.startsWith('--project-root=')) ROOT = path.resolve(arg.slice('--project-root='.length));
29
+ }
30
+
31
+ // ── 0. Parse --spec argument ──────────────────────────────────────────────────
32
+ const VALID_SPECS = ['install-scope', 'taxonomy-safety'];
33
+ const CHECK_ID_MAP = {
34
+ 'install-scope': 'alloy:install-scope',
35
+ 'taxonomy-safety': 'alloy:taxonomy-safety',
36
+ };
37
+ const PROPERTY_MAP = {
38
+ 'install-scope': 'Installer rollback soundness and config sync completeness',
39
+ 'taxonomy-safety': 'Taxonomy injection prevention and closed/open consistency',
40
+ };
41
+
42
+ const args = process.argv.slice(2);
43
+ const specArg = args.find(a => a.startsWith('--spec=')) || null;
44
+ const specName = specArg
45
+ ? specArg.split('=')[1]
46
+ : (args.find(a => !a.startsWith('-')) || 'install-scope');
47
+
48
+ if (!VALID_SPECS.includes(specName)) {
49
+ process.stderr.write(
50
+ '[run-installer-alloy] Unknown spec: ' + specName +
51
+ '. Valid: ' + VALID_SPECS.join(', ') + '\n'
52
+ );
53
+ const check_id = CHECK_ID_MAP[specName] || ('alloy:' + specName);
54
+ try { writeCheckResult({ tool: 'run-installer-alloy', formalism: 'alloy', result: 'fail', check_id: check_id, surface: 'alloy', property: PROPERTY_MAP[specName] || specName, runtime_ms: 0, summary: 'fail: ' + check_id + ' (invalid spec)', triage_tags: [], requirement_ids: getRequirementIds(check_id), metadata: { spec: specName } }); } catch (e) { process.stderr.write('[run-installer-alloy] Warning: failed to write check result: ' + e.message + '\n'); }
55
+ process.exit(1);
56
+ }
57
+
58
+ // ── 1. Locate Java ───────────────────────────────────────────────────────────
59
+ const JAVA_HOME = process.env.JAVA_HOME;
60
+ let javaExe;
61
+
62
+ const check_id = CHECK_ID_MAP[specName];
63
+ const property = PROPERTY_MAP[specName];
64
+
65
+ if (JAVA_HOME) {
66
+ javaExe = path.join(JAVA_HOME, 'bin', 'java');
67
+ if (!fs.existsSync(javaExe)) {
68
+ process.stderr.write(
69
+ '[run-installer-alloy] JAVA_HOME is set but java binary not found at: ' + javaExe + '\n' +
70
+ '[run-installer-alloy] Unset JAVA_HOME or fix the path.\n'
71
+ );
72
+ try { writeCheckResult({ tool: 'run-installer-alloy', formalism: 'alloy', result: 'fail', check_id: check_id, surface: 'alloy', property: property, runtime_ms: 0, summary: 'fail: ' + check_id + ' (Java not found)', triage_tags: [], requirement_ids: getRequirementIds(check_id), metadata: { spec: specName } }); } catch (e) { process.stderr.write('[run-installer-alloy] Warning: failed to write check result: ' + e.message + '\n'); }
73
+ process.exit(1);
74
+ }
75
+ } else {
76
+ // Fall back to PATH lookup
77
+ const probe = spawnSync('java', ['--version'], { encoding: 'utf8' });
78
+ if (probe.error || probe.status !== 0) {
79
+ process.stderr.write(
80
+ '[run-installer-alloy] Java not found. Install Java >=17 and set JAVA_HOME.\n' +
81
+ '[run-installer-alloy] Download: https://adoptium.net/\n'
82
+ );
83
+ try { writeCheckResult({ tool: 'run-installer-alloy', formalism: 'alloy', result: 'fail', check_id: check_id, surface: 'alloy', property: property, runtime_ms: 0, summary: 'fail: ' + check_id + ' (Java not found)', triage_tags: [], requirement_ids: getRequirementIds(check_id), metadata: { spec: specName } }); } catch (e) { process.stderr.write('[run-installer-alloy] Warning: failed to write check result: ' + e.message + '\n'); }
84
+ process.exit(1);
85
+ }
86
+ javaExe = 'java';
87
+ }
88
+
89
+ // ── 2. Check Java version >=17 ───────────────────────────────────────────────
90
+ const versionResult = spawnSync(javaExe, ['--version'], { encoding: 'utf8' });
91
+ if (versionResult.error || versionResult.status !== 0) {
92
+ process.stderr.write('[run-installer-alloy] Failed to run: ' + javaExe + ' --version\n');
93
+ try { writeCheckResult({ tool: 'run-installer-alloy', formalism: 'alloy', result: 'fail', check_id: check_id, surface: 'alloy', property: property, runtime_ms: 0, summary: 'fail: ' + check_id + ' (version check failed)', triage_tags: [], requirement_ids: getRequirementIds(check_id), metadata: { spec: specName } }); } catch (e) { process.stderr.write('[run-installer-alloy] Warning: failed to write check result: ' + e.message + '\n'); }
94
+ process.exit(1);
95
+ }
96
+ const versionOutput = versionResult.stdout + versionResult.stderr;
97
+ // Java version string varies: "openjdk 17.0.1 ..." or "java version \"17.0.1\""
98
+ const versionMatch = versionOutput.match(/(?:openjdk\s+|java version\s+[""]?)(\d+)/i);
99
+ const javaMajor = versionMatch ? parseInt(versionMatch[1], 10) : 0;
100
+ if (javaMajor < 17) {
101
+ process.stderr.write(
102
+ '[run-installer-alloy] Java >=17 required. Found: ' + versionOutput.split('\n')[0] + '\n' +
103
+ '[run-installer-alloy] Download Java 17+: https://adoptium.net/\n'
104
+ );
105
+ try { writeCheckResult({ tool: 'run-installer-alloy', formalism: 'alloy', result: 'fail', check_id: check_id, surface: 'alloy', property: property, runtime_ms: 0, summary: 'fail: ' + check_id + ' (Java < 17)', triage_tags: [], requirement_ids: getRequirementIds(check_id), metadata: { spec: specName } }); } catch (e) { process.stderr.write('[run-installer-alloy] Warning: failed to write check result: ' + e.message + '\n'); }
106
+ process.exit(1);
107
+ }
108
+
109
+ // ── 3. Locate org.alloytools.alloy.dist.jar ──────────────────────────────────
110
+ const jarPath = path.join(ROOT, '.planning', 'formal', 'alloy', 'org.alloytools.alloy.dist.jar');
111
+ if (!fs.existsSync(jarPath)) {
112
+ process.stderr.write(
113
+ '[run-installer-alloy] org.alloytools.alloy.dist.jar not found at: ' + jarPath + '\n' +
114
+ '[run-installer-alloy] Download Alloy 6.2.0:\n' +
115
+ ' curl -L https://github.com/AlloyTools/org.alloytools.alloy/releases/download/v6.2.0/org.alloytools.alloy.dist.jar \\\n' +
116
+ ' -o .planning/formal/alloy/org.alloytools.alloy.dist.jar\n'
117
+ );
118
+ try { writeCheckResult({ tool: 'run-installer-alloy', formalism: 'alloy', result: 'fail', check_id: check_id, surface: 'alloy', property: property, runtime_ms: 0, summary: 'fail: ' + check_id + ' (JAR not found)', triage_tags: [], requirement_ids: getRequirementIds(check_id), metadata: { spec: specName } }); } catch (e) { process.stderr.write('[run-installer-alloy] Warning: failed to write check result: ' + e.message + '\n'); }
119
+ process.exit(1);
120
+ }
121
+
122
+ // ── 4. Locate .als file ───────────────────────────────────────────────────────
123
+ const alsPath = path.join(ROOT, '.planning', 'formal', 'alloy', specName + '.als');
124
+ if (!fs.existsSync(alsPath)) {
125
+ process.stderr.write(
126
+ '[run-installer-alloy] ' + specName + '.als not found at: ' + alsPath + '\n' +
127
+ '[run-installer-alloy] This file should exist in the repository. Check your git status.\n'
128
+ );
129
+ try { writeCheckResult({ tool: 'run-installer-alloy', formalism: 'alloy', result: 'fail', check_id: check_id, surface: 'alloy', property: property, runtime_ms: 0, summary: 'fail: ' + check_id + ' (ALS not found)', triage_tags: [], requirement_ids: getRequirementIds(check_id), metadata: { spec: specName } }); } catch (e) { process.stderr.write('[run-installer-alloy] Warning: failed to write check result: ' + e.message + '\n'); }
130
+ process.exit(1);
131
+ }
132
+
133
+ // ── 5. Invoke Alloy 6 ────────────────────────────────────────────────────────
134
+ process.stdout.write('[run-installer-alloy] spec: ' + specName + '\n');
135
+ process.stdout.write('[run-installer-alloy] ALS: ' + alsPath + '\n');
136
+ process.stdout.write('[run-installer-alloy] JAR: ' + jarPath + '\n');
137
+
138
+ const _startMs = Date.now();
139
+
140
+ // Use stdio: 'pipe' so we can scan stdout for counterexamples (Alloy exits 0 even on CEX)
141
+ process.stderr.write('[heap] Xms=64m Xmx=' + JAVA_HEAP_MAX + '\n');
142
+ const alloyResult = spawnSync(javaExe, [
143
+ '-Djava.awt.headless=true',
144
+ '-Xms64m', '-Xmx' + JAVA_HEAP_MAX,
145
+ '-jar', jarPath,
146
+ 'exec',
147
+ '--output', '-',
148
+ '--type', 'text',
149
+ '--quiet',
150
+ alsPath,
151
+ ], { encoding: 'utf8', stdio: 'pipe' });
152
+
153
+ if (alloyResult.error) {
154
+ process.stderr.write('[run-installer-alloy] Alloy invocation failed: ' + alloyResult.error.message + '\n');
155
+ const _runtimeMs = Date.now() - _startMs;
156
+ try { writeCheckResult({ tool: 'run-installer-alloy', formalism: 'alloy', result: 'fail', check_id: check_id, surface: 'alloy', property: property, runtime_ms: _runtimeMs, summary: 'fail: ' + check_id + ' in ' + _runtimeMs + 'ms', triage_tags: _runtimeMs > 60000 ? ['timeout-risk'] : [], requirement_ids: getRequirementIds(check_id), metadata: { spec: specName } }); } catch (e) { process.stderr.write('[run-installer-alloy] Warning: failed to write check result: ' + e.message + '\n'); }
157
+ process.exit(1);
158
+ }
159
+
160
+ // ── 6. Scan stdout for counterexamples ───────────────────────────────────────
161
+ // Alloy 6 exits 0 even when counterexamples are found. Scan stdout to detect them.
162
+ const stdout = alloyResult.stdout || '';
163
+ const stderr = alloyResult.stderr || '';
164
+
165
+ // Write stdout to process.stdout (mirrors stdio: 'inherit' output)
166
+ if (stdout) { process.stdout.write(stdout); }
167
+ if (stderr) { process.stderr.write(stderr); }
168
+
169
+ if (/Counterexample/i.test(stdout)) {
170
+ process.stderr.write(
171
+ '[run-installer-alloy] WARNING: Counterexample found in ' + specName + '.als assertion.\n' +
172
+ '[run-installer-alloy] This indicates a spec violation — review .planning/formal/alloy/' + specName + '.als.\n'
173
+ );
174
+ const _runtimeMs = Date.now() - _startMs;
175
+ try { writeCheckResult({ tool: 'run-installer-alloy', formalism: 'alloy', result: 'fail', check_id: check_id, surface: 'alloy', property: property, runtime_ms: _runtimeMs, summary: 'fail: ' + check_id + ' in ' + _runtimeMs + 'ms', triage_tags: _runtimeMs > 60000 ? ['timeout-risk'] : [], requirement_ids: getRequirementIds(check_id), metadata: { spec: specName } }); } catch (e) { process.stderr.write('[run-installer-alloy] Warning: failed to write check result: ' + e.message + '\n'); }
176
+ process.exit(1);
177
+ }
178
+
179
+ // ── 7. Propagate Alloy exit code ─────────────────────────────────────────────
180
+ if (alloyResult.status !== 0) {
181
+ const _runtimeMs = Date.now() - _startMs;
182
+ try { writeCheckResult({ tool: 'run-installer-alloy', formalism: 'alloy', result: 'fail', check_id: check_id, surface: 'alloy', property: property, runtime_ms: _runtimeMs, summary: 'fail: ' + check_id + ' in ' + _runtimeMs + 'ms', triage_tags: _runtimeMs > 60000 ? ['timeout-risk'] : [], requirement_ids: getRequirementIds(check_id), metadata: { spec: specName } }); } catch (e) { process.stderr.write('[run-installer-alloy] Warning: failed to write check result: ' + e.message + '\n'); }
183
+ process.exit(alloyResult.status || 1);
184
+ }
185
+
186
+ const _runtimeMs = Date.now() - _startMs;
187
+ try { writeCheckResult({ tool: 'run-installer-alloy', formalism: 'alloy', result: 'pass', check_id: check_id, surface: 'alloy', property: property, runtime_ms: _runtimeMs, summary: 'pass: ' + check_id + ' in ' + _runtimeMs + 'ms', triage_tags: _runtimeMs > 60000 ? ['timeout-risk'] : [], requirement_ids: getRequirementIds(check_id), metadata: { spec: specName } }); } catch (e) { process.stderr.write('[run-installer-alloy] Warning: failed to write check result: ' + e.message + '\n'); }
188
+ process.exit(0);
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // bin/run-oauth-rotation-prism.cjs
4
+ // Invokes PRISM model checker against .planning/formal/prism/oauth-rotation.pm.
5
+ // Requirements: PRM-AM-01
6
+ //
7
+ // Usage:
8
+ // node bin/run-oauth-rotation-prism.cjs # all 3 properties
9
+ // node bin/run-oauth-rotation-prism.cjs -pf "P=? [ F s=1 ]" # explicit property
10
+ // node bin/run-oauth-rotation-prism.cjs -const p_fail=0.15 # override failure rate
11
+ // node bin/run-oauth-rotation-prism.cjs -const p_fail=0.15 -const max_retries=5
12
+ //
13
+ // Properties (.planning/formal/prism/oauth-rotation.props):
14
+ // P=? [ F s=1 ] — probability of eventual success within max_retries
15
+ // P=? [ F s=0 ] — probability all attempts fail (complement)
16
+ // R{"rotations"}=? [ F s<=1] — expected number of rotation attempts before outcome
17
+ //
18
+ // Defaults: p_fail=0.30, max_retries=3
19
+ // Expected P(succeed) ≈ 1 - 0.3^3 = 0.973
20
+ //
21
+ // Prerequisites:
22
+ // - PRISM 4.x installed; set PRISM_BIN to path of the prism shell script
23
+ // e.g. export PRISM_BIN="$HOME/prism/bin/prism"
24
+ // - Java >=17 (same JRE used by TLA+/Alloy)
25
+
26
+ const { spawnSync } = require('child_process');
27
+ const fs = require('fs');
28
+ const path = require('path');
29
+ const { writeCheckResult } = require('./write-check-result.cjs');
30
+ const { getRequirementIds } = require('./requirement-map.cjs');
31
+
32
+ // ── Locate PRISM binary ──────────────────────────────────────────────────────
33
+ const prismBin = process.env.PRISM_BIN || 'prism';
34
+
35
+ if (prismBin !== 'prism' && !fs.existsSync(prismBin)) {
36
+ process.stderr.write(
37
+ '[run-oauth-rotation-prism] PRISM binary not found at: ' + prismBin + '\n' +
38
+ '[run-oauth-rotation-prism] Install PRISM and set PRISM_BIN env var:\n' +
39
+ '[run-oauth-rotation-prism] export PRISM_BIN="$HOME/prism/bin/prism"\n' +
40
+ '[run-oauth-rotation-prism] Download: https://www.prismmodelchecker.org/download.php\n'
41
+ );
42
+ try { writeCheckResult({ tool: 'run-oauth-rotation-prism', formalism: 'prism', result: 'fail', check_id: 'prism:oauth-rotation', surface: 'prism', property: 'OAuth token rotation probability — successful rotation under expiry and concurrency', runtime_ms: 0, summary: 'fail: prism:oauth-rotation (binary not found)', triage_tags: [], requirement_ids: getRequirementIds('prism:oauth-rotation'), observation_window: { window_start: new Date().toISOString(), window_end: new Date().toISOString(), n_traces: 0, n_events: 0, window_days: 0 }, metadata: {} }); } catch (e) { process.stderr.write('[run-oauth-rotation-prism] Warning: failed to write check result: ' + e.message + '\n'); }
43
+ process.exit(1);
44
+ }
45
+
46
+ // ── Locate model file ────────────────────────────────────────────────────────
47
+ const modelPath = path.join(__dirname, '..', '.planning', 'formal', 'prism', 'oauth-rotation.pm');
48
+ if (!fs.existsSync(modelPath)) {
49
+ process.stderr.write(
50
+ '[run-oauth-rotation-prism] Model file not found: ' + modelPath + '\n'
51
+ );
52
+ try { writeCheckResult({ tool: 'run-oauth-rotation-prism', formalism: 'prism', result: 'fail', check_id: 'prism:oauth-rotation', surface: 'prism', property: 'OAuth token rotation probability — successful rotation under expiry and concurrency', runtime_ms: 0, summary: 'fail: prism:oauth-rotation (model not found)', triage_tags: [], requirement_ids: getRequirementIds('prism:oauth-rotation'), observation_window: { window_start: new Date().toISOString(), window_end: new Date().toISOString(), n_traces: 0, n_events: 0, window_days: 0 }, metadata: {} }); } catch (e) { process.stderr.write('[run-oauth-rotation-prism] Warning: failed to write check result: ' + e.message + '\n'); }
53
+ process.exit(1);
54
+ }
55
+
56
+ // ── Extract max_retries from providers.json (live source of truth) ───────────
57
+ // Reads bin/providers.json → gemini-1.oauth_rotation.max_retries.
58
+ // Injected as -const max_retries=<N> unless the caller already supplies it.
59
+ let liveMaxRetries = null;
60
+ const providersPath = path.join(__dirname, 'providers.json');
61
+ if (fs.existsSync(providersPath)) {
62
+ try {
63
+ const parsed = JSON.parse(fs.readFileSync(providersPath, 'utf8'));
64
+ // providers.json has shape { providers: [...] } with each entry having a `name` field
65
+ const list = Array.isArray(parsed) ? parsed : (parsed.providers || []);
66
+ const gemini1 = list.find(function(p) { return p.name === 'gemini-1' || p.id === 'gemini-1'; }) || {};
67
+ const rot = gemini1.oauth_rotation || {};
68
+ if (typeof rot.max_retries === 'number') {
69
+ liveMaxRetries = rot.max_retries;
70
+ process.stdout.write(
71
+ '[run-oauth-rotation-prism] max_retries=' + liveMaxRetries +
72
+ ' (from providers.json)\n'
73
+ );
74
+ }
75
+ } catch (_) { /* malformed providers.json — fall through to spec default */ }
76
+ }
77
+
78
+ // ── Build argument list ──────────────────────────────────────────────────────
79
+ // Extra args passed to this script are forwarded to PRISM after the model path.
80
+ // If .planning/formal/prism/oauth-rotation.props exists, pass it as the properties file.
81
+ // Otherwise fall back to a single inline property.
82
+ const extraArgs = process.argv.slice(2);
83
+ const hasPf = extraArgs.some(a => a === '-pf' || a === '-prop');
84
+ const propsFile = path.join(__dirname, '..', '.planning', 'formal', 'prism', 'oauth-rotation.props');
85
+ const hasProps = !hasPf && fs.existsSync(propsFile);
86
+ // Inject live max_retries unless the caller already overrides it
87
+ const callerOverridesRetries = extraArgs.some(
88
+ (a, i) => a === '-const' && (extraArgs[i + 1] || '').startsWith('max_retries=')
89
+ );
90
+
91
+ const prismArgs = [modelPath];
92
+ if (hasProps) {
93
+ prismArgs.push(propsFile);
94
+ } else if (!hasPf) {
95
+ prismArgs.push('-pf', 'P=? [ F s=1 ]');
96
+ }
97
+ if (liveMaxRetries !== null && !callerOverridesRetries) {
98
+ prismArgs.push('-const', 'max_retries=' + liveMaxRetries);
99
+ }
100
+ prismArgs.push(...extraArgs);
101
+
102
+ process.stdout.write('[run-oauth-rotation-prism] Binary: ' + prismBin + '\n');
103
+ process.stdout.write('[run-oauth-rotation-prism] Model: ' + modelPath + '\n');
104
+ process.stdout.write('[run-oauth-rotation-prism] Args: ' + prismArgs.slice(1).join(' ') + '\n');
105
+
106
+ // ── Invoke PRISM ─────────────────────────────────────────────────────────────
107
+ const _startMs = Date.now();
108
+
109
+ const result = spawnSync(prismBin, prismArgs, {
110
+ encoding: 'utf8',
111
+ stdio: 'inherit',
112
+ });
113
+
114
+ if (result.error) {
115
+ process.stderr.write('[run-oauth-rotation-prism] Failed to launch PRISM: ' + result.error.message + '\n');
116
+ const _runtimeMs = Date.now() - _startMs;
117
+ const tags = [];
118
+ if (_runtimeMs > 300000) tags.push('timeout-risk');
119
+ else if (_runtimeMs > 120000) tags.push('slow-verify');
120
+ tags.push('low-confidence');
121
+ try { writeCheckResult({ tool: 'run-oauth-rotation-prism', formalism: 'prism', result: 'fail', check_id: 'prism:oauth-rotation', surface: 'prism', property: 'OAuth token rotation probability — successful rotation under expiry and concurrency', runtime_ms: _runtimeMs, summary: 'fail: prism:oauth-rotation in ' + _runtimeMs + 'ms', triage_tags: tags, requirement_ids: getRequirementIds('prism:oauth-rotation'), observation_window: { window_start: new Date().toISOString(), window_end: new Date().toISOString(), n_traces: 0, n_events: 0, window_days: 0 }, metadata: {} }); } catch (e) { process.stderr.write('[run-oauth-rotation-prism] Warning: failed to write check result: ' + e.message + '\n'); }
122
+ process.exit(1);
123
+ }
124
+
125
+ const passed = (result.status || 0) === 0;
126
+ const _runtimeMs = Date.now() - _startMs;
127
+ const tags = [];
128
+ if (_runtimeMs > 300000) tags.push('timeout-risk');
129
+ else if (_runtimeMs > 120000) tags.push('slow-verify');
130
+ tags.push('low-confidence'); // OAuth rotation always insufficient data
131
+ try { writeCheckResult({ tool: 'run-oauth-rotation-prism', formalism: 'prism', result: passed ? 'pass' : 'fail', check_id: 'prism:oauth-rotation', surface: 'prism', property: 'OAuth token rotation probability — successful rotation under expiry and concurrency', runtime_ms: _runtimeMs, summary: (passed ? 'pass' : 'fail') + ': prism:oauth-rotation in ' + _runtimeMs + 'ms', triage_tags: tags, requirement_ids: getRequirementIds('prism:oauth-rotation'), observation_window: { window_start: new Date().toISOString(), window_end: new Date().toISOString(), n_traces: 0, n_events: 0, window_days: 0 }, metadata: {} }); } catch (e) { process.stderr.write('[run-oauth-rotation-prism] Warning: failed to write check result: ' + e.message + '\n'); }
132
+ process.exit(result.status || 0);
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // bin/run-oscillation-tlc.cjs
4
+ // Invokes TLC model checker for QGSD oscillation and convergence TLA+ specifications.
5
+ // Requirements: GAP-1, GAP-5
6
+ //
7
+ // Usage:
8
+ // node bin/run-oscillation-tlc.cjs MCoscillation # oscillation detection (liveness, -workers 1)
9
+ // node bin/run-oscillation-tlc.cjs MCconvergence # state persistence (liveness, -workers 1)
10
+ // node bin/run-oscillation-tlc.cjs --config=MCoscillation
11
+ //
12
+ // Prerequisites:
13
+ // - Java >=17 (https://adoptium.net/)
14
+ // - .planning/formal/tla/tla2tools.jar (see .planning/formal/tla/README.md for download command)
15
+
16
+ const { spawnSync } = require('child_process');
17
+ const JAVA_HEAP_MAX = process.env.QGSD_JAVA_HEAP_MAX || '512m';
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+ const { writeCheckResult } = require('./write-check-result.cjs');
21
+ const { detectLivenessProperties } = require('./run-tlc.cjs');
22
+ const { getRequirementIds } = require('./requirement-map.cjs');
23
+
24
+ // ── Resolve project root (--project-root= overrides __dirname-relative) ─────
25
+ let ROOT = path.join(__dirname, '..');
26
+ for (const arg of process.argv) {
27
+ if (arg.startsWith('--project-root=')) ROOT = path.resolve(arg.slice('--project-root='.length));
28
+ }
29
+
30
+ const CHECK_ID_MAP = {
31
+ 'MCoscillation': 'tla:oscillation',
32
+ 'MCconvergence': 'tla:convergence',
33
+ };
34
+
35
+ const PROPERTY_MAP = {
36
+ 'MCoscillation': 'Run-collapse oscillation detection algorithm correctness',
37
+ 'MCconvergence': 'Haiku convergence — OscillationConvergence liveness property',
38
+ };
39
+
40
+ // ── Parse --config argument ──────────────────────────────────────────────────
41
+ const args = process.argv.slice(2);
42
+ const configArg = args.find(a => a.startsWith('--config=')) || null;
43
+ const configName = configArg
44
+ ? configArg.split('=')[1]
45
+ : (args.find(a => !a.startsWith('-')) || 'MCoscillation');
46
+
47
+ const VALID_CONFIGS = ['MCoscillation', 'MCconvergence'];
48
+ if (!VALID_CONFIGS.includes(configName)) {
49
+ process.stderr.write(
50
+ '[run-oscillation-tlc] Unknown config: ' + configName +
51
+ '. Valid: ' + VALID_CONFIGS.join(', ') + '\n'
52
+ );
53
+ const _startMs = Date.now();
54
+ const _runtimeMs = 0;
55
+ try { writeCheckResult({ tool: 'run-oscillation-tlc', formalism: 'tla', result: 'fail', check_id: CHECK_ID_MAP[configName] || ('tla:' + configName.toLowerCase()), surface: 'tla', property: PROPERTY_MAP[configName] || configName, runtime_ms: _runtimeMs, summary: 'fail: unknown config in ' + _runtimeMs + 'ms', requirement_ids: getRequirementIds(CHECK_ID_MAP[configName] || ('tla:' + configName.toLowerCase())), metadata: { config: configName } }); } catch (e) { process.stderr.write('[run-oscillation-tlc] Warning: failed to write check result: ' + e.message + '\n'); }
56
+ process.exit(1);
57
+ }
58
+
59
+ // ── 1. Locate Java ───────────────────────────────────────────────────────────
60
+ const JAVA_HOME = process.env.JAVA_HOME;
61
+ let javaExe;
62
+
63
+ if (JAVA_HOME) {
64
+ javaExe = path.join(JAVA_HOME, 'bin', 'java');
65
+ if (!fs.existsSync(javaExe)) {
66
+ process.stderr.write(
67
+ '[run-oscillation-tlc] JAVA_HOME is set but java binary not found at: ' + javaExe + '\n' +
68
+ '[run-oscillation-tlc] Unset JAVA_HOME or fix the path.\n'
69
+ );
70
+ const _startMs = Date.now();
71
+ const _runtimeMs = 0;
72
+ try { writeCheckResult({ tool: 'run-oscillation-tlc', formalism: 'tla', result: 'fail', check_id: CHECK_ID_MAP[configName] || ('tla:' + configName.toLowerCase()), surface: 'tla', property: PROPERTY_MAP[configName] || configName, runtime_ms: _runtimeMs, summary: 'fail: Java not found in ' + _runtimeMs + 'ms', requirement_ids: getRequirementIds(CHECK_ID_MAP[configName] || ('tla:' + configName.toLowerCase())), metadata: { config: configName } }); } catch (e) { process.stderr.write('[run-oscillation-tlc] Warning: failed to write check result: ' + e.message + '\n'); }
73
+ process.exit(1);
74
+ }
75
+ } else {
76
+ // Fall back to PATH lookup
77
+ const probe = spawnSync('java', ['--version'], { encoding: 'utf8' });
78
+ if (probe.error || probe.status !== 0) {
79
+ process.stderr.write(
80
+ '[run-oscillation-tlc] Java not found. Install Java >=17 and set JAVA_HOME.\n' +
81
+ '[run-oscillation-tlc] Download: https://adoptium.net/\n'
82
+ );
83
+ const _startMs = Date.now();
84
+ const _runtimeMs = 0;
85
+ try { writeCheckResult({ tool: 'run-oscillation-tlc', formalism: 'tla', result: 'fail', check_id: CHECK_ID_MAP[configName] || ('tla:' + configName.toLowerCase()), surface: 'tla', property: PROPERTY_MAP[configName] || configName, runtime_ms: _runtimeMs, summary: 'fail: Java not found in ' + _runtimeMs + 'ms', requirement_ids: getRequirementIds(CHECK_ID_MAP[configName] || ('tla:' + configName.toLowerCase())), metadata: { config: configName } }); } catch (e) { process.stderr.write('[run-oscillation-tlc] Warning: failed to write check result: ' + e.message + '\n'); }
86
+ process.exit(1);
87
+ }
88
+ javaExe = 'java';
89
+ }
90
+
91
+ // ── 2. Check Java version >=17 ───────────────────────────────────────────────
92
+ const versionResult = spawnSync(javaExe, ['--version'], { encoding: 'utf8' });
93
+ if (versionResult.error || versionResult.status !== 0) {
94
+ process.stderr.write('[run-oscillation-tlc] Failed to run: ' + javaExe + ' --version\n');
95
+ const _startMs = Date.now();
96
+ const _runtimeMs = 0;
97
+ try { writeCheckResult({ tool: 'run-oscillation-tlc', formalism: 'tla', result: 'fail', check_id: CHECK_ID_MAP[configName] || ('tla:' + configName.toLowerCase()), surface: 'tla', property: PROPERTY_MAP[configName] || configName, runtime_ms: _runtimeMs, summary: 'fail: Java version check failed in ' + _runtimeMs + 'ms', requirement_ids: getRequirementIds(CHECK_ID_MAP[configName] || ('tla:' + configName.toLowerCase())), metadata: { config: configName } }); } catch (e) { process.stderr.write('[run-oscillation-tlc] Warning: failed to write check result: ' + e.message + '\n'); }
98
+ process.exit(1);
99
+ }
100
+ const versionOutput = versionResult.stdout + versionResult.stderr;
101
+ // Java version string varies: "openjdk 17.0.1 ..." or "java version \"17.0.1\""
102
+ const versionMatch = versionOutput.match(/(?:openjdk\s+|java version\s+[""]?)(\d+)/i);
103
+ const javaMajor = versionMatch ? parseInt(versionMatch[1], 10) : 0;
104
+ if (javaMajor < 17) {
105
+ process.stderr.write(
106
+ '[run-oscillation-tlc] Java >=17 required. Found: ' + versionOutput.split('\n')[0] + '\n' +
107
+ '[run-oscillation-tlc] Download Java 17+: https://adoptium.net/\n'
108
+ );
109
+ const _startMs = Date.now();
110
+ const _runtimeMs = 0;
111
+ try { writeCheckResult({ tool: 'run-oscillation-tlc', formalism: 'tla', result: 'fail', check_id: CHECK_ID_MAP[configName] || ('tla:' + configName.toLowerCase()), surface: 'tla', property: PROPERTY_MAP[configName] || configName, runtime_ms: _runtimeMs, summary: 'fail: Java ' + javaMajor + ' < 17 in ' + _runtimeMs + 'ms', requirement_ids: getRequirementIds(CHECK_ID_MAP[configName] || ('tla:' + configName.toLowerCase())), metadata: { config: configName } }); } catch (e) { process.stderr.write('[run-oscillation-tlc] Warning: failed to write check result: ' + e.message + '\n'); }
112
+ process.exit(1);
113
+ }
114
+
115
+ // ── 3. Locate tla2tools.jar ──────────────────────────────────────────────────
116
+ const jarPath = path.join(ROOT, '.planning', 'formal', 'tla', 'tla2tools.jar');
117
+ if (!fs.existsSync(jarPath)) {
118
+ process.stderr.write(
119
+ '[run-oscillation-tlc] tla2tools.jar not found at: ' + jarPath + '\n' +
120
+ '[run-oscillation-tlc] Download v1.8.0:\n' +
121
+ ' curl -L https://github.com/tlaplus/tlaplus/releases/download/v1.8.0/tla2tools.jar \\\n' +
122
+ ' -o .planning/formal/tla/tla2tools.jar\n'
123
+ );
124
+ const _startMs = Date.now();
125
+ const _runtimeMs = 0;
126
+ try { writeCheckResult({ tool: 'run-oscillation-tlc', formalism: 'tla', result: 'fail', check_id: CHECK_ID_MAP[configName] || ('tla:' + configName.toLowerCase()), surface: 'tla', property: PROPERTY_MAP[configName] || configName, runtime_ms: _runtimeMs, summary: 'fail: tla2tools.jar not found in ' + _runtimeMs + 'ms', requirement_ids: getRequirementIds(CHECK_ID_MAP[configName] || ('tla:' + configName.toLowerCase())), metadata: { config: configName } }); } catch (e) { process.stderr.write('[run-oscillation-tlc] Warning: failed to write check result: ' + e.message + '\n'); }
127
+ process.exit(1);
128
+ }
129
+
130
+ // ── 4. Resolve spec and config paths ─────────────────────────────────────────
131
+ const specFileName = configName === 'MCoscillation'
132
+ ? 'QGSDOscillation.tla'
133
+ : 'QGSDConvergence.tla';
134
+ const specPath = path.join(ROOT, '.planning', 'formal', 'tla', specFileName);
135
+ const cfgPath = path.join(ROOT, '.planning', 'formal', 'tla', configName + '.cfg');
136
+
137
+ // Both MCoscillation and MCconvergence declare PROPERTY (liveness) clauses in their .cfg files.
138
+ // TLC has a known multi-worker liveness checking bug (v1.8.0) — always use -workers 1.
139
+ // MCconvergence.cfg declares PROPERTY ConvergenceEventuallyResolves; 'auto' workers is NOT safe.
140
+ const workers = '1';
141
+
142
+ // ── 5. Invoke TLC ────────────────────────────────────────────────────────────
143
+ process.stdout.write('[run-oscillation-tlc] Config: ' + configName + ' Workers: ' + workers + '\n');
144
+ process.stdout.write('[run-oscillation-tlc] Spec: ' + specPath + '\n');
145
+ process.stdout.write('[run-oscillation-tlc] Cfg: ' + cfgPath + '\n');
146
+
147
+ const _startMs = Date.now();
148
+ process.stderr.write('[heap] Xms=64m Xmx=' + JAVA_HEAP_MAX + '\n');
149
+ const tlcResult = spawnSync(javaExe, [
150
+ '-Xms64m', '-Xmx' + JAVA_HEAP_MAX,
151
+ '-jar', jarPath,
152
+ '-config', cfgPath,
153
+ '-workers', workers,
154
+ specPath,
155
+ ], { encoding: 'utf8', stdio: 'inherit' });
156
+ const _runtimeMs = Date.now() - _startMs;
157
+
158
+ if (tlcResult.error) {
159
+ process.stderr.write('[run-oscillation-tlc] TLC invocation failed: ' + tlcResult.error.message + '\n');
160
+ const check_id = CHECK_ID_MAP[configName] || ('tla:' + configName.toLowerCase());
161
+ const property = PROPERTY_MAP[configName] || configName;
162
+ try { writeCheckResult({ tool: 'run-oscillation-tlc', formalism: 'tla', result: 'fail', check_id: check_id, surface: 'tla', property: property, runtime_ms: _runtimeMs, summary: 'fail: TLC invocation failed in ' + _runtimeMs + 'ms', requirement_ids: getRequirementIds(check_id), metadata: { config: configName } }); } catch (e) { process.stderr.write('[run-oscillation-tlc] Warning: failed to write check result: ' + e.message + '\n'); }
163
+ process.exit(1);
164
+ }
165
+
166
+ const passed = (tlcResult.status || 0) === 0;
167
+ const check_id = CHECK_ID_MAP[configName] || ('tla:' + configName.toLowerCase());
168
+ const property = PROPERTY_MAP[configName] || configName;
169
+ const triage_tags = _runtimeMs > 120000 ? ['timeout-risk'] : [];
170
+
171
+ if (passed) {
172
+ const missingDeclarations = detectLivenessProperties(configName, cfgPath);
173
+ if (missingDeclarations.length > 0) {
174
+ try {
175
+ writeCheckResult({
176
+ tool: 'run-oscillation-tlc',
177
+ formalism: 'tla',
178
+ result: 'inconclusive',
179
+ check_id: check_id,
180
+ surface: 'tla',
181
+ property: property,
182
+ runtime_ms: _runtimeMs,
183
+ summary: 'inconclusive: fairness missing in ' + _runtimeMs + 'ms',
184
+ triage_tags: ['needs-fairness'],
185
+ requirement_ids: getRequirementIds(check_id),
186
+ metadata: {
187
+ config: configName,
188
+ reason: 'Fairness declaration missing for: ' + missingDeclarations.join(', '),
189
+ }
190
+ });
191
+ } catch (e) {
192
+ process.stderr.write('[run-oscillation-tlc] Warning: failed to write inconclusive result: ' + e.message + '\n');
193
+ }
194
+ process.stdout.write('[run-oscillation-tlc] Result: inconclusive — fairness declaration missing for: ' + missingDeclarations.join(', ') + '\n');
195
+ process.exit(0);
196
+ }
197
+ try { writeCheckResult({ tool: 'run-oscillation-tlc', formalism: 'tla', result: 'pass', check_id: check_id, surface: 'tla', property: property, runtime_ms: _runtimeMs, summary: 'pass: ' + configName + ' in ' + _runtimeMs + 'ms', triage_tags: triage_tags, requirement_ids: getRequirementIds(check_id), metadata: { config: configName } }); } catch (e) { process.stderr.write('[run-oscillation-tlc] Warning: failed to write check result: ' + e.message + '\n'); }
198
+ process.exit(0);
199
+ } else {
200
+ try { writeCheckResult({ tool: 'run-oscillation-tlc', formalism: 'tla', result: 'fail', check_id: check_id, surface: 'tla', property: property, runtime_ms: _runtimeMs, summary: 'fail: ' + configName + ' in ' + _runtimeMs + 'ms', triage_tags: triage_tags, requirement_ids: getRequirementIds(check_id), metadata: { config: configName } }); } catch (e) { process.stderr.write('[run-oscillation-tlc] Warning: failed to write check result: ' + e.message + '\n'); }
201
+ process.exit(tlcResult.status || 0);
202
+ }