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.
- package/.agents/skills/cyberstrike-compare-not-clone/SKILL.md +36 -0
- package/.agents/skills/gitlab-sandbox-allowlist-not-trust/SKILL.md +77 -0
- package/.agents/skills/jit-harness-compare-not-clone/SKILL.md +34 -0
- package/.agents/skills/openui-catalog-compose-honesty/SKILL.md +64 -0
- package/.agents/skills/zvec-grep-compare-not-clone/SKILL.md +34 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/llms.txt +1 -0
- package/.well-known/mcp/server-card.json +1 -1
- package/CONTRIBUTING.md +95 -0
- package/README.md +195 -632
- package/THIRD_PARTY_NOTICES.md +89 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/forge/forge.yaml +3 -3
- package/adapters/future-agi/.mcp.json +8 -0
- package/adapters/future-agi/FUTURE_AGI.md +23 -0
- package/adapters/future-agi/config.toml +3 -0
- package/adapters/future-agi/future-agi-bridge.js +9 -0
- package/adapters/future-agi/opencode.json +8 -0
- package/adapters/herdr/herdr-plugin.toml +18 -0
- package/adapters/mcp/server-stdio.js +238 -25
- package/adapters/opencode/opencode.json +1 -1
- package/adapters/workos/WORKOS.md +52 -0
- package/bin/cli.js +373 -5
- package/bin/futureagi-bridge +9 -0
- package/config/gate-templates.json +653 -4
- package/config/gates/actor-critic-audit.json +34 -0
- package/config/gates/default.json +21 -2
- package/config/gates/five-walls-governance.json +34 -0
- package/config/gates/future-agi-guardrails.json +34 -0
- package/config/gates/radware-threat-defense-2026.json +61 -0
- package/config/gates/simatree-data-governance.json +33 -0
- package/config/mcp-allowlists.json +4 -0
- package/config/merge-quality-checks.json +10 -1
- package/config/model-candidates.json +382 -24
- package/config/model-tiers.json +18 -0
- package/config/post-deploy-marketing-pages.json +10 -0
- package/config/progressive/01-wire-only.json +11 -0
- package/config/progressive/02-dashboard-empty-ok.json +10 -0
- package/config/progressive/03-one-lesson.json +10 -0
- package/config/progressive/04-warn-fires.json +11 -0
- package/config/progressive/05-strict-optional.json +11 -0
- package/config/progressive/README.md +15 -0
- package/config/schemas/broker-execution-receipt.schema.json +139 -0
- package/config/schemas/provider-execution-attestation-v1.schema.json +58 -0
- package/conformance/provider-attestation/vectors.json +320 -0
- package/docs/specs/provider-execution-attestation-v1.md +69 -0
- package/openapi/openapi.yaml +15 -0
- package/package.json +401 -147
- package/public/about.html +2 -2
- package/public/ai-malpractice-prevention.html +7 -7
- package/public/blog/a-10-dollar-vps-is-not-a-computer.html +143 -0
- package/public/blog/a-receipt-is-not-world-state.html +388 -0
- package/public/blog/git-at-agent-scale.html +374 -0
- package/public/blog/no-llm-in-the-gate.html +133 -0
- package/public/blog.html +80 -0
- package/public/case-studies.html +16 -1
- package/public/compare.html +28 -0
- package/public/diagnostic.html +216 -7
- package/public/docs/connectors.html +39 -0
- package/public/federal.html +2 -2
- package/public/founders.html +639 -0
- package/public/index.html +87 -9
- package/public/install.html +8 -8
- package/public/learn.html +39 -0
- package/public/numbers.html +2 -2
- package/public/peter.html +310 -0
- package/public/platform-partners.html +119 -0
- package/public/pricing.html +24 -3
- package/public/privacy.html +117 -0
- package/public/pro.html +17 -0
- package/public/support.html +62 -0
- package/public/terms.html +130 -0
- package/public/third-party-notices.html +95 -0
- package/public/yt.html +351 -0
- package/scripts/action-receipts.js +133 -3
- package/scripts/adaptive-governance-arena.js +349 -0
- package/scripts/admin-override.js +205 -0
- package/scripts/agent-action-inventory.js +869 -0
- package/scripts/agent-audit-trace.js +42 -2
- package/scripts/agent-egress-policy.js +1117 -0
- package/scripts/agent-memory-lifecycle.js +141 -2
- package/scripts/agent-operations-planner.js +441 -1
- package/scripts/agent-readiness.js +68 -0
- package/scripts/agent-security-central.js +647 -0
- package/scripts/allowlist-bridge-honesty.js +417 -0
- package/scripts/async-job-runner.js +102 -11
- package/scripts/audit-trail.js +212 -0
- package/scripts/auto-promote-gates.js +178 -27
- package/scripts/billing.js +1 -1
- package/scripts/broker-execution-receipts.js +719 -0
- package/scripts/budget-aware-gates-proof.js +423 -0
- package/scripts/claude-feedback-sync.js +29 -3
- package/scripts/claw-harness-production.js +237 -0
- package/scripts/cli-progress.js +111 -0
- package/scripts/cli-schema.js +163 -1
- package/scripts/codex-runbook-flywheel.js +318 -0
- package/scripts/context-footprint.js +186 -0
- package/scripts/contextfs.js +143 -61
- package/scripts/dashboard-limits.js +27 -0
- package/scripts/dashboard.js +279 -9
- package/scripts/deepseek-v4-runtime-guardrails.js +72 -6
- package/scripts/docker-sandbox-planner.js +18 -0
- package/scripts/double-blind-eval-protocol.js +252 -0
- package/scripts/edotenv-rl-gateway.js +259 -0
- package/scripts/ensure-production-search-corpus.js +162 -0
- package/scripts/eval-holdout.js +311 -0
- package/scripts/feedback-aggregate.js +21 -2
- package/scripts/feedback-loop.js +87 -5
- package/scripts/feedback-quality.js +9 -0
- package/scripts/file-ledger-lock.js +4 -1
- package/scripts/financial-control-plane.js +41 -1
- package/scripts/find-dormant-requires.js +118 -0
- package/scripts/fs-utils.js +84 -8
- package/scripts/gate-stats.js +2 -2
- package/scripts/gates-engine.js +859 -58
- package/scripts/generate-case-study-outreach.js +24 -15
- package/scripts/git-at-scale.js +628 -0
- package/scripts/governance-conflict-audit.js +1650 -0
- package/scripts/governance-difficulty-curriculum.js +328 -0
- package/scripts/graphrag-retrieval.js +275 -0
- package/scripts/gurobi-optimizer.js +324 -0
- package/scripts/gurobi_optimizer.py +485 -0
- package/scripts/harness-selector.js +82 -1
- package/scripts/hidden-entry-points.js +284 -0
- package/scripts/human-escalation.js +199 -1
- package/scripts/hybrid-feedback-context.js +152 -19
- package/scripts/intent-governed-execution.js +602 -0
- package/scripts/intervention-policy.js +123 -20
- package/scripts/jit-harness-compose.js +628 -0
- package/scripts/jsonl-watcher.js +10 -0
- package/scripts/lesson-embedding-index.js +95 -12
- package/scripts/lesson-retrieval.js +105 -19
- package/scripts/local-model-profile.js +19 -2
- package/scripts/mailer/resend-mailer.js +1 -1
- package/scripts/matryoshka-embedding.js +235 -0
- package/scripts/mcp-oauth.js +42 -4
- package/scripts/mcp-session-handles.js +1016 -0
- package/scripts/mcp-wiring-doctor.js +314 -0
- package/scripts/memory-firewall.js +115 -2
- package/scripts/memory-scope-readiness.js +299 -0
- package/scripts/memory-vs-rag-route.js +161 -0
- package/scripts/model-tier-router.js +148 -21
- package/scripts/nvidia-specdecode-al-doctor.js +536 -0
- package/scripts/openui-catalog-compose-honesty.js +593 -0
- package/scripts/operational-integrity.js +19 -1
- package/scripts/override-audit.js +213 -0
- package/scripts/package-manager-honesty-doctor.js +458 -0
- package/scripts/pr-manager.js +63 -1
- package/scripts/prove-herdr-adapter.js +52 -0
- package/scripts/prove-memory-pyramid-and-symbolic-canvas.js +95 -0
- package/scripts/prove-workos.js +73 -0
- package/scripts/provider-attestation-conformance.js +192 -0
- package/scripts/provider-receipt-contract.js +136 -0
- package/scripts/qwen38-max-cost-optimizer.js +401 -0
- package/scripts/radware-threat-defense.js +280 -0
- package/scripts/rag-embedding-identity.js +221 -0
- package/scripts/rag-precision-guardrails.js +112 -2
- package/scripts/remote-feedback-capture.js +159 -0
- package/scripts/research-agent-harness.js +256 -0
- package/scripts/rsi-safety-hillclimb.js +200 -0
- package/scripts/rule-sprawl.js +188 -0
- package/scripts/schedule-manager.js +147 -0
- package/scripts/self-heal.js +8 -0
- package/scripts/session-lease.js +415 -0
- package/scripts/simatree-data-governance.js +347 -0
- package/scripts/slo-alert-engine.js +172 -7
- package/scripts/solver-parity.js +539 -0
- package/scripts/stealth-memory-injection-gate.js +333 -0
- package/scripts/switchyard-router.js +366 -0
- package/scripts/telemetry-analytics.js +84 -27
- package/scripts/temporal-decay-weighting.js +138 -0
- package/scripts/test-all.js +165 -0
- package/scripts/token-savings.js +42 -0
- package/scripts/tool-kpi-tracker.js +108 -5
- package/scripts/tool-registry.js +193 -5
- package/scripts/universal-claim-evaluator.js +14 -2
- package/scripts/vector-store.js +279 -9
- package/scripts/workflow-notebook.js +391 -0
- package/scripts/workflow-sentinel.js +111 -12
- package/scripts/workos-production-guard.js +260 -0
- package/scripts/workspace-search-route.js +515 -0
- package/server.json +2 -2
- package/src/agent-identity-boundary.js +76 -0
- package/src/agent-retrieval-cache.js +155 -0
- package/src/alert-noise-ledger.js +502 -0
- package/src/api/server.js +802 -185
- package/src/git-fast-cache.js +220 -0
- package/src/git-wal-sync.js +156 -0
- package/src/hash-anchored-edit.js +82 -0
- package/src/hermes-platform-protocol.js +475 -0
- package/src/hermes-sync-plane.js +241 -0
- package/src/index.js +30 -1
- package/src/iso42001-compliance-guard.js +97 -0
- package/src/latency-budget.js +244 -0
- package/src/mcp-writeguard.js +316 -0
- package/src/miminions-adapter.js +106 -0
- package/src/pipeline-compass.js +104 -0
- package/src/ppl-alert-pipeline.js +284 -0
- package/src/rendezvous-router.js +90 -0
- 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
|
+
};
|