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
@@ -28,6 +28,10 @@ const PENDING_BUCKETS = new Set((MERGE_QUALITY_CHECKS.pendingBuckets || []).map(
28
28
  const FAILING_BUCKETS = new Set((MERGE_QUALITY_CHECKS.failingBuckets || []).map((value) => String(value || '').toLowerCase()));
29
29
 
30
30
  const SELF_REFERENTIAL_CHECKS = new Set(MERGE_QUALITY_CHECKS.selfReferentialChecks || []);
31
+ // Optional checks are ignored by summarizeChecks. Vercel is optional because it
32
+ // is not in requiredStatusCheckContexts / classic branch protection; free-tier
33
+ // deploy rate-limits must not stall Trunk when required checks are green.
34
+ const OPTIONAL_CHECKS = new Set(MERGE_QUALITY_CHECKS.optionalChecks || []);
31
35
 
32
36
  function assertSafeGhArgs(args) {
33
37
  if (!Array.isArray(args) || args.length === 0) {
@@ -176,6 +180,7 @@ function summarizeChecks(checks = []) {
176
180
  const name = check.name || 'unknown-check';
177
181
 
178
182
  if (SELF_REFERENTIAL_CHECKS.has(name)) continue;
183
+ if (OPTIONAL_CHECKS.has(name)) continue;
179
184
 
180
185
  const bucket = String(check.bucket || '').toLowerCase();
181
186
  if (bucket) {
@@ -220,6 +225,43 @@ function sleep(ms) {
220
225
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
221
226
  }
222
227
 
228
+
229
+ /**
230
+ * Read-only repository ruleset inventory for diagnosis.
231
+ * Never mutates rulesets or bypass actors.
232
+ */
233
+ function inspectRepositoryRulesets(repo = '', runner = runGh) {
234
+ const normalizedRepo = String(repo || '').trim();
235
+ const args = normalizedRepo
236
+ ? ['api', `repos/${normalizedRepo}/rulesets`, '-H', 'Accept: application/vnd.github+json']
237
+ : ['api', 'repos/{owner}/{repo}/rulesets', '-H', 'Accept: application/vnd.github+json'];
238
+
239
+ try {
240
+ const result = runner(args);
241
+ if (result && result.status === 0 && result.stdout) {
242
+ const rulesets = JSON.parse(result.stdout);
243
+ if (Array.isArray(rulesets)) {
244
+ return {
245
+ ok: true,
246
+ count: rulesets.length,
247
+ activeRulesets: rulesets
248
+ .filter((rs) => String(rs?.enforcement || '').toLowerCase() === 'active')
249
+ .map((rs) => ({
250
+ id: rs.id,
251
+ name: rs.name,
252
+ target: rs.target || 'branch',
253
+ enforcement: rs.enforcement,
254
+ })),
255
+ };
256
+ }
257
+ }
258
+ } catch {
259
+ // Diagnostic only — never block merges on inventory failure.
260
+ }
261
+
262
+ return { ok: false, count: 0, activeRulesets: [] };
263
+ }
264
+
223
265
  async function resolveBlockers(pr, runner = runGh) {
224
266
  const title = pr.title || 'Untitled PR';
225
267
  const mergeState = pr.mergeStateStatus || 'UNKNOWN';
@@ -228,6 +270,25 @@ async function resolveBlockers(pr, runner = runGh) {
228
270
  console.log(`[PR Manager] Diagnosing PR #${pr.number}: "${title}"`);
229
271
  console.log(`[PR Manager] Merge State: ${mergeState} | Mergeable: ${mergeable}`);
230
272
 
273
+ // Live-default runner only: injected test runners must not have their mock
274
+ // queues consumed by optional ruleset inventory.
275
+ if (runner === runGh) {
276
+ try {
277
+ const rulesetInventory = inspectRepositoryRulesets('', runner);
278
+ if (rulesetInventory.ok) {
279
+ const names = rulesetInventory.activeRulesets.map((rs) => rs.name).join(', ') || '(none)';
280
+ console.log(
281
+ `[PR Manager] Repository rulesets: ${rulesetInventory.count} total, `
282
+ + `${rulesetInventory.activeRulesets.length} active [${names}]`,
283
+ );
284
+ } else {
285
+ console.log('[PR Manager] Repository rulesets: unavailable (diagnostic only)');
286
+ }
287
+ } catch {
288
+ console.log('[PR Manager] Repository rulesets: unavailable (diagnostic only)');
289
+ }
290
+ }
291
+
231
292
  if (pr.isDraft) {
232
293
  console.log('[PR Manager] PR is a draft. Skipping.');
233
294
  return { status: 'skipped', reason: 'draft' };
@@ -282,7 +343,7 @@ async function resolveBlockers(pr, runner = runGh) {
282
343
  }
283
344
 
284
345
  if (
285
- (pr.mergeStateStatus === 'CLEAN' || pr.mergeStateStatus === 'UNSTABLE')
346
+ (pr.mergeStateStatus === 'CLEAN' || pr.mergeStateStatus === 'UNSTABLE' || pr.mergeStateStatus === 'BLOCKED')
286
347
  && pr.mergeable === 'MERGEABLE'
287
348
  ) {
288
349
  console.log('[PR Manager] SUCCESS: PR is ready for protected autonomous merge.');
@@ -446,4 +507,5 @@ module.exports = {
446
507
  performMerge,
447
508
  managePrs,
448
509
  summarizeChecks,
510
+ inspectRepositoryRulesets,
449
511
  };
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * prove-herdr-adapter.js — Prove Herdr terminal multiplexer governance plugin manifest.
6
+ */
7
+
8
+ const fs = require('node:fs');
9
+ const path = require('node:path');
10
+
11
+ const MANIFEST_PATH = path.join(__dirname, '..', 'adapters', 'herdr', 'herdr-plugin.toml');
12
+
13
+ function verifyHerdrPlugin() {
14
+ if (!fs.existsSync(MANIFEST_PATH)) {
15
+ return { ok: false, error: `manifest missing at ${MANIFEST_PATH}` };
16
+ }
17
+ const content = fs.readFileSync(MANIFEST_PATH, 'utf8');
18
+
19
+ const idMatch = content.match(/^id\s*=\s*"(.*?)"/m);
20
+ const nameMatch = content.match(/^name\s*=\s*"(.*?)"/m);
21
+ const versionMatch = content.match(/^version\s*=\s*"(.*?)"/m);
22
+ const minHerdrMatch = content.match(/^minHerdrVersion\s*=\s*"(.*?)"/m);
23
+ const categoryMatch = content.match(/^category\s*=\s*"(.*?)"/m);
24
+ const commandMatch = content.match(/^command\s*=\s*"(.*?)"/m);
25
+
26
+ if (!idMatch || !nameMatch || !versionMatch || !minHerdrMatch || !categoryMatch || !commandMatch) {
27
+ return { ok: false, error: 'missing required fields in herdr-plugin.toml' };
28
+ }
29
+
30
+ const pluginId = idMatch[1];
31
+ if (pluginId !== 'thumbgate-approvals') {
32
+ return { ok: false, error: `expected plugin id thumbgate-approvals, got ${pluginId}` };
33
+ }
34
+
35
+ return {
36
+ ok: true,
37
+ pluginId,
38
+ pluginName: nameMatch[1],
39
+ version: versionMatch[1],
40
+ category: categoryMatch[1],
41
+ mcpCommand: commandMatch[1],
42
+ mcpArgs: ['--yes', '--package', `thumbgate@${versionMatch[1]}`, 'thumbgate', 'serve'],
43
+ };
44
+ }
45
+
46
+ if (process.argv[1] && require('node:path').resolve(process.argv[1]) === require('node:path').resolve(__filename)) {
47
+ const res = verifyHerdrPlugin();
48
+ process.stdout.write(`${JSON.stringify(res, null, 2)}\n`);
49
+ process.exit(res.ok ? 0 : 1);
50
+ }
51
+
52
+ module.exports = { verifyHerdrPlugin, MANIFEST_PATH };
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const assert = require('node:assert/strict');
5
+ const {
6
+ PYRAMID_LAYERS,
7
+ classifyPyramidLayer,
8
+ distillMemoryPyramid,
9
+ } = require('./agent-memory-lifecycle');
10
+ const {
11
+ renderSymbolicTaskCanvas,
12
+ compactSymbolicTaskCanvas,
13
+ buildContextFootprintReport,
14
+ } = require('./context-footprint');
15
+
16
+ function proveMemoryPyramidAndSymbolicCanvas() {
17
+ console.log('--- Proving High-ROI Memory Pyramid & Symbolic Task Canvas ---');
18
+
19
+ // 1. Prove Semantic Memory Pyramid Classification & Distillation
20
+ const sampleMemories = [
21
+ {
22
+ type: 'preference',
23
+ tags: ['sop', 'guardrail'],
24
+ content: 'NEVER approve PR or satisfy branch protection without human review',
25
+ },
26
+ {
27
+ type: 'procedural',
28
+ tags: ['workflow'],
29
+ content: 'Multi-agent parallel execution sequence and lock coordination',
30
+ },
31
+ {
32
+ type: 'semantic',
33
+ whatWentWrong: 'Passed raw 500KB transcript to prompt',
34
+ whatWorked: 'Compacted with Symbolic Task Canvas',
35
+ },
36
+ {
37
+ type: 'working',
38
+ content: 'Raw subagent output chunk #14',
39
+ },
40
+ ];
41
+
42
+ const pyramidReport = distillMemoryPyramid(sampleMemories);
43
+ assert.strictEqual(pyramidReport.kind, 'semantic-memory-pyramid');
44
+ assert.strictEqual(pyramidReport.totalMemories, 4);
45
+ assert.strictEqual(pyramidReport.layers.L3_PERSONA_SOP.count, 1);
46
+ assert.strictEqual(pyramidReport.layers.L2_SCENARIO.count, 1);
47
+ assert.strictEqual(pyramidReport.layers.L1_ATOM.count, 1);
48
+ assert.strictEqual(pyramidReport.layers.L0_CONVERSATION.count, 1);
49
+ console.log('✔ Semantic Memory Pyramid (L0-L3) classification & distillation verified');
50
+
51
+
52
+ // 2. Prove Symbolic Task Canvas Generation
53
+ const canvas = renderSymbolicTaskCanvas({
54
+ activeTask: 'Refactor Context Engine',
55
+ milestones: [
56
+ { name: 'Implement L0-L3 Pyramid Classifier', status: 'completed' },
57
+ { name: 'Wire Symbolic Task Canvas Generator', status: 'completed' },
58
+ { name: 'Verify Token Savings', status: 'in_progress' },
59
+ ],
60
+ blockers: [],
61
+ });
62
+
63
+ assert.strictEqual(canvas.milestoneCount, 3);
64
+ assert.match(canvas.mermaidDiagram, /graph TD/);
65
+
66
+ assert.match(canvas.mermaidDiagram, /Implement L0-L3 Pyramid Classifier \[DONE\]/);
67
+ console.log('✔ Symbolic Task Canvas (Mermaid state graph) generation verified');
68
+
69
+ // 3. Prove Token Footprint Compaction & Report Integration
70
+ const verboseTrace = Array.from({ length: 15 }, (_, i) => ({
71
+ step: i,
72
+ rawLog: `Verbose interaction step ${i} with heavy transcript chatter and raw tool calls`,
73
+ }));
74
+
75
+ const footprintReport = buildContextFootprintReport({
76
+ entries: verboseTrace,
77
+ symbolicCanvas: true,
78
+ activeTask: 'Proof Task',
79
+ });
80
+
81
+ assert.ok(footprintReport.symbolicTaskCanvas);
82
+ assert.ok(footprintReport.symbolicTaskCanvas.footprint.savings.estimatedTokens > 0);
83
+ console.log(
84
+ `✔ Context Footprint Compaction verified: Saved ${footprintReport.symbolicTaskCanvas.footprint.savings.estimatedTokens} tokens (${footprintReport.symbolicTaskCanvas.footprint.savings.reductionPercent}% reduction)`,
85
+ );
86
+
87
+ console.log('--- ALL HIGH-ROI PROOFS PASSED CLEANLY ---');
88
+ return true;
89
+ }
90
+
91
+ if (process.argv[1] && require('node:path').resolve(process.argv[1]) === require('node:path').resolve(__filename)) {
92
+ proveMemoryPyramidAndSymbolicCanvas();
93
+ }
94
+
95
+ module.exports = { proveMemoryPyramidAndSymbolicCanvas };
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * prove-workos.js — evidence that WorkOS production auth + MCP OAuth hierarchy
6
+ * still satisfy ThumbGate's enterprise readiness contract ($10/mo AuthKit cap,
7
+ * production client, scope implication for MCP tools).
8
+ *
9
+ * Usage: node scripts/prove-workos.js [--skip-live]
10
+ */
11
+
12
+ const path = require('path');
13
+ const { spawnSync } = require('child_process');
14
+
15
+ const skipLive = process.argv.includes('--skip-live');
16
+ const root = path.resolve(__dirname, '..');
17
+
18
+ function run(cmd, args, opts = {}) {
19
+ const res = spawnSync(cmd, args, {
20
+ encoding: 'utf8',
21
+ cwd: root,
22
+ timeout: opts.timeout || 120000,
23
+ env: process.env,
24
+ });
25
+ return res;
26
+ }
27
+
28
+ function main() {
29
+ const failures = [];
30
+ // Unit tests: OAuth hierarchy + guard constants
31
+ const unit = run(process.execPath, [
32
+ '--test',
33
+ 'tests/mcp-oauth.test.js',
34
+ 'tests/workos-production-guard.test.js',
35
+ ]);
36
+ if (unit.status !== 0) {
37
+ failures.push(`unit tests failed:\n${unit.stdout || ''}\n${unit.stderr || ''}`);
38
+ }
39
+
40
+ if (!skipLive) {
41
+ const live = run(process.execPath, ['scripts/workos-production-guard.js', '--json'], { timeout: 60000 });
42
+ let report = null;
43
+ try {
44
+ report = JSON.parse(live.stdout || '{}');
45
+ } catch {
46
+ failures.push(`live guard did not emit JSON: ${live.stdout || live.stderr}`);
47
+ }
48
+ if (report && report.ok !== true) {
49
+ failures.push(`live WorkOS guard failed: ${JSON.stringify(report.failures || report)}`);
50
+ }
51
+ if (report && report.ok) {
52
+ console.log(
53
+ `LIVE ok client=${report.clientId} host=${report.finalHost} methods=${(report.methodsFound || []).join(',')}`,
54
+ );
55
+ }
56
+ } else {
57
+ console.log('LIVE skipped (--skip-live)');
58
+ }
59
+
60
+ if (failures.length) {
61
+ console.error('prove:workos FAILED');
62
+ for (const f of failures) console.error(f);
63
+ process.exit(1);
64
+ }
65
+ console.log('prove:workos OK');
66
+ process.exit(0);
67
+ }
68
+
69
+ if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(__filename)) {
70
+ main();
71
+ }
72
+
73
+ module.exports = { main };
@@ -0,0 +1,192 @@
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 { validateToolContract } = require('./tool-contract-validator');
8
+
9
+ const SCHEMA_VERSION = 'thumbgate-provider-execution-attestation-v1';
10
+ const SCHEMA_PATH = path.join(__dirname, '..', 'config', 'schemas', 'provider-execution-attestation-v1.schema.json');
11
+ const DEFAULT_VECTORS_PATH = path.join(__dirname, '..', 'conformance', 'provider-attestation', 'vectors.json');
12
+ const SCHEMA = JSON.parse(fs.readFileSync(SCHEMA_PATH, 'utf8'));
13
+
14
+ // Strict RFC 3339 / ISO-8601 date-time with required timezone (Z or ±HH:MM).
15
+ const RFC3339_DATE_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
16
+ const BASE64_STRICT = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
17
+ const ED25519_SIG_BYTES = 64;
18
+
19
+ function hasUnpairedSurrogate(text) {
20
+ for (let i = 0; i < text.length; i += 1) {
21
+ const code = text.charCodeAt(i);
22
+ if (code >= 0xd800 && code <= 0xdbff) {
23
+ const next = text.charCodeAt(i + 1);
24
+ if (!(next >= 0xdc00 && next <= 0xdfff)) return true;
25
+ i += 1;
26
+ continue;
27
+ }
28
+ if (code >= 0xdc00 && code <= 0xdfff) return true;
29
+ }
30
+ return false;
31
+ }
32
+
33
+ function jcsCanonicalize(value) {
34
+ if (value === null) return 'null';
35
+ if (typeof value === 'boolean') return JSON.stringify(value);
36
+ if (typeof value === 'string') {
37
+ if (hasUnpairedSurrogate(value)) {
38
+ throw new TypeError('JCS rejects unpaired UTF-16 surrogates');
39
+ }
40
+ return JSON.stringify(value);
41
+ }
42
+ if (typeof value === 'number') {
43
+ if (!Number.isFinite(value)) throw new TypeError('JCS rejects non-finite numbers');
44
+ return JSON.stringify(value);
45
+ }
46
+ if (Array.isArray(value)) return `[${value.map(jcsCanonicalize).join(',')}]`;
47
+ if (typeof value !== 'object') throw new TypeError(`JCS cannot serialize ${typeof value}`);
48
+ return `{${Object.keys(value)
49
+ .sort()
50
+ .map((key) => {
51
+ if (hasUnpairedSurrogate(key)) {
52
+ throw new TypeError('JCS rejects unpaired UTF-16 surrogates in property names');
53
+ }
54
+ return `${JSON.stringify(key)}:${jcsCanonicalize(value[key])}`;
55
+ })
56
+ .join(',')}}`;
57
+ }
58
+
59
+ function unsignedPayload(attestation) {
60
+ const payload = { ...attestation };
61
+ delete payload.signature;
62
+ return payload;
63
+ }
64
+
65
+ function sha256Hex(value) {
66
+ return crypto.createHash('sha256').update(value).digest('hex');
67
+ }
68
+
69
+ function parseRfc3339Ms(value) {
70
+ if (typeof value !== 'string' || !RFC3339_DATE_TIME.test(value)) return Number.NaN;
71
+ const ms = Date.parse(value);
72
+ return Number.isFinite(ms) ? ms : Number.NaN;
73
+ }
74
+
75
+ function isStrictBase64(value) {
76
+ if (typeof value !== 'string' || value.length === 0 || value.length % 4 !== 0) return false;
77
+ if (!BASE64_STRICT.test(value)) return false;
78
+ return true;
79
+ }
80
+
81
+ function semanticErrors(attestation) {
82
+ const errors = [];
83
+ const evidence = attestation.providerEvidence || {};
84
+ const policy = attestation.reconciliationPolicy || {};
85
+ const observedAt = parseRfc3339Ms(evidence.observedAt);
86
+ const startedAt = parseRfc3339Ms(evidence.windowStartedAt);
87
+ const closesAt = parseRfc3339Ms(evidence.windowClosesAt);
88
+ const issuedAt = parseRfc3339Ms(attestation.issuedAt);
89
+
90
+ if (!Number.isFinite(observedAt)) errors.push('observedAt_not_rfc3339');
91
+ if (!Number.isFinite(startedAt)) errors.push('windowStartedAt_not_rfc3339');
92
+ if (!Number.isFinite(closesAt)) errors.push('windowClosesAt_not_rfc3339');
93
+ if (!Number.isFinite(issuedAt)) errors.push('issuedAt_not_rfc3339');
94
+
95
+ if (attestation.provider?.id !== policy.provider) errors.push('policy_provider_mismatch');
96
+ if (policy.declaredWindowMs > policy.maxWindowMs) errors.push('holder_window_exceeds_gate_maximum');
97
+ if (Number.isFinite(startedAt) && Number.isFinite(closesAt) && closesAt - startedAt !== policy.declaredWindowMs) {
98
+ errors.push('declared_window_duration_mismatch');
99
+ }
100
+ if (attestation.outcome === 'not-yet-visible' && Number.isFinite(observedAt) && Number.isFinite(closesAt) && observedAt >= closesAt) {
101
+ errors.push('not_yet_visible_after_window');
102
+ }
103
+ if (attestation.outcome === 'absent-after-window' && Number.isFinite(observedAt) && Number.isFinite(closesAt) && observedAt < closesAt) {
104
+ errors.push('absence_claimed_before_window_closed');
105
+ }
106
+ if ((attestation.outcome === 'matched' || attestation.outcome === 'mismatched') && !evidence.eventId) {
107
+ errors.push('provider_event_required_for_comparison');
108
+ }
109
+ if ((attestation.outcome === 'not-yet-visible' || attestation.outcome === 'absent-after-window') && evidence.eventId !== null) {
110
+ errors.push('provider_event_must_be_null_when_unobserved');
111
+ }
112
+ return errors;
113
+ }
114
+
115
+ function verifyAttestation(attestation, publicKeyPem) {
116
+ const schema = validateToolContract(SCHEMA, attestation);
117
+ const errors = schema.valid ? [] : schema.errors.map((error) => `schema:${error}`);
118
+ errors.push(...semanticErrors(attestation));
119
+ if (errors.length > 0) return { valid: false, errors };
120
+
121
+ let canonicalPayload;
122
+ let payloadHash;
123
+ try {
124
+ canonicalPayload = jcsCanonicalize(unsignedPayload(attestation));
125
+ payloadHash = sha256Hex(canonicalPayload);
126
+ } catch (error) {
127
+ return {
128
+ valid: false,
129
+ errors: [`jcs_canonicalization_failed:${error.message}`],
130
+ };
131
+ }
132
+
133
+ if (payloadHash !== attestation.signature.payloadHash) errors.push('payload_hash_mismatch');
134
+
135
+ const signatureValue = attestation.signature?.value;
136
+ if (!isStrictBase64(signatureValue)) {
137
+ errors.push('signature_not_strict_base64');
138
+ } else {
139
+ try {
140
+ const signatureBytes = Buffer.from(signatureValue, 'base64');
141
+ if (signatureBytes.length !== ED25519_SIG_BYTES) {
142
+ errors.push('signature_length_invalid');
143
+ } else {
144
+ const verified = crypto.verify(
145
+ null,
146
+ Buffer.from(payloadHash, 'utf8'),
147
+ crypto.createPublicKey(publicKeyPem),
148
+ signatureBytes,
149
+ );
150
+ if (!verified) errors.push('signature_invalid');
151
+ }
152
+ } catch {
153
+ errors.push('public_key_or_signature_unusable');
154
+ }
155
+ }
156
+
157
+ return { valid: errors.length === 0, errors, canonicalPayload, payloadHash };
158
+ }
159
+
160
+ function runConformance(vectorsPath = DEFAULT_VECTORS_PATH) {
161
+ const suite = JSON.parse(fs.readFileSync(vectorsPath, 'utf8'));
162
+ const results = suite.vectors.map((vector) => {
163
+ const result = verifyAttestation(vector.attestation, suite.publicKeyPem);
164
+ const passed = result.valid === vector.expectedValid
165
+ && (!vector.expectedError || result.errors.includes(vector.expectedError))
166
+ && (!vector.expectedCanonicalPayload || result.canonicalPayload === vector.expectedCanonicalPayload);
167
+ return { id: vector.id, passed, expectedValid: vector.expectedValid, actualValid: result.valid, errors: result.errors };
168
+ });
169
+ return { passed: results.every((result) => result.passed), count: results.length, results };
170
+ }
171
+
172
+ function runCli(argv = process.argv.slice(2)) {
173
+ const result = runConformance(argv[0] ? path.resolve(argv[0]) : DEFAULT_VECTORS_PATH);
174
+ console.log(JSON.stringify(result, null, 2));
175
+ return result.passed ? 0 : 1;
176
+ }
177
+
178
+ if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(__filename)) process.exitCode = runCli();
179
+
180
+ module.exports = {
181
+ SCHEMA,
182
+ SCHEMA_VERSION,
183
+ jcsCanonicalize,
184
+ runConformance,
185
+ semanticErrors,
186
+ sha256Hex,
187
+ unsignedPayload,
188
+ verifyAttestation,
189
+ parseRfc3339Ms,
190
+ isStrictBase64,
191
+ hasUnpairedSurrogate,
192
+ };
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const crypto = require('node:crypto');
5
+
6
+ /**
7
+ * Partner-Neutral Broker Execution Receipt Contract Schema:
8
+ * - schemaVersion: '1.0'
9
+ * - receiptId: string (UUID / unique receipt identifier)
10
+ * - idempotencyKey: string (deterministic action key)
11
+ * - principal: string (agent or user identity)
12
+ * - target: string (target resource, URL, or API endpoint)
13
+ * - decision: 'allow' | 'block' | 'warn'
14
+ * - toolName: string
15
+ * - canonicalDigest: string (SHA-256 over canonical request payload)
16
+ * - providerEventId: string (external event ID from provider/broker)
17
+ * - brokerId: string (identifier of the out-of-boundary credential holder, e.g. 'aigate')
18
+ * - signature: string (HMAC-SHA256 or RSA/ECDSA signature emitted by broker)
19
+ * - reconciliation: { status: 'pending'|'reconciled'|'failed', timestamp: string }
20
+ */
21
+
22
+ const RECEIPT_SCHEMA_VERSION = '1.0';
23
+
24
+ function canonicalStringify(value) {
25
+ if (value === null || typeof value !== 'object') return JSON.stringify(value);
26
+ if (Array.isArray(value)) return `[${value.map(canonicalStringify).join(',')}]`;
27
+ return `{${Object.keys(value).sort((left, right) => left.localeCompare(right, 'en')).map((key) => `${JSON.stringify(key)}:${canonicalStringify(value[key])}`).join(',')}}`;
28
+ }
29
+
30
+ function computeCanonicalRequestDigest({ toolName, toolInput, target, idempotencyKey, recordedAt }) {
31
+ const parts = [
32
+ String(toolName || ''),
33
+ typeof toolInput === 'object' ? canonicalStringify(toolInput || {}) : String(toolInput || ''),
34
+ String(target || ''),
35
+ String(idempotencyKey || ''),
36
+ String(recordedAt || ''),
37
+ ];
38
+ return crypto.createHash('sha256').update(parts.join('|')).digest('hex');
39
+ }
40
+
41
+ function validateProviderReceiptSchema(receipt) {
42
+ if (!receipt || typeof receipt !== 'object') {
43
+ return { ok: false, errors: ['Receipt must be an object.'] };
44
+ }
45
+
46
+ const errors = [];
47
+ for (const field of ['schemaVersion', 'receiptId', 'toolName', 'providerEventId']) {
48
+ if (!receipt[field] || typeof receipt[field] !== 'string') {
49
+ errors.push(`Missing or invalid ${field}.`);
50
+ }
51
+ }
52
+ if (!receipt.canonicalDigest || typeof receipt.canonicalDigest !== 'string') {
53
+ errors.push('Missing or invalid canonicalDigest.');
54
+ }
55
+ if (!receipt.principal || typeof receipt.principal !== 'string') {
56
+ errors.push('Missing or invalid principal.');
57
+ }
58
+ if (!receipt.target || typeof receipt.target !== 'string') {
59
+ errors.push('Missing or invalid target.');
60
+ }
61
+ if (!['allow', 'block', 'warn'].includes(receipt.decision)) {
62
+ errors.push('Missing or invalid decision (must be allow, block, or warn).');
63
+ }
64
+ if (!receipt.idempotencyKey || typeof receipt.idempotencyKey !== 'string') {
65
+ errors.push('Missing or invalid idempotencyKey.');
66
+ }
67
+ if (!receipt.brokerId || typeof receipt.brokerId !== 'string') {
68
+ errors.push('Missing or invalid brokerId.');
69
+ }
70
+ if (!receipt.signature || typeof receipt.signature !== 'string') {
71
+ errors.push('Missing or invalid broker signature.');
72
+ }
73
+
74
+ return { ok: errors.length === 0, errors };
75
+ }
76
+
77
+ function verifyBrokerSignature(receipt, brokerSecretKey) {
78
+ const schemaValidation = validateProviderReceiptSchema(receipt);
79
+ if (!schemaValidation.ok) {
80
+ return { verified: false, reason: 'schema_validation_failed', errors: schemaValidation.errors };
81
+ }
82
+
83
+ if (!brokerSecretKey) {
84
+ return { verified: false, reason: 'missing_broker_key' };
85
+ }
86
+
87
+ const signedClaims = { ...receipt };
88
+ delete signedClaims.signature;
89
+ delete signedClaims.reconciliation;
90
+ const expectedSignature = crypto
91
+ .createHmac('sha256', brokerSecretKey)
92
+ .update(canonicalStringify(signedClaims))
93
+ .digest('hex');
94
+
95
+ try {
96
+ const verified = crypto.timingSafeEqual(
97
+ Buffer.from(receipt.signature, 'hex'),
98
+ Buffer.from(expectedSignature, 'hex')
99
+ );
100
+ return { verified, reason: verified ? 'valid_broker_signature' : 'signature_mismatch' };
101
+ } catch {
102
+ return { verified: false, reason: 'crypto_error' };
103
+ }
104
+ }
105
+
106
+ function reconcileProviderEvent(receipt, providerEventResponse = {}) {
107
+ const schemaCheck = validateProviderReceiptSchema(receipt);
108
+ if (!schemaCheck.ok) {
109
+ return { reconciled: false, reason: 'invalid_receipt_schema' };
110
+ }
111
+
112
+ if (!providerEventResponse.eventId || typeof providerEventResponse.eventId !== 'string') {
113
+ return { reconciled: false, reason: 'missing_provider_event_id' };
114
+ }
115
+
116
+ const matchesEventId = providerEventResponse.eventId === receipt.providerEventId;
117
+ const matchesTarget = !providerEventResponse.target || providerEventResponse.target === receipt.target;
118
+
119
+ const reconciled = Boolean(matchesEventId && matchesTarget);
120
+
121
+ return {
122
+ reconciled,
123
+ reconciliationTimestamp: new Date().toISOString(),
124
+ providerEventId: receipt.providerEventId,
125
+ reason: reconciled ? 'event_reconciled' : 'event_mismatch',
126
+ };
127
+ }
128
+
129
+ module.exports = {
130
+ RECEIPT_SCHEMA_VERSION,
131
+ canonicalStringify,
132
+ computeCanonicalRequestDigest,
133
+ validateProviderReceiptSchema,
134
+ verifyBrokerSignature,
135
+ reconcileProviderEvent,
136
+ };