thumbgate 1.34.3 → 1.37.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 (200) hide show
  1. package/.agents/skills/cyberstrike-compare-not-clone/SKILL.md +36 -0
  2. package/.agents/skills/gitlab-sandbox-allowlist-not-trust/SKILL.md +77 -0
  3. package/.agents/skills/jit-harness-compare-not-clone/SKILL.md +34 -0
  4. package/.agents/skills/openui-catalog-compose-honesty/SKILL.md +64 -0
  5. package/.agents/skills/zvec-grep-compare-not-clone/SKILL.md +34 -0
  6. package/.claude-plugin/plugin.json +1 -1
  7. package/.well-known/llms.txt +1 -0
  8. package/.well-known/mcp/server-card.json +1 -1
  9. package/CONTRIBUTING.md +95 -0
  10. package/README.md +195 -632
  11. package/THIRD_PARTY_NOTICES.md +89 -0
  12. package/adapters/claude/.mcp.json +2 -2
  13. package/adapters/forge/forge.yaml +3 -3
  14. package/adapters/future-agi/.mcp.json +8 -0
  15. package/adapters/future-agi/FUTURE_AGI.md +23 -0
  16. package/adapters/future-agi/config.toml +3 -0
  17. package/adapters/future-agi/future-agi-bridge.js +9 -0
  18. package/adapters/future-agi/opencode.json +8 -0
  19. package/adapters/herdr/herdr-plugin.toml +18 -0
  20. package/adapters/mcp/server-stdio.js +238 -25
  21. package/adapters/opencode/opencode.json +1 -1
  22. package/adapters/workos/WORKOS.md +52 -0
  23. package/bin/cli.js +373 -5
  24. package/bin/futureagi-bridge +9 -0
  25. package/config/gate-templates.json +653 -4
  26. package/config/gates/actor-critic-audit.json +34 -0
  27. package/config/gates/default.json +21 -2
  28. package/config/gates/five-walls-governance.json +34 -0
  29. package/config/gates/future-agi-guardrails.json +34 -0
  30. package/config/gates/radware-threat-defense-2026.json +61 -0
  31. package/config/gates/simatree-data-governance.json +33 -0
  32. package/config/mcp-allowlists.json +4 -0
  33. package/config/merge-quality-checks.json +10 -1
  34. package/config/model-candidates.json +382 -24
  35. package/config/model-tiers.json +18 -0
  36. package/config/post-deploy-marketing-pages.json +10 -0
  37. package/config/progressive/01-wire-only.json +11 -0
  38. package/config/progressive/02-dashboard-empty-ok.json +10 -0
  39. package/config/progressive/03-one-lesson.json +10 -0
  40. package/config/progressive/04-warn-fires.json +11 -0
  41. package/config/progressive/05-strict-optional.json +11 -0
  42. package/config/progressive/README.md +15 -0
  43. package/config/schemas/broker-execution-receipt.schema.json +139 -0
  44. package/config/schemas/provider-execution-attestation-v1.schema.json +58 -0
  45. package/conformance/provider-attestation/vectors.json +320 -0
  46. package/docs/specs/provider-execution-attestation-v1.md +69 -0
  47. package/openapi/openapi.yaml +15 -0
  48. package/package.json +401 -147
  49. package/public/about.html +2 -2
  50. package/public/ai-malpractice-prevention.html +7 -7
  51. package/public/blog/a-10-dollar-vps-is-not-a-computer.html +143 -0
  52. package/public/blog/a-receipt-is-not-world-state.html +388 -0
  53. package/public/blog/git-at-agent-scale.html +374 -0
  54. package/public/blog/no-llm-in-the-gate.html +133 -0
  55. package/public/blog.html +80 -0
  56. package/public/case-studies.html +16 -1
  57. package/public/compare.html +28 -0
  58. package/public/diagnostic.html +216 -7
  59. package/public/docs/connectors.html +39 -0
  60. package/public/federal.html +2 -2
  61. package/public/founders.html +639 -0
  62. package/public/index.html +87 -9
  63. package/public/install.html +8 -8
  64. package/public/learn.html +39 -0
  65. package/public/numbers.html +2 -2
  66. package/public/peter.html +310 -0
  67. package/public/platform-partners.html +119 -0
  68. package/public/pricing.html +24 -3
  69. package/public/privacy.html +117 -0
  70. package/public/pro.html +17 -0
  71. package/public/support.html +62 -0
  72. package/public/terms.html +130 -0
  73. package/public/third-party-notices.html +95 -0
  74. package/public/yt.html +351 -0
  75. package/scripts/action-receipts.js +133 -3
  76. package/scripts/adaptive-governance-arena.js +349 -0
  77. package/scripts/admin-override.js +205 -0
  78. package/scripts/agent-action-inventory.js +869 -0
  79. package/scripts/agent-audit-trace.js +42 -2
  80. package/scripts/agent-egress-policy.js +1117 -0
  81. package/scripts/agent-memory-lifecycle.js +141 -2
  82. package/scripts/agent-operations-planner.js +441 -1
  83. package/scripts/agent-readiness.js +68 -0
  84. package/scripts/agent-security-central.js +647 -0
  85. package/scripts/allowlist-bridge-honesty.js +417 -0
  86. package/scripts/async-job-runner.js +102 -11
  87. package/scripts/audit-trail.js +212 -0
  88. package/scripts/auto-promote-gates.js +178 -27
  89. package/scripts/billing.js +1 -1
  90. package/scripts/broker-execution-receipts.js +719 -0
  91. package/scripts/budget-aware-gates-proof.js +423 -0
  92. package/scripts/claude-feedback-sync.js +29 -3
  93. package/scripts/claw-harness-production.js +237 -0
  94. package/scripts/cli-progress.js +111 -0
  95. package/scripts/cli-schema.js +163 -1
  96. package/scripts/codex-runbook-flywheel.js +318 -0
  97. package/scripts/context-footprint.js +186 -0
  98. package/scripts/contextfs.js +143 -61
  99. package/scripts/dashboard-limits.js +27 -0
  100. package/scripts/dashboard.js +279 -9
  101. package/scripts/deepseek-v4-runtime-guardrails.js +72 -6
  102. package/scripts/docker-sandbox-planner.js +18 -0
  103. package/scripts/double-blind-eval-protocol.js +252 -0
  104. package/scripts/edotenv-rl-gateway.js +259 -0
  105. package/scripts/ensure-production-search-corpus.js +162 -0
  106. package/scripts/eval-holdout.js +311 -0
  107. package/scripts/feedback-aggregate.js +21 -2
  108. package/scripts/feedback-loop.js +87 -5
  109. package/scripts/feedback-quality.js +9 -0
  110. package/scripts/file-ledger-lock.js +4 -1
  111. package/scripts/financial-control-plane.js +41 -1
  112. package/scripts/find-dormant-requires.js +118 -0
  113. package/scripts/fs-utils.js +84 -8
  114. package/scripts/gate-stats.js +2 -2
  115. package/scripts/gates-engine.js +859 -58
  116. package/scripts/generate-case-study-outreach.js +24 -15
  117. package/scripts/git-at-scale.js +628 -0
  118. package/scripts/governance-conflict-audit.js +1650 -0
  119. package/scripts/governance-difficulty-curriculum.js +328 -0
  120. package/scripts/graphrag-retrieval.js +275 -0
  121. package/scripts/gurobi-optimizer.js +324 -0
  122. package/scripts/gurobi_optimizer.py +485 -0
  123. package/scripts/harness-selector.js +82 -1
  124. package/scripts/hidden-entry-points.js +284 -0
  125. package/scripts/human-escalation.js +199 -1
  126. package/scripts/hybrid-feedback-context.js +152 -19
  127. package/scripts/intent-governed-execution.js +602 -0
  128. package/scripts/intervention-policy.js +123 -20
  129. package/scripts/jit-harness-compose.js +628 -0
  130. package/scripts/jsonl-watcher.js +10 -0
  131. package/scripts/lesson-embedding-index.js +95 -12
  132. package/scripts/lesson-retrieval.js +105 -19
  133. package/scripts/local-model-profile.js +19 -2
  134. package/scripts/mailer/resend-mailer.js +1 -1
  135. package/scripts/matryoshka-embedding.js +235 -0
  136. package/scripts/mcp-oauth.js +42 -4
  137. package/scripts/mcp-session-handles.js +1016 -0
  138. package/scripts/mcp-wiring-doctor.js +314 -0
  139. package/scripts/memory-firewall.js +115 -2
  140. package/scripts/memory-scope-readiness.js +299 -0
  141. package/scripts/memory-vs-rag-route.js +161 -0
  142. package/scripts/model-tier-router.js +148 -21
  143. package/scripts/nvidia-specdecode-al-doctor.js +536 -0
  144. package/scripts/openui-catalog-compose-honesty.js +593 -0
  145. package/scripts/operational-integrity.js +19 -1
  146. package/scripts/override-audit.js +213 -0
  147. package/scripts/package-manager-honesty-doctor.js +458 -0
  148. package/scripts/pr-manager.js +63 -1
  149. package/scripts/prove-herdr-adapter.js +52 -0
  150. package/scripts/prove-memory-pyramid-and-symbolic-canvas.js +95 -0
  151. package/scripts/prove-workos.js +73 -0
  152. package/scripts/provider-attestation-conformance.js +192 -0
  153. package/scripts/provider-receipt-contract.js +136 -0
  154. package/scripts/qwen38-max-cost-optimizer.js +401 -0
  155. package/scripts/radware-threat-defense.js +280 -0
  156. package/scripts/rag-embedding-identity.js +221 -0
  157. package/scripts/rag-precision-guardrails.js +112 -2
  158. package/scripts/remote-feedback-capture.js +159 -0
  159. package/scripts/research-agent-harness.js +256 -0
  160. package/scripts/rsi-safety-hillclimb.js +200 -0
  161. package/scripts/rule-sprawl.js +188 -0
  162. package/scripts/schedule-manager.js +147 -0
  163. package/scripts/self-heal.js +8 -0
  164. package/scripts/session-lease.js +415 -0
  165. package/scripts/simatree-data-governance.js +347 -0
  166. package/scripts/slo-alert-engine.js +172 -7
  167. package/scripts/solver-parity.js +539 -0
  168. package/scripts/stealth-memory-injection-gate.js +333 -0
  169. package/scripts/switchyard-router.js +366 -0
  170. package/scripts/telemetry-analytics.js +84 -27
  171. package/scripts/temporal-decay-weighting.js +138 -0
  172. package/scripts/test-all.js +165 -0
  173. package/scripts/token-savings.js +42 -0
  174. package/scripts/tool-kpi-tracker.js +108 -5
  175. package/scripts/tool-registry.js +193 -5
  176. package/scripts/universal-claim-evaluator.js +14 -2
  177. package/scripts/vector-store.js +279 -9
  178. package/scripts/workflow-notebook.js +391 -0
  179. package/scripts/workflow-sentinel.js +111 -12
  180. package/scripts/workos-production-guard.js +260 -0
  181. package/scripts/workspace-search-route.js +515 -0
  182. package/server.json +2 -2
  183. package/src/agent-identity-boundary.js +76 -0
  184. package/src/agent-retrieval-cache.js +155 -0
  185. package/src/alert-noise-ledger.js +502 -0
  186. package/src/api/server.js +802 -185
  187. package/src/git-fast-cache.js +220 -0
  188. package/src/git-wal-sync.js +156 -0
  189. package/src/hash-anchored-edit.js +82 -0
  190. package/src/hermes-platform-protocol.js +475 -0
  191. package/src/hermes-sync-plane.js +241 -0
  192. package/src/index.js +30 -1
  193. package/src/iso42001-compliance-guard.js +97 -0
  194. package/src/latency-budget.js +244 -0
  195. package/src/mcp-writeguard.js +316 -0
  196. package/src/miminions-adapter.js +106 -0
  197. package/src/pipeline-compass.js +104 -0
  198. package/src/ppl-alert-pipeline.js +284 -0
  199. package/src/rendezvous-router.js +90 -0
  200. package/src/security-questionnaire.js +195 -0
@@ -0,0 +1,349 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const crypto = require('node:crypto');
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const { buildRewardHackingGuardrailsPlan } = require('./reward-hacking-guardrails');
8
+
9
+ const ROOT = path.join(__dirname, '..');
10
+ const DEFAULT_ARENA_PATH = path.join(ROOT, 'bench', 'adaptive-governance-arena.json');
11
+ const GENESIS_HASH = '0'.repeat(64);
12
+ const ACTIONS = new Set(['allow', 'deny', 'review']);
13
+
14
+ function stableStringify(value) {
15
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
16
+ if (value && typeof value === 'object') {
17
+ const entries = Object.keys(value)
18
+ .sort((left, right) => left.localeCompare(right))
19
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`);
20
+ return `{${entries.join(',')}}`;
21
+ }
22
+ return JSON.stringify(value);
23
+ }
24
+
25
+ function sha256(value) {
26
+ return crypto.createHash('sha256').update(value).digest('hex');
27
+ }
28
+
29
+ function assertObject(value, label) {
30
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
31
+ throw new Error(`${label} must be an object`);
32
+ }
33
+ }
34
+
35
+ function loadArena(filePath = DEFAULT_ARENA_PATH) {
36
+ const arena = JSON.parse(fs.readFileSync(filePath, 'utf8'));
37
+ assertObject(arena, 'Arena');
38
+ if (!Array.isArray(arena.levels) || arena.levels.length === 0) {
39
+ throw new Error('Arena must define a non-empty levels array');
40
+ }
41
+
42
+ const levelIds = new Set();
43
+ const scenarioIds = new Set();
44
+ let previousDifficulty = 0;
45
+ const levels = arena.levels.map((level, levelIndex) => {
46
+ assertObject(level, `Level ${levelIndex + 1}`);
47
+ if (!level.id || levelIds.has(level.id)) throw new Error(`Level ${levelIndex + 1} must have a unique id`);
48
+ levelIds.add(level.id);
49
+ if (!Number.isInteger(level.difficulty) || level.difficulty <= previousDifficulty) {
50
+ throw new Error(`Level ${level.id} difficulty must increase monotonically`);
51
+ }
52
+ previousDifficulty = level.difficulty;
53
+ if (!Array.isArray(level.scenarios) || level.scenarios.length === 0) {
54
+ throw new Error(`Level ${level.id} must define scenarios`);
55
+ }
56
+
57
+ return {
58
+ ...level,
59
+ scenarios: level.scenarios.map((scenario, scenarioIndex) => {
60
+ assertObject(scenario, `Scenario ${level.id}/${scenarioIndex + 1}`);
61
+ if (!scenario.id || scenarioIds.has(scenario.id)) {
62
+ throw new Error(`Scenario ${level.id}/${scenarioIndex + 1} must have a globally unique id`);
63
+ }
64
+ scenarioIds.add(scenario.id);
65
+ if (!ACTIONS.has(scenario.expectedAction)) {
66
+ throw new Error(`Scenario ${scenario.id} has invalid expectedAction`);
67
+ }
68
+ assertObject(scenario.context, `Scenario ${scenario.id} context`);
69
+ return scenario;
70
+ }),
71
+ };
72
+ });
73
+
74
+ return {
75
+ version: arena.version || 1,
76
+ name: arena.name || 'ThumbGate Adaptive Governance Arena',
77
+ seed: String(arena.seed || 'thumbgate-arena-v1'),
78
+ sourcePath: filePath,
79
+ levels,
80
+ };
81
+ }
82
+
83
+ function evaluatePolicy(scenario) {
84
+ const context = scenario.context;
85
+ const reasons = [];
86
+ let action = 'allow';
87
+
88
+ if (context.environment === 'live' && context.marketAction) {
89
+ action = 'deny';
90
+ reasons.push('live_market_boundary');
91
+ }
92
+ if (context.dynamicToolCreation && !context.toolApproved) {
93
+ action = 'deny';
94
+ reasons.push('unapproved_dynamic_tool');
95
+ }
96
+ if (context.sensitiveData && context.hybridRoute === 'cloud') {
97
+ action = 'deny';
98
+ reasons.push('sensitive_cloud_route');
99
+ }
100
+ if (context.agentIdentity && context.credentialOwner
101
+ && context.agentIdentity === context.credentialOwner) {
102
+ action = 'deny';
103
+ reasons.push('identity_separation_required');
104
+ }
105
+ if (Number.isFinite(context.estimatedCostUsd)
106
+ && Number.isFinite(context.budgetLimitUsd)
107
+ && context.estimatedCostUsd > context.budgetLimitUsd) {
108
+ action = 'deny';
109
+ reasons.push('budget_exceeded');
110
+ }
111
+ if (context.irreversible && !context.approvalReceipt) {
112
+ if (action === 'allow') action = 'review';
113
+ reasons.push('approval_receipt_required');
114
+ }
115
+
116
+ const rewardPlan = buildRewardHackingGuardrailsPlan({
117
+ workflow: scenario.id,
118
+ text: context.candidateText || '',
119
+ evidence: context.evidence || [],
120
+ metrics: context.metrics || [],
121
+ hasHoldout: context.hasHoldout,
122
+ hasHumanObjective: context.hasHumanObjective,
123
+ optimizedForScore: context.optimizedForScore,
124
+ });
125
+ if (rewardPlan.status === 'blocked') {
126
+ action = 'deny';
127
+ reasons.push(...rewardPlan.signals.map((signal) => signal.id));
128
+ } else if (rewardPlan.status === 'needs_evidence' && action === 'allow') {
129
+ action = 'review';
130
+ reasons.push(...rewardPlan.signals.map((signal) => signal.id));
131
+ }
132
+
133
+ return {
134
+ action,
135
+ reasons: [...new Set(reasons)].sort((left, right) => left.localeCompare(right)),
136
+ rewardHackingStatus: rewardPlan.status,
137
+ rewardSignals: rewardPlan.signals.map((signal) => signal.id).sort(),
138
+ };
139
+ }
140
+
141
+ function createReceipt(previousHash, arena, level, scenario, decision) {
142
+ const body = {
143
+ arena: arena.name,
144
+ arenaVersion: arena.version,
145
+ seed: arena.seed,
146
+ levelId: level.id,
147
+ difficulty: level.difficulty,
148
+ scenarioId: scenario.id,
149
+ expectedAction: scenario.expectedAction,
150
+ actualAction: decision.action,
151
+ passed: decision.action === scenario.expectedAction,
152
+ reasons: decision.reasons,
153
+ previousHash,
154
+ };
155
+ return { ...body, hash: sha256(stableStringify(body)) };
156
+ }
157
+
158
+ function runArenaPass(arena) {
159
+ const receipts = [];
160
+ const levels = [];
161
+ let previousHash = GENESIS_HASH;
162
+ let curriculumOpen = true;
163
+
164
+ for (const level of arena.levels) {
165
+ if (!curriculumOpen) {
166
+ levels.push({
167
+ id: level.id,
168
+ difficulty: level.difficulty,
169
+ status: 'locked',
170
+ passed: false,
171
+ scenarios: [],
172
+ });
173
+ continue;
174
+ }
175
+
176
+ const scenarios = level.scenarios.map((scenario) => {
177
+ const decision = evaluatePolicy(scenario);
178
+ const receipt = createReceipt(previousHash, arena, level, scenario, decision);
179
+ previousHash = receipt.hash;
180
+ receipts.push(receipt);
181
+ return {
182
+ id: scenario.id,
183
+ intent: scenario.intent,
184
+ expectedAction: scenario.expectedAction,
185
+ actualAction: decision.action,
186
+ reasons: decision.reasons,
187
+ rewardHackingStatus: decision.rewardHackingStatus,
188
+ passed: decision.action === scenario.expectedAction,
189
+ receiptHash: receipt.hash,
190
+ };
191
+ });
192
+ const passed = scenarios.every((scenario) => scenario.passed);
193
+ levels.push({
194
+ id: level.id,
195
+ difficulty: level.difficulty,
196
+ status: passed ? 'passed' : 'failed',
197
+ passed,
198
+ scenarios,
199
+ });
200
+ curriculumOpen = passed;
201
+ }
202
+
203
+ return {
204
+ arena: arena.name,
205
+ version: arena.version,
206
+ seed: arena.seed,
207
+ passed: levels.every((level) => level.passed),
208
+ highestPassedDifficulty: Math.max(0, ...levels.filter((level) => level.passed).map((level) => level.difficulty)),
209
+ levels,
210
+ receipts,
211
+ receiptChainHead: previousHash,
212
+ };
213
+ }
214
+
215
+ function verifyReceiptChain(report) {
216
+ const scenarioResults = new Map();
217
+ for (const level of report.levels || []) {
218
+ for (const scenario of level.scenarios || []) {
219
+ scenarioResults.set(scenario.id, scenario);
220
+ }
221
+ }
222
+ let previousHash = GENESIS_HASH;
223
+ for (const receipt of report.receipts || []) {
224
+ const { hash, ...body } = receipt;
225
+ if (body.previousHash !== previousHash) return false;
226
+ if (sha256(stableStringify(body)) !== hash) return false;
227
+ const result = scenarioResults.get(body.scenarioId);
228
+ if (!result
229
+ || result.receiptHash !== hash
230
+ || result.expectedAction !== body.expectedAction
231
+ || result.actualAction !== body.actualAction
232
+ || result.passed !== body.passed
233
+ || stableStringify(result.reasons) !== stableStringify(body.reasons)) {
234
+ return false;
235
+ }
236
+ scenarioResults.delete(body.scenarioId);
237
+ previousHash = hash;
238
+ }
239
+ return scenarioResults.size === 0 && previousHash === report.receiptChainHead;
240
+ }
241
+
242
+ function buildDpoCandidates(report, arena) {
243
+ const scenarios = new Map();
244
+ for (const level of arena.levels) {
245
+ for (const scenario of level.scenarios) scenarios.set(scenario.id, scenario);
246
+ }
247
+ return report.levels.flatMap((level) => level.scenarios)
248
+ .filter((result) => !result.passed)
249
+ .filter((result) => {
250
+ const scenario = scenarios.get(result.id);
251
+ return Boolean(scenario && scenario.preferredResponse && scenario.rejectedResponse);
252
+ })
253
+ .map((result) => {
254
+ const scenario = scenarios.get(result.id);
255
+ return {
256
+ prompt: scenario.context.candidateText || scenario.intent,
257
+ chosen: scenario.preferredResponse,
258
+ rejected: scenario.rejectedResponse,
259
+ metadata: {
260
+ arena: report.arena,
261
+ scenarioId: result.id,
262
+ difficulty: levelDifficulty(report, result.id),
263
+ expectedAction: result.expectedAction,
264
+ actualAction: result.actualAction,
265
+ receiptHash: result.receiptHash,
266
+ },
267
+ };
268
+ });
269
+ }
270
+
271
+ function levelDifficulty(report, scenarioId) {
272
+ const level = report.levels.find((candidate) => candidate.scenarios.some((scenario) => scenario.id === scenarioId));
273
+ return level ? level.difficulty : null;
274
+ }
275
+
276
+ function replayArena(arena) {
277
+ const first = runArenaPass(arena);
278
+ const second = runArenaPass(arena);
279
+ return {
280
+ stable: stableStringify(first) === stableStringify(second),
281
+ first,
282
+ second,
283
+ };
284
+ }
285
+
286
+ function summarize(report) {
287
+ const scenarios = report.levels.flatMap((level) => level.scenarios);
288
+ const passed = scenarios.filter((scenario) => scenario.passed).length;
289
+ return {
290
+ scenarioCount: scenarios.length,
291
+ passedScenarioCount: passed,
292
+ passRate: scenarios.length ? passed / scenarios.length : 0,
293
+ highestPassedDifficulty: report.highestPassedDifficulty,
294
+ receiptChainValid: verifyReceiptChain(report),
295
+ };
296
+ }
297
+
298
+ function parseArgs(argv = process.argv.slice(2)) {
299
+ const options = { arenaPath: DEFAULT_ARENA_PATH, json: false, outPath: null };
300
+ for (const arg of argv) {
301
+ if (arg === '--json') options.json = true;
302
+ else if (arg.startsWith('--arena=')) options.arenaPath = path.resolve(arg.slice('--arena='.length));
303
+ else if (arg.startsWith('--out=')) options.outPath = path.resolve(arg.slice('--out='.length));
304
+ else throw new Error(`Unknown argument: ${arg}`);
305
+ }
306
+ return options;
307
+ }
308
+
309
+ function main(argv = process.argv.slice(2)) {
310
+ const options = parseArgs(argv);
311
+ const arena = loadArena(options.arenaPath);
312
+ const replay = replayArena(arena);
313
+ const report = {
314
+ ...replay.first,
315
+ replayStable: replay.stable,
316
+ metrics: summarize(replay.first),
317
+ dpoCandidates: buildDpoCandidates(replay.first, arena),
318
+ };
319
+ if (options.outPath) {
320
+ fs.mkdirSync(path.dirname(options.outPath), { recursive: true });
321
+ fs.writeFileSync(options.outPath, `${JSON.stringify(report, null, 2)}\n`);
322
+ }
323
+ if (options.json) console.log(JSON.stringify(report, null, 2));
324
+ else {
325
+ console.log(`Adaptive Governance Arena: ${report.passed ? 'PASS' : 'FAIL'}`);
326
+ console.log(`Scenarios: ${report.metrics.passedScenarioCount}/${report.metrics.scenarioCount}`);
327
+ console.log(`Highest difficulty: ${report.metrics.highestPassedDifficulty}`);
328
+ console.log(`Replay stable: ${report.replayStable}`);
329
+ console.log(`Receipt chain valid: ${report.metrics.receiptChainValid}`);
330
+ }
331
+ if (!report.passed || !report.replayStable || !report.metrics.receiptChainValid) process.exitCode = 1;
332
+ return report;
333
+ }
334
+
335
+ module.exports = {
336
+ DEFAULT_ARENA_PATH,
337
+ GENESIS_HASH,
338
+ buildDpoCandidates,
339
+ createReceipt,
340
+ evaluatePolicy,
341
+ loadArena,
342
+ replayArena,
343
+ runArenaPass,
344
+ stableStringify,
345
+ summarize,
346
+ verifyReceiptChain,
347
+ };
348
+
349
+ if (process.argv[1] && path.resolve(process.argv[1]) === __filename) main();
@@ -0,0 +1,205 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * admin-override.js — a governed way for the operator to unlock a gate.
5
+ *
6
+ * DESIGN HONESTY
7
+ * --------------
8
+ * An agent running in the operator's shell can already do anything the operator
9
+ * can. So this module does NOT pretend to be a wall that stops a determined
10
+ * agent. Its job is to make every override **loud, attributed, time-boxed, and
11
+ * permanently recorded**. Accountability, not prevention — which is the same
12
+ * thesis the product sells: receipts at the boundary, not a taller fence.
13
+ *
14
+ * Three properties it does enforce, because each one failed in practice:
15
+ *
16
+ * 1. NO BLANKET GRANTS. A grant names exactly one gate. There is no "*".
17
+ * A blanket override is indistinguishable from turning the product off.
18
+ * 2. ALWAYS EXPIRES. A grant without a deadline becomes permanent by
19
+ * forgetting, which is how controls die quietly.
20
+ * 3. SELF-PROTECT GATES NEED EXPLICIT ACKNOWLEDGEMENT. Overriding the gates
21
+ * that protect the enforcement machinery itself is legitimate for an owner
22
+ * but must never be a side effect of routine work.
23
+ */
24
+
25
+ const fs = require('node:fs');
26
+ const path = require('node:path');
27
+ const os = require('node:os');
28
+
29
+ const { recordOverride } = require('./override-audit');
30
+
31
+ /** Grants live beside the other runtime state, not in tracked config. */
32
+ function grantStorePath() {
33
+ const base = process.env.THUMBGATE_FEEDBACK_DIR
34
+ || path.join(os.homedir(), '.thumbgate');
35
+ return path.join(base, 'admin-overrides.json');
36
+ }
37
+
38
+ /** Hard ceiling. A grant that outlives the task it was for is a standing hole. */
39
+ const MAX_TTL_MS = 24 * 60 * 60 * 1000; // 24h
40
+ const DEFAULT_TTL_MS = 60 * 60 * 1000; // 1h
41
+
42
+ /**
43
+ * Gates that protect the enforcement mechanism itself. Overriding these is the
44
+ * difference between "unblock my work" and "disable the product", so they
45
+ * require the caller to say so out loud.
46
+ */
47
+ const SELF_PROTECT_PREFIXES = Object.freeze([
48
+ 'self-protect',
49
+ 'never-bypass',
50
+ 'branch-protection',
51
+ 'secret',
52
+ ]);
53
+
54
+ function isSelfProtectGate(gateId) {
55
+ const lower = String(gateId || '').toLowerCase();
56
+ return SELF_PROTECT_PREFIXES.some((p) => lower.includes(p));
57
+ }
58
+
59
+ function readStore() {
60
+ try {
61
+ const raw = fs.readFileSync(grantStorePath(), 'utf8');
62
+ const parsed = JSON.parse(raw);
63
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
64
+ } catch {
65
+ return {}; // absent or corrupt store means "no active grants", never a crash
66
+ }
67
+ }
68
+
69
+ function writeStore(store) {
70
+ const p = grantStorePath();
71
+ fs.mkdirSync(path.dirname(p), { recursive: true });
72
+ fs.writeFileSync(p, `${JSON.stringify(store, null, 2)}\n`);
73
+ }
74
+
75
+ /**
76
+ * Grant a time-boxed override for exactly one gate.
77
+ *
78
+ * @param {object} params
79
+ * @param {string} params.gateId exact gate id; wildcards refused
80
+ * @param {string} params.grantedBy who authorized it (a person)
81
+ * @param {string} params.reason why — required, non-trivial
82
+ * @param {number} [params.ttlMs] defaults 1h, capped at 24h
83
+ * @param {boolean} [params.acknowledgeSelfProtect] required for self-protect gates
84
+ * @returns {{ok: boolean, error?: string, grant?: object}}
85
+ */
86
+ function grantOverride(params = {}) {
87
+ const gateId = typeof params.gateId === 'string' ? params.gateId.trim() : '';
88
+ if (!gateId) return { ok: false, error: 'gateId is required' };
89
+ if (gateId === '*' || gateId.includes('*')) {
90
+ return { ok: false, error: 'blanket overrides are refused; name exactly one gate' };
91
+ }
92
+
93
+ const grantedBy = typeof params.grantedBy === 'string' ? params.grantedBy.trim() : '';
94
+ if (!grantedBy) return { ok: false, error: 'grantedBy is required (name a person)' };
95
+
96
+ const reason = typeof params.reason === 'string' ? params.reason.trim() : '';
97
+ // A one-word reason is not a reason. This is the field a reviewer reads later.
98
+ if (reason.length < 12) {
99
+ return { ok: false, error: 'reason must be a real sentence (>= 12 chars)' };
100
+ }
101
+
102
+ if (isSelfProtectGate(gateId) && params.acknowledgeSelfProtect !== true) {
103
+ return {
104
+ ok: false,
105
+ error:
106
+ `${gateId} protects the enforcement mechanism itself. `
107
+ + 'Pass acknowledgeSelfProtect: true to override it deliberately.',
108
+ };
109
+ }
110
+
111
+ const ttlMs = Math.min(
112
+ Number.isFinite(params.ttlMs) && params.ttlMs > 0 ? params.ttlMs : DEFAULT_TTL_MS,
113
+ MAX_TTL_MS,
114
+ );
115
+ const now = Date.now();
116
+ const grant = {
117
+ gateId,
118
+ grantedBy,
119
+ reason,
120
+ grantedAt: new Date(now).toISOString(),
121
+ expiresAt: new Date(now + ttlMs).toISOString(),
122
+ ttlMs,
123
+ selfProtect: isSelfProtectGate(gateId),
124
+ };
125
+
126
+ const store = readStore();
127
+ store[gateId] = grant;
128
+ writeStore(store);
129
+
130
+ // The receipt is the point. A grant that is not recorded did not happen.
131
+ recordOverride({
132
+ gateId,
133
+ source: 'break-glass',
134
+ actor: grantedBy,
135
+ reason,
136
+ evidence: `admin grant, expires ${grant.expiresAt}`
137
+ + (grant.selfProtect ? ' [SELF-PROTECT GATE — acknowledged]' : ''),
138
+ ttlMs,
139
+ });
140
+
141
+ return { ok: true, grant };
142
+ }
143
+
144
+ /** True only while an unexpired grant exists for this exact gate. */
145
+ function isOverrideActive(gateId) {
146
+ if (!gateId) return false;
147
+ const grant = readStore()[gateId];
148
+ if (!grant || !grant.expiresAt) return false;
149
+ const expiry = Date.parse(grant.expiresAt);
150
+ return Number.isFinite(expiry) && expiry > Date.now();
151
+ }
152
+
153
+ /** Active grants only. Expired entries are dropped from the returned view. */
154
+ function listActiveOverrides() {
155
+ const store = readStore();
156
+ const now = Date.now();
157
+ return Object.values(store).filter((g) => {
158
+ const expiry = Date.parse(g && g.expiresAt);
159
+ return Number.isFinite(expiry) && expiry > now;
160
+ });
161
+ }
162
+
163
+ /** Remove expired grants from disk. Returns how many were reaped. */
164
+ function expireOverrides() {
165
+ const store = readStore();
166
+ const now = Date.now();
167
+ let reaped = 0;
168
+ for (const [gateId, grant] of Object.entries(store)) {
169
+ const expiry = Date.parse(grant && grant.expiresAt);
170
+ if (!Number.isFinite(expiry) || expiry <= now) {
171
+ delete store[gateId];
172
+ reaped += 1;
173
+ }
174
+ }
175
+ if (reaped > 0) writeStore(store);
176
+ return reaped;
177
+ }
178
+
179
+ /** Revoke a grant early. Recorded, because revocation is also an audit event. */
180
+ function revokeOverride(gateId, revokedBy = 'operator') {
181
+ const store = readStore();
182
+ if (!store[gateId]) return { ok: false, error: 'no such grant' };
183
+ delete store[gateId];
184
+ writeStore(store);
185
+ recordOverride({
186
+ gateId,
187
+ source: 'break-glass',
188
+ actor: revokedBy,
189
+ reason: 'grant revoked before expiry',
190
+ });
191
+ return { ok: true };
192
+ }
193
+
194
+ module.exports = {
195
+ grantOverride,
196
+ isOverrideActive,
197
+ listActiveOverrides,
198
+ expireOverrides,
199
+ revokeOverride,
200
+ isSelfProtectGate,
201
+ grantStorePath,
202
+ MAX_TTL_MS,
203
+ DEFAULT_TTL_MS,
204
+ SELF_PROTECT_PREFIXES,
205
+ };