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,347 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ThumbGate — Simatree Enterprise Data Lifecycle & BI Governance Engine
|
|
5
|
+
*
|
|
6
|
+
* Inspired by Wesley Flores (Managing Partner at Simatree, Top 25 Information Manager,
|
|
7
|
+
* Computerworld Best Practices BI Leader).
|
|
8
|
+
*
|
|
9
|
+
* Core Principles:
|
|
10
|
+
* 1. "Why Before How" — Data operations require explicit business intent and rollback receipts.
|
|
11
|
+
* 2. Full Lifecycle Data Governance — Interdicts unverified schema/table mutations, lakehouse drops,
|
|
12
|
+
* and ungrounded batch modifications across BigQuery, Snowflake, Databricks, and Postgres.
|
|
13
|
+
* 3. Bayesian Uncertainty Estimation — Computes statistical posterior confidence bounds before
|
|
14
|
+
* AI data agents dispatch queries or mutations against enterprise analytics tables.
|
|
15
|
+
* 4. PMO Transformation Gates — Validates multi-stage IT modernization milestones with verifiable receipts.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
const fs = require('node:fs');
|
|
21
|
+
const path = require('node:path');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* One part of a SQL object name, in every form the advertised warehouses emit:
|
|
25
|
+
* a bare identifier (`customers`), a double-quoted one (Postgres/Snowflake),
|
|
26
|
+
* a backtick-quoted one (BigQuery/Databricks — note it may contain dots and
|
|
27
|
+
* hyphens, as in `prod-project.analytics.customers`), or a bracketed one
|
|
28
|
+
* (SQL Server, `[dbo]`).
|
|
29
|
+
*/
|
|
30
|
+
const SQL_IDENT_PART = '(?:[\\w$]+|"[^"]+"|`[^`]+`|\\[[^\\]]+\\])';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A possibly schema- or project-qualified table reference: one or more parts
|
|
34
|
+
* joined by dots.
|
|
35
|
+
*
|
|
36
|
+
* WHY THIS IS NOT `\w+`: `\w+` matches only the single-part form. With it,
|
|
37
|
+
* `DELETE FROM analytics.customers;` failed the destructive test entirely and
|
|
38
|
+
* received an authorized receipt with no rollback plan, no risk tier and no
|
|
39
|
+
* declared targetEntities — the exact full-table deletion this gate exists to
|
|
40
|
+
* stop. Every qualified and quoted form was equally invisible.
|
|
41
|
+
*/
|
|
42
|
+
const QUALIFIED_TABLE = `${SQL_IDENT_PART}(?:\\s*\\.\\s*${SQL_IDENT_PART})*`;
|
|
43
|
+
|
|
44
|
+
const DESTRUCTIVE_SQL_PATTERNS = [
|
|
45
|
+
/\bDROP\s+(TABLE|DATABASE|SCHEMA|VIEW|INDEX)\b/i,
|
|
46
|
+
/\bTRUNCATE\s+(TABLE)?\b/i,
|
|
47
|
+
/\bALTER\s+TABLE\s+.*\b(DROP\s+COLUMN|RENAME\s+TO)\b/i,
|
|
48
|
+
new RegExp(`\\bDELETE\\s+FROM\\s+${QUALIFIED_TABLE}(\\s+WHERE\\s+(1=1|TRUE))?\\s*;?$`, 'i'),
|
|
49
|
+
// An UPDATE with no WHERE clause rewrites every row. The previous form put the
|
|
50
|
+
// negative lookahead after `.*`, where backtracking always satisfies it, so the
|
|
51
|
+
// clause was inert and the pattern reduced to "any UPDATE ... SET".
|
|
52
|
+
new RegExp(`\\bUPDATE\\s+${QUALIFIED_TABLE}\\s+SET\\b(?![\\s\\S]*\\bWHERE\\b)`, 'i'),
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const LOW_SIGNAL_INTENT_PATTERNS = [
|
|
56
|
+
/^(test|testing|fix|update|clean|temp|misc|stuff|run|do it|asap)$/i,
|
|
57
|
+
/^(as per request|needed|required|ticket|todo)$/i,
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Evaluates whether a data mutation complies with the "Why Before How" intent mandate.
|
|
62
|
+
*
|
|
63
|
+
* @param {Object} payload
|
|
64
|
+
* @param {string} payload.sql - SQL statement or data operation command
|
|
65
|
+
* @param {string} [payload.businessIntent] - Explicit rationale for the operation
|
|
66
|
+
* @param {string} [payload.riskTier] - 'LOW', 'MEDIUM', 'HIGH', 'CRITICAL'
|
|
67
|
+
* @param {Array<string>} [payload.targetEntities] - Target tables/views
|
|
68
|
+
* @param {Object} [payload.rollbackPlan] - Defined rollback procedure or snapshot ID
|
|
69
|
+
* @returns {Object} Evaluation verdict and telemetry
|
|
70
|
+
*/
|
|
71
|
+
function evaluateDataLifecycleIntent(payload = {}) {
|
|
72
|
+
const sql = String(payload.sql || '').trim();
|
|
73
|
+
const businessIntent = String(payload.businessIntent || '').trim();
|
|
74
|
+
const riskTier = String(payload.riskTier || 'MEDIUM').toUpperCase();
|
|
75
|
+
const targetEntities = Array.isArray(payload.targetEntities) ? payload.targetEntities : [];
|
|
76
|
+
const rollbackPlan = payload.rollbackPlan && typeof payload.rollbackPlan === 'object' ? payload.rollbackPlan : null;
|
|
77
|
+
|
|
78
|
+
const violations = [];
|
|
79
|
+
let isDestructive = false;
|
|
80
|
+
|
|
81
|
+
for (const pattern of DESTRUCTIVE_SQL_PATTERNS) {
|
|
82
|
+
if (pattern.test(sql)) {
|
|
83
|
+
isDestructive = true;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 1. Intent check
|
|
89
|
+
if (!businessIntent) {
|
|
90
|
+
violations.push('MISSING_BUSINESS_INTENT: Data operations require an explicit "why" before execution.');
|
|
91
|
+
} else if (businessIntent.length < 15 || LOW_SIGNAL_INTENT_PATTERNS.some((p) => p.test(businessIntent))) {
|
|
92
|
+
violations.push(`INSUFFICIENT_BUSINESS_INTENT: Intent "${businessIntent}" lacks context-grounded rationale.`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 2. Destructive SQL checks
|
|
96
|
+
if (isDestructive) {
|
|
97
|
+
if (!rollbackPlan || !rollbackPlan.snapshotId) {
|
|
98
|
+
violations.push('DESTRUCTIVE_MUTATION_WITHOUT_ROLLBACK: Destructive SQL requires a verified snapshotId or restore receipt.');
|
|
99
|
+
}
|
|
100
|
+
if (riskTier !== 'HIGH' && riskTier !== 'CRITICAL') {
|
|
101
|
+
violations.push('INVALID_RISK_TIER: Destructive operations must be classified as HIGH or CRITICAL risk.');
|
|
102
|
+
}
|
|
103
|
+
if (targetEntities.length === 0) {
|
|
104
|
+
violations.push('UNSPECIFIED_TARGET_ENTITIES: Destructive operations must declare explicit targetEntities.');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const allowed = violations.length === 0;
|
|
109
|
+
const score = Math.max(0, 100 - (violations.length * 35));
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
allowed,
|
|
113
|
+
score,
|
|
114
|
+
isDestructive,
|
|
115
|
+
intentValidated: Boolean(businessIntent && businessIntent.length >= 15),
|
|
116
|
+
riskTier,
|
|
117
|
+
targetEntities,
|
|
118
|
+
violations,
|
|
119
|
+
receipt: allowed ? {
|
|
120
|
+
gate: 'simatree-data-governance',
|
|
121
|
+
timestamp: new Date().toISOString(),
|
|
122
|
+
intent: businessIntent,
|
|
123
|
+
entities: targetEntities,
|
|
124
|
+
authorized: true,
|
|
125
|
+
} : null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Estimates Bayesian uncertainty and posterior confidence bounds for AI data queries.
|
|
131
|
+
*
|
|
132
|
+
* @param {Object} metrics
|
|
133
|
+
* @param {number} [metrics.sampleSize=100] - Number of historical benchmark runs
|
|
134
|
+
* @param {number} [metrics.priorSuccessRate=0.95] - Prior probability of successful execution (0-1)
|
|
135
|
+
* @param {number} [metrics.observedFailures=0] - Observed anomalies or schema drift incidents
|
|
136
|
+
* @param {number} [metrics.schemaDriftScore=0.0] - Drift score between 0.0 (pristine) and 1.0 (corrupted)
|
|
137
|
+
* @param {number} [metrics.uncertaintyThreshold=0.20] - Max tolerable posterior uncertainty (0-1)
|
|
138
|
+
* @returns {Object} Bayesian posterior metrics and safety gate verdict
|
|
139
|
+
*/
|
|
140
|
+
function estimateBayesianUncertainty(metrics = {}) {
|
|
141
|
+
const sampleSize = Math.max(1, Number(metrics.sampleSize) || 100);
|
|
142
|
+
const priorSuccess = Math.min(1, Math.max(0.1, Number(metrics.priorSuccessRate) || 0.95));
|
|
143
|
+
const observedFailures = Math.max(0, Number(metrics.observedFailures) || 0);
|
|
144
|
+
const schemaDriftScore = Math.min(1, Math.max(0, Number(metrics.schemaDriftScore) || 0.0));
|
|
145
|
+
const threshold = Number(metrics.uncertaintyThreshold) || 0.20;
|
|
146
|
+
|
|
147
|
+
// Beta distribution update (Beta(alpha, beta))
|
|
148
|
+
// Prior pseudo-counts: alpha_0 = priorSuccess * 10, beta_0 = (1 - priorSuccess) * 10
|
|
149
|
+
const alpha0 = priorSuccess * 10;
|
|
150
|
+
const beta0 = (1 - priorSuccess) * 10;
|
|
151
|
+
|
|
152
|
+
const observedSuccesses = Math.max(0, sampleSize - observedFailures);
|
|
153
|
+
const alphaPost = alpha0 + observedSuccesses;
|
|
154
|
+
const betaPost = beta0 + observedFailures + (schemaDriftScore * 10);
|
|
155
|
+
|
|
156
|
+
const posteriorMean = alphaPost / (alphaPost + betaPost);
|
|
157
|
+
const posteriorVariance = (alphaPost * betaPost) / (Math.pow(alphaPost + betaPost, 2) * (alphaPost + betaPost + 1));
|
|
158
|
+
const uncertainty = Math.min(1, Math.sqrt(posteriorVariance) * 3 + schemaDriftScore * 0.4);
|
|
159
|
+
|
|
160
|
+
const safe = uncertainty <= threshold && posteriorMean >= 0.80;
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
safe,
|
|
164
|
+
posteriorMean: Number(posteriorMean.toFixed(4)),
|
|
165
|
+
uncertainty: Number(uncertainty.toFixed(4)),
|
|
166
|
+
threshold,
|
|
167
|
+
schemaDriftScore,
|
|
168
|
+
verdict: safe ? 'CONFIDENT_EXECUTION' : 'HIGH_UNCERTAINTY_INTERDICTED',
|
|
169
|
+
recommendation: safe
|
|
170
|
+
? 'Execute data pipeline stage autonomously.'
|
|
171
|
+
: 'Require operator escalation or rollback snapshot pre-execution.',
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Validates PMO transformation stages for enterprise data architectures.
|
|
177
|
+
*
|
|
178
|
+
* @param {Object} plan - Multi-stage transformation plan
|
|
179
|
+
* @param {Array<Object>} plan.stages - Sequence of milestone stages
|
|
180
|
+
* @returns {Object} PMO compliance audit result
|
|
181
|
+
*/
|
|
182
|
+
function auditPMOTransformationGate(plan = {}) {
|
|
183
|
+
const stages = Array.isArray(plan.stages) ? plan.stages : [];
|
|
184
|
+
const findings = [];
|
|
185
|
+
|
|
186
|
+
if (stages.length === 0) {
|
|
187
|
+
return {
|
|
188
|
+
compliant: false,
|
|
189
|
+
score: 0,
|
|
190
|
+
findings: ['PLAN_EMPTY: Transformation plan contains no actionable milestone stages.'],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
stages.forEach((stage, idx) => {
|
|
195
|
+
const stageId = stage.id || `stage_${idx + 1}`;
|
|
196
|
+
if (!stage.name) {
|
|
197
|
+
findings.push(`${stageId}: Missing stage name.`);
|
|
198
|
+
}
|
|
199
|
+
if (!stage.outcomeMetric) {
|
|
200
|
+
findings.push(`${stageId}: Missing measurable outcomeMetric.`);
|
|
201
|
+
}
|
|
202
|
+
if (!stage.rollbackReceipt && stage.isMutating) {
|
|
203
|
+
findings.push(`${stageId}: Mutating stage lacks verifiable rollbackReceipt.`);
|
|
204
|
+
}
|
|
205
|
+
if (!stage.owner) {
|
|
206
|
+
findings.push(`${stageId}: Missing assigned stakeholder / owner.`);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const compliant = findings.length === 0;
|
|
211
|
+
const score = Math.max(0, 100 - (findings.length * 20));
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
compliant,
|
|
215
|
+
score,
|
|
216
|
+
totalStages: stages.length,
|
|
217
|
+
findings,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Doctor check verifying runtime integrity.
|
|
223
|
+
*/
|
|
224
|
+
function checkDoctor() {
|
|
225
|
+
const sampleEvaluation = evaluateDataLifecycleIntent({
|
|
226
|
+
sql: 'SELECT customer_id, revenue FROM analytics.sales_monthly WHERE year = 2026',
|
|
227
|
+
businessIntent: 'Monthly recurring revenue cohort analysis for Q3 executive review.',
|
|
228
|
+
riskTier: 'LOW',
|
|
229
|
+
targetEntities: ['analytics.sales_monthly'],
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const sampleBayesian = estimateBayesianUncertainty({
|
|
233
|
+
sampleSize: 200,
|
|
234
|
+
priorSuccessRate: 0.98,
|
|
235
|
+
observedFailures: 1,
|
|
236
|
+
schemaDriftScore: 0.02,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const samplePMO = auditPMOTransformationGate({
|
|
240
|
+
stages: [
|
|
241
|
+
{
|
|
242
|
+
id: 'STAGE_1',
|
|
243
|
+
name: 'Lakehouse Ingestion Validation',
|
|
244
|
+
outcomeMetric: 'zero_schema_mismatch',
|
|
245
|
+
owner: 'data-engineering',
|
|
246
|
+
isMutating: false,
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const ok = sampleEvaluation.allowed && sampleBayesian.safe && samplePMO.compliant;
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
ok,
|
|
255
|
+
name: 'simatree-data-governance',
|
|
256
|
+
version: '1.35.0',
|
|
257
|
+
evaluator: sampleEvaluation.allowed ? 'ONLINE' : 'ERROR',
|
|
258
|
+
bayesianModel: sampleBayesian.safe ? 'ONLINE' : 'ERROR',
|
|
259
|
+
pmoGate: samplePMO.compliant ? 'ONLINE' : 'ERROR',
|
|
260
|
+
timestamp: new Date().toISOString(),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Path-based main check. The `require.main === module` form trips SonarCloud
|
|
266
|
+
* rule javascript:S3403 (always-false strict equality under strict type
|
|
267
|
+
* inference), so we compare paths instead.
|
|
268
|
+
*
|
|
269
|
+
* WHY realpath AND NOT A BARE `path.resolve` COMPARE: when this CLI is launched
|
|
270
|
+
* through a symlink — an npm `bin` shim, a global install, `npx` — Node leaves
|
|
271
|
+
* `process.argv[1]` as the symlink path while `__filename` is already the
|
|
272
|
+
* realpath of the target. A plain resolve-and-compare is then false, and the
|
|
273
|
+
* process would exit 0 having printed nothing: no `--doctor` report, no
|
|
274
|
+
* `--eval` result, no `--sql` verdict. `require.main === module` did not have
|
|
275
|
+
* that hole because Node's module resolution canonicalises first. Canonicalising
|
|
276
|
+
* both sides restores the original semantics.
|
|
277
|
+
*
|
|
278
|
+
* realpathSync throws on a path that no longer exists, so each side falls back
|
|
279
|
+
* to its resolved form; that keeps the check total rather than crashing on a
|
|
280
|
+
* deleted or exotic entry point.
|
|
281
|
+
*/
|
|
282
|
+
function canonicalPath(candidate) {
|
|
283
|
+
const resolved = path.resolve(candidate);
|
|
284
|
+
try {
|
|
285
|
+
return fs.realpathSync(resolved);
|
|
286
|
+
} catch {
|
|
287
|
+
return resolved;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function isDirectInvocation() {
|
|
292
|
+
const entryPoint = process.argv[1];
|
|
293
|
+
if (!entryPoint) return false;
|
|
294
|
+
return canonicalPath(entryPoint) === canonicalPath(__filename);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// CLI Interface
|
|
298
|
+
if (isDirectInvocation()) {
|
|
299
|
+
const args = process.argv.slice(2);
|
|
300
|
+
|
|
301
|
+
if (args.includes('--doctor')) {
|
|
302
|
+
const report = checkDoctor();
|
|
303
|
+
console.log(JSON.stringify(report, null, 2));
|
|
304
|
+
process.exit(report.ok ? 0 : 1);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (args.includes('--eval')) {
|
|
308
|
+
const evalIdx = args.indexOf('--eval');
|
|
309
|
+
const input = args[evalIdx + 1] || '{}';
|
|
310
|
+
try {
|
|
311
|
+
const payload = JSON.parse(input);
|
|
312
|
+
const res = evaluateDataLifecycleIntent(payload);
|
|
313
|
+
console.log(JSON.stringify(res, null, 2));
|
|
314
|
+
process.exit(res.allowed ? 0 : 1);
|
|
315
|
+
} catch (err) {
|
|
316
|
+
console.error(`Invalid JSON input: ${err.message}`);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (args.includes('--sql')) {
|
|
322
|
+
const sqlIdx = args.indexOf('--sql');
|
|
323
|
+
const sql = args[sqlIdx + 1] || '';
|
|
324
|
+
const whyIdx = args.indexOf('--why');
|
|
325
|
+
const why = whyIdx !== -1 ? args[whyIdx + 1] : '';
|
|
326
|
+
|
|
327
|
+
const res = evaluateDataLifecycleIntent({
|
|
328
|
+
sql,
|
|
329
|
+
businessIntent: why,
|
|
330
|
+
targetEntities: ['unspecified'],
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
console.log(JSON.stringify(res, null, 2));
|
|
334
|
+
process.exit(res.allowed ? 0 : 1);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
console.log('Usage: node scripts/simatree-data-governance.js [--doctor | --eval <json> | --sql <query> --why <intent>]');
|
|
338
|
+
process.exit(0);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
module.exports = {
|
|
342
|
+
evaluateDataLifecycleIntent,
|
|
343
|
+
estimateBayesianUncertainty,
|
|
344
|
+
auditPMOTransformationGate,
|
|
345
|
+
checkDoctor,
|
|
346
|
+
DESTRUCTIVE_SQL_PATTERNS,
|
|
347
|
+
};
|
|
@@ -1,14 +1,179 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
+
|
|
3
4
|
const fs = require('fs');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
const { computeToolKpis, getAtRiskTools } = require('./tool-kpi-tracker');
|
|
6
7
|
const { deliver } = require('./webhook-delivery');
|
|
7
8
|
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
|
|
10
|
+
// Agentic tool-hop latency wall (Akamai State of AI Inference 2026): most critical
|
|
11
|
+
// use cases need ≤500ms end-to-end; many need ≤250ms. More GPUs do not fix
|
|
12
|
+
// CPU/tool hops — budget each hop and fail closed when p95 exceeds the tier.
|
|
13
|
+
const AGENTIC_HOP_LATENCY_BUDGETS_MS = Object.freeze({
|
|
14
|
+
'read-only': 250,
|
|
15
|
+
'minimal-impact': 250,
|
|
16
|
+
'contained-write': 500,
|
|
17
|
+
critical: 1000,
|
|
18
|
+
default: 500,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const DEFAULT_SLOS = {
|
|
22
|
+
successRate: 90,
|
|
23
|
+
// Matches Akamai's published agentic 500ms wall for generic tool hops.
|
|
24
|
+
p95LatencyMs: AGENTIC_HOP_LATENCY_BUDGETS_MS.default,
|
|
25
|
+
minCallsForAlert: 3,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function getAlertLogPath() {
|
|
29
|
+
return path.join(resolveFeedbackDir(), 'slo-alerts.jsonl');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function hopLatencyBudgetMs(writeRiskTier) {
|
|
33
|
+
if (writeRiskTier && Object.prototype.hasOwnProperty.call(AGENTIC_HOP_LATENCY_BUDGETS_MS, writeRiskTier)) {
|
|
34
|
+
return AGENTIC_HOP_LATENCY_BUDGETS_MS[writeRiskTier];
|
|
35
|
+
}
|
|
36
|
+
return AGENTIC_HOP_LATENCY_BUDGETS_MS.default;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Evaluate per-tool p95 against WriteGuard-tier hop budgets.
|
|
41
|
+
* Measures the hop chain ThumbGate actually sees (tool KPI), not tokens/sec.
|
|
42
|
+
*/
|
|
43
|
+
function checkHopLatencyBudgets({
|
|
44
|
+
periodHours = 24,
|
|
45
|
+
feedbackDir,
|
|
46
|
+
minCalls = DEFAULT_SLOS.minCallsForAlert,
|
|
47
|
+
budgets = AGENTIC_HOP_LATENCY_BUDGETS_MS,
|
|
48
|
+
} = {}) {
|
|
49
|
+
const { tools, totalCalls, evidenceStatus } = computeToolKpis({ periodHours, feedbackDir });
|
|
50
|
+
const evaluated = tools
|
|
51
|
+
.filter((tool) => tool.requestCount >= minCalls)
|
|
52
|
+
.map((tool) => {
|
|
53
|
+
const budgetMs = hopLatencyBudgetMs(tool.writeRiskTier);
|
|
54
|
+
const withinBudget = tool.p95 <= budgetMs;
|
|
55
|
+
return {
|
|
56
|
+
toolName: tool.toolName,
|
|
57
|
+
writeRiskTier: tool.writeRiskTier || null,
|
|
58
|
+
requestCount: tool.requestCount,
|
|
59
|
+
p95: tool.p95,
|
|
60
|
+
budgetMs,
|
|
61
|
+
withinBudget,
|
|
62
|
+
overBudgetMs: withinBudget ? 0 : tool.p95 - budgetMs,
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
const violations = evaluated.filter((tool) => !tool.withinBudget);
|
|
66
|
+
return {
|
|
67
|
+
wallMs: budgets.default || AGENTIC_HOP_LATENCY_BUDGETS_MS.default,
|
|
68
|
+
budgets: { ...AGENTIC_HOP_LATENCY_BUDGETS_MS, ...budgets },
|
|
69
|
+
periodHours,
|
|
70
|
+
totalCalls,
|
|
71
|
+
evidenceStatus,
|
|
72
|
+
tools: evaluated,
|
|
73
|
+
violations,
|
|
74
|
+
violationCount: violations.length,
|
|
75
|
+
checkedAt: new Date().toISOString(),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function checkSloViolations({ slos, periodHours = 24, feedbackDir } = {}) {
|
|
80
|
+
const t = { ...DEFAULT_SLOS, ...slos };
|
|
81
|
+
const atRisk = getAtRiskTools({
|
|
82
|
+
successRateThreshold: t.successRate,
|
|
83
|
+
p95Threshold: t.p95LatencyMs,
|
|
84
|
+
periodHours,
|
|
85
|
+
feedbackDir,
|
|
86
|
+
});
|
|
87
|
+
const hopBudgets = checkHopLatencyBudgets({
|
|
88
|
+
periodHours,
|
|
89
|
+
feedbackDir,
|
|
90
|
+
minCalls: t.minCallsForAlert,
|
|
91
|
+
});
|
|
92
|
+
const byName = new Map(atRisk.map((tool) => [tool.toolName, { ...tool, reasons: [] }]));
|
|
93
|
+
|
|
94
|
+
for (const tool of atRisk) {
|
|
95
|
+
const entry = byName.get(tool.toolName);
|
|
96
|
+
if (tool.successRate < t.successRate) {
|
|
97
|
+
entry.reasons.push(`success rate ${tool.successRate}% < ${t.successRate}% SLO`);
|
|
98
|
+
}
|
|
99
|
+
if (tool.p95 > t.p95LatencyMs) {
|
|
100
|
+
entry.reasons.push(`P95 ${tool.p95}ms > ${t.p95LatencyMs}ms SLO`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (const hop of hopBudgets.violations) {
|
|
105
|
+
if (!byName.has(hop.toolName)) {
|
|
106
|
+
byName.set(hop.toolName, {
|
|
107
|
+
toolName: hop.toolName,
|
|
108
|
+
requestCount: hop.requestCount,
|
|
109
|
+
successRate: null,
|
|
110
|
+
p95: hop.p95,
|
|
111
|
+
writeRiskTier: hop.writeRiskTier,
|
|
112
|
+
reasons: [],
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
const entry = byName.get(hop.toolName);
|
|
116
|
+
entry.writeRiskTier = hop.writeRiskTier;
|
|
117
|
+
entry.budgetMs = hop.budgetMs;
|
|
118
|
+
entry.reasons.push(
|
|
119
|
+
`hop P95 ${hop.p95}ms > ${hop.budgetMs}ms ${hop.writeRiskTier || 'default'} budget`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const violations = [...byName.values()]
|
|
124
|
+
.filter((tool) => tool.reasons.length > 0)
|
|
125
|
+
.map((tool) => ({
|
|
126
|
+
...tool,
|
|
127
|
+
severity: (tool.successRate != null && tool.successRate < 70) ? 'critical' : 'warning',
|
|
128
|
+
}));
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
thresholds: t,
|
|
132
|
+
periodHours,
|
|
133
|
+
hopBudgets,
|
|
134
|
+
violations,
|
|
135
|
+
violationCount: violations.length,
|
|
136
|
+
checkedAt: new Date().toISOString(),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function logAlert(alert) {
|
|
141
|
+
const lp = getAlertLogPath();
|
|
142
|
+
const dir = path.dirname(lp);
|
|
143
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
144
|
+
fs.appendFileSync(lp, `${JSON.stringify({ ...alert, loggedAt: new Date().toISOString() })}\n`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function runSloCheck({ slos, periodHours = 24, platform, webhookUrl } = {}) {
|
|
148
|
+
const result = checkSloViolations({ slos, periodHours });
|
|
149
|
+
if (result.violationCount > 0) {
|
|
150
|
+
logAlert(result);
|
|
151
|
+
if (platform && webhookUrl) {
|
|
152
|
+
const title = `ThumbGate SLO Alert — ${result.violationCount} violation(s)`;
|
|
153
|
+
const lines = result.violations.map((v) => `- ${v.toolName}: ${v.reasons.join(', ')} [${v.severity}]`);
|
|
154
|
+
await deliver(platform, webhookUrl, title, lines.join('\n'));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function formatSloSection(r) {
|
|
161
|
+
if (!r || r.violationCount === 0) return '';
|
|
162
|
+
const lines = ['', 'SLO Violations:'];
|
|
163
|
+
for (const v of r.violations) {
|
|
164
|
+
lines.push(` - [${v.severity.toUpperCase()}] ${v.toolName}: ${v.reasons.join('; ')} (${v.requestCount} calls)`);
|
|
165
|
+
}
|
|
166
|
+
return lines.join('\n');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = {
|
|
170
|
+
AGENTIC_HOP_LATENCY_BUDGETS_MS,
|
|
171
|
+
DEFAULT_SLOS,
|
|
172
|
+
checkHopLatencyBudgets,
|
|
173
|
+
checkSloViolations,
|
|
174
|
+
formatSloSection,
|
|
175
|
+
getAlertLogPath,
|
|
176
|
+
hopLatencyBudgetMs,
|
|
177
|
+
logAlert,
|
|
178
|
+
runSloCheck,
|
|
179
|
+
};
|