sneakoscope 4.1.0 → 4.2.0
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/README.md +16 -3
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/cli/command-registry.js +1 -1
- package/dist/cli/router.js +6 -1
- package/dist/commands/doctor.js +272 -127
- package/dist/core/auto-review.js +1 -1
- package/dist/core/codex/agent-config-file-repair.js +43 -2
- package/dist/core/codex-app/codex-agent-role-sync.js +4 -4
- package/dist/core/codex-control/codex-0142-capability.js +51 -6
- package/dist/core/codex-control/codex-app-server-v2-client.js +2 -2
- package/dist/core/codex-native/codex-native-feature-broker.js +50 -0
- package/dist/core/codex-native/native-capability-postcheck.js +59 -16
- package/dist/core/codex-native/native-capability-repair-matrix.js +77 -13
- package/dist/core/commands/mad-db-command.js +146 -51
- package/dist/core/commands/mad-sks-command.js +51 -61
- package/dist/core/db-safety.js +35 -37
- package/dist/core/doctor/doctor-dirty-planner.js +9 -4
- package/dist/core/doctor/doctor-native-capability-repair.js +42 -7
- package/dist/core/doctor/doctor-readiness-matrix.js +9 -5
- package/dist/core/doctor/doctor-repair-postcheck.js +10 -1
- package/dist/core/doctor/doctor-transaction.js +1 -1
- package/dist/core/doctor/supabase-mcp-repair.js +2 -2
- package/dist/core/feature-registry.js +1 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/init.js +5 -4
- package/dist/core/mad-db/mad-db-capability.js +203 -74
- package/dist/core/mad-db/mad-db-coordinator.js +287 -0
- package/dist/core/mad-db/mad-db-executor.js +156 -0
- package/dist/core/mad-db/mad-db-ledger.js +1 -1
- package/dist/core/mad-db/mad-db-lock.js +40 -0
- package/dist/core/mad-db/mad-db-operation-store.js +140 -0
- package/dist/core/mad-db/mad-db-policy-resolver.js +42 -22
- package/dist/core/mad-db/mad-db-policy.js +195 -0
- package/dist/core/mad-db/mad-db-postconditions.js +30 -0
- package/dist/core/mad-db/mad-db-recovery.js +27 -0
- package/dist/core/mad-db/mad-db-result-lifecycle.js +31 -102
- package/dist/core/mad-db/mad-db-runtime-profile.js +121 -0
- package/dist/core/mad-db/mad-db-target.js +64 -0
- package/dist/core/managed-assets/managed-assets-manifest.js +14 -4
- package/dist/core/pipeline-internals/runtime-core.js +40 -0
- package/dist/core/providers/glm/bench/glm-benchmark-runner.js +4 -3
- package/dist/core/providers/glm/bench/glm-benchmark-types.js +1 -1
- package/dist/core/release/release-gate-dag.js +6 -5
- package/dist/core/routes.js +23 -8
- package/dist/core/update/update-migration-state.js +265 -50
- package/dist/core/update-check.js +6 -6
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-launcher.js +17 -5
- package/dist/core/zellij/zellij-slot-column-anchor.js +5 -1
- package/dist/scripts/check-dist-runtime.js +3 -2
- package/dist/scripts/codex-0142-manifest-check.js +2 -1
- package/dist/scripts/config-managed-merge-callsite-coverage-check.js +6 -0
- package/dist/scripts/doctor-dirty-plan-check.js +1 -1
- package/dist/scripts/doctor-transaction-engine-check.js +1 -0
- package/dist/scripts/doctor-warning-only-not-blocker-check.js +18 -1
- package/dist/scripts/loop-directive-check-lib.js +2 -1
- package/dist/scripts/mad-db-capability-check.js +13 -2
- package/dist/scripts/mad-db-command-check.js +7 -5
- package/dist/scripts/mad-db-hook-idempotency-check.js +21 -0
- package/dist/scripts/mad-db-ledger-check.js +2 -1
- package/dist/scripts/mad-db-lifecycle-hook-decision-check.js +5 -4
- package/dist/scripts/mad-db-mad-command-check.js +29 -16
- package/dist/scripts/mad-db-mcp-result-lifecycle-check.js +11 -10
- package/dist/scripts/mad-db-one-cycle-bounded-check.js +15 -18
- package/dist/scripts/mad-db-one-cycle-consumption-check.js +3 -3
- package/dist/scripts/mad-db-operation-lifecycle-blackbox.js +9 -9
- package/dist/scripts/mad-db-operation-lifecycle-ledger-check.js +6 -6
- package/dist/scripts/mad-db-parallel-lifecycle-check.js +24 -0
- package/dist/scripts/mad-db-policy-v2-check.js +20 -0
- package/dist/scripts/mad-db-priority-resolver-check.js +5 -5
- package/dist/scripts/mad-db-real-supabase-e2e.js +166 -0
- package/dist/scripts/mad-db-route-identity-check.js +28 -0
- package/dist/scripts/mad-db-runtime-profile-lifecycle-check.js +24 -0
- package/dist/scripts/mad-db-safety-conflict-matrix-check.js +3 -3
- package/dist/scripts/mad-db-skill-policy-snapshot-check.js +15 -0
- package/dist/scripts/mad-sks-zellij-launch-check.js +7 -1
- package/dist/scripts/managed-role-manifest-parity-check.js +4 -1
- package/dist/scripts/naruto-real-parallelism-blackbox.js +17 -4
- package/dist/scripts/native-capability-postcheck-check.js +1 -0
- package/dist/scripts/native-capability-repair-matrix-check.js +2 -0
- package/dist/scripts/native-chrome-web-review-repair-check.js +1 -0
- package/dist/scripts/native-computer-use-repair-check.js +1 -0
- package/dist/scripts/release-dag-full-coverage-check.js +6 -0
- package/dist/scripts/release-triwiki-first-runner-blackbox.js +5 -1
- package/dist/scripts/sks-3-1-5-directive-check-lib.js +1 -1
- package/dist/scripts/sks-401-all-feature-regression-blackbox.js +1 -1
- package/dist/scripts/update-concurrent-lock-check.js +1 -0
- package/dist/scripts/update-first-command-migration-check.js +4 -3
- package/package.json +13 -2
- package/schemas/mad-db/mad-db-capability.schema.json +92 -19
- package/schemas/update-migration.schema.json +13 -1
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { createMission, missionDir, setCurrent } from '../mission.js';
|
|
3
|
+
import { nowIso, readJson, readText, sha256, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { createMadDbCapability, activateMadDbCapability, closeMadDbCycle, MAD_DB_ACK, markMadDbTransportReady, readMadDbCapability } from './mad-db-capability.js';
|
|
5
|
+
import { MadDbMcpExecutor } from './mad-db-executor.js';
|
|
6
|
+
import { createMadDbRuntimeProfile, closeMadDbRuntimeProfile, redactedRuntimeProfile } from './mad-db-runtime-profile.js';
|
|
7
|
+
import { reserveMadDbOperation, transitionMadDbOperation } from './mad-db-operation-store.js';
|
|
8
|
+
import { readBackCheck, runReadBackChecks } from './mad-db-postconditions.js';
|
|
9
|
+
import { madDbOperationClassesFromClassification } from './mad-db-policy.js';
|
|
10
|
+
import { projectRootHash, resolveMadDbTarget } from './mad-db-target.js';
|
|
11
|
+
import { classifySql } from '../db-safety.js';
|
|
12
|
+
export async function prepareMadDbMission(input) {
|
|
13
|
+
const target = await resolveMadDbTarget(input.root, { args: input.args || [] });
|
|
14
|
+
const { id, dir } = await createMission(input.root, { mode: 'mad-db', prompt: input.task || 'MadDB SQL-plane execution' });
|
|
15
|
+
const cycleId = `mad-db-${Date.now().toString(36)}`;
|
|
16
|
+
const runtimeSessionId = input.runtimeSessionId || `mad-db-session-${Date.now().toString(36)}`;
|
|
17
|
+
const blockers = [...target.blockers];
|
|
18
|
+
let profile;
|
|
19
|
+
if (!target.project_ref) {
|
|
20
|
+
profile = await createMadDbRuntimeProfile({ root: input.root, missionId: id, cycleId, projectRef: 'missing-project-ref', runtimeSessionId });
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
profile = await createMadDbRuntimeProfile({ root: input.root, missionId: id, cycleId, projectRef: target.project_ref, runtimeSessionId });
|
|
24
|
+
}
|
|
25
|
+
const capability = await createMadDbCapability(input.root, {
|
|
26
|
+
missionId: id,
|
|
27
|
+
ack: MAD_DB_ACK,
|
|
28
|
+
cwd: input.root,
|
|
29
|
+
cycleId,
|
|
30
|
+
projectRef: target.project_ref || 'missing-project-ref',
|
|
31
|
+
targetEnvironment: target.target_environment,
|
|
32
|
+
allowedSchemas: target.allowed_schemas,
|
|
33
|
+
runtimeSessionId,
|
|
34
|
+
operatorIntent: input.task,
|
|
35
|
+
profilePath: profile.profile_path,
|
|
36
|
+
profileSha256: profile.profile_sha256,
|
|
37
|
+
serverUrlRedacted: profile.server_url_redacted,
|
|
38
|
+
operations: [...madDbOperationClassesFromClassification(classifySql(input.task))],
|
|
39
|
+
status: blockers.length ? 'quarantined' : 'transport_ready'
|
|
40
|
+
});
|
|
41
|
+
let toolInventory = null;
|
|
42
|
+
if (input.verifyTools && !blockers.length) {
|
|
43
|
+
const executor = new MadDbMcpExecutor(profile);
|
|
44
|
+
toolInventory = await executor.inventory();
|
|
45
|
+
await executor.close();
|
|
46
|
+
await writeJsonAtomic(path.join(dir, 'mad-db', 'runtime', 'tool-inventory.json'), toolInventory);
|
|
47
|
+
if (!toolInventory.ok)
|
|
48
|
+
blockers.push('mad_db_execute_sql_or_apply_migration_unavailable');
|
|
49
|
+
else
|
|
50
|
+
await markMadDbTransportReady(input.root, id);
|
|
51
|
+
}
|
|
52
|
+
await writeJsonAtomic(path.join(dir, 'route-context.json'), {
|
|
53
|
+
route: 'MadDB',
|
|
54
|
+
command: '$MAD-DB',
|
|
55
|
+
mode: 'MADDB',
|
|
56
|
+
task: input.task,
|
|
57
|
+
target: { ...target, project_ref: target.project_ref ? `<hash:${target.project_ref_hash}>` : null },
|
|
58
|
+
capability_file: 'mad-db-capability.json',
|
|
59
|
+
runtime_profile_manifest: 'mad-db/runtime/runtime-profile-manifest.json',
|
|
60
|
+
tool_inventory: toolInventory ? 'mad-db/runtime/tool-inventory.json' : null
|
|
61
|
+
});
|
|
62
|
+
await writeJsonAtomic(path.join(dir, 'mad-db-gate.json'), {
|
|
63
|
+
schema: 'sks.mad-db-gate.v1',
|
|
64
|
+
passed: false,
|
|
65
|
+
mad_db_capability_active: !blockers.length,
|
|
66
|
+
sql_plane_all_mutations_allowed: !blockers.length,
|
|
67
|
+
control_plane_denied: true,
|
|
68
|
+
mission_id: id,
|
|
69
|
+
cycle_id: cycleId,
|
|
70
|
+
blockers,
|
|
71
|
+
created_at: nowIso()
|
|
72
|
+
});
|
|
73
|
+
await setCurrent(input.root, {
|
|
74
|
+
mission_id: id,
|
|
75
|
+
mad_db_capability_mission_id: id,
|
|
76
|
+
route: 'MadDB',
|
|
77
|
+
route_command: '$MAD-DB',
|
|
78
|
+
mode: 'MADDB',
|
|
79
|
+
phase: blockers.length ? 'MADDB_BLOCKED' : 'MADDB_SQL_PLANE_CAPABILITY_ACTIVE',
|
|
80
|
+
questions_allowed: false,
|
|
81
|
+
implementation_allowed: !blockers.length,
|
|
82
|
+
mad_db_active: !blockers.length,
|
|
83
|
+
mad_db_cycle_id: cycleId,
|
|
84
|
+
mad_db_runtime_session_id: runtimeSessionId,
|
|
85
|
+
mad_db_profile_sha256: profile.profile_sha256,
|
|
86
|
+
mad_db_capability_file: 'mad-db-capability.json',
|
|
87
|
+
stop_gate: 'mad-db-gate.json',
|
|
88
|
+
prompt: input.task
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
schema: 'sks.mad-db-prepared-mission.v1',
|
|
92
|
+
ok: blockers.length === 0,
|
|
93
|
+
mission_id: id,
|
|
94
|
+
cycle_id: cycleId,
|
|
95
|
+
target,
|
|
96
|
+
capability,
|
|
97
|
+
runtime_profile: redactedRuntimeProfile(profile),
|
|
98
|
+
tool_inventory: toolInventory,
|
|
99
|
+
blockers
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export async function runMadDbCycle(input) {
|
|
103
|
+
const timings = {};
|
|
104
|
+
const start = Date.now();
|
|
105
|
+
const prepared = await prepareMadDbMission({ root: input.root, task: input.task, args: input.args || [], verifyTools: false });
|
|
106
|
+
timings.prepare_ms = Date.now() - start;
|
|
107
|
+
const profile = await recreateProfileFromPrepared(input.root, prepared);
|
|
108
|
+
const executor = new MadDbMcpExecutor(profile);
|
|
109
|
+
let inventory = null;
|
|
110
|
+
let execution = null;
|
|
111
|
+
let operation = null;
|
|
112
|
+
let readBack = null;
|
|
113
|
+
const blockers = [...prepared.blockers];
|
|
114
|
+
try {
|
|
115
|
+
const connectStart = Date.now();
|
|
116
|
+
inventory = await executor.inventory();
|
|
117
|
+
timings.mcp_connect_ms = Date.now() - connectStart;
|
|
118
|
+
await writeJsonAtomic(path.join(missionDir(input.root, prepared.mission_id), 'mad-db', 'runtime', 'tool-inventory.json'), inventory);
|
|
119
|
+
if (!inventory.ok) {
|
|
120
|
+
blockers.push('mad_db_execute_sql_or_apply_migration_unavailable');
|
|
121
|
+
throw new Error('mad_db_tool_inventory_failed');
|
|
122
|
+
}
|
|
123
|
+
await activateMadDbCapability(input.root, prepared.mission_id);
|
|
124
|
+
const sql = await resolveSqlInput(input);
|
|
125
|
+
if (!sql) {
|
|
126
|
+
blockers.push('mad_db_sql_missing_for_execution');
|
|
127
|
+
throw new Error('mad_db_sql_missing_for_execution');
|
|
128
|
+
}
|
|
129
|
+
const classification = classifySql(sql);
|
|
130
|
+
const toolName = input.action === 'apply-migration' ? 'apply_migration' : 'execute_sql';
|
|
131
|
+
const toolCallId = `cli-${input.action}-${sha256(`${prepared.mission_id}:${sql}:${Date.now()}`).slice(0, 16)}`;
|
|
132
|
+
const reservation = await reserveMadDbOperation({
|
|
133
|
+
root: input.root,
|
|
134
|
+
missionId: prepared.mission_id,
|
|
135
|
+
capability: prepared.capability,
|
|
136
|
+
toolCallId,
|
|
137
|
+
toolName,
|
|
138
|
+
sql,
|
|
139
|
+
migrationName: input.migrationName || null,
|
|
140
|
+
operationClasses: madDbOperationClassesFromClassification({ ...classification, toolName })
|
|
141
|
+
});
|
|
142
|
+
await transitionMadDbOperation({ root: input.root, missionId: prepared.mission_id, toolCallId, state: 'started' });
|
|
143
|
+
const execStart = Date.now();
|
|
144
|
+
execution = input.action === 'apply-migration'
|
|
145
|
+
? await executor.applyMigration(input.migrationName || `mad_db_${Date.now()}`, sql)
|
|
146
|
+
: await executor.executeSql(sql);
|
|
147
|
+
timings.execution_ms = Date.now() - execStart;
|
|
148
|
+
operation = await transitionMadDbOperation({
|
|
149
|
+
root: input.root,
|
|
150
|
+
missionId: prepared.mission_id,
|
|
151
|
+
toolCallId,
|
|
152
|
+
state: execution.ok ? 'succeeded' : 'failed',
|
|
153
|
+
result: execution
|
|
154
|
+
});
|
|
155
|
+
if (!execution.ok)
|
|
156
|
+
blockers.push('mad_db_tool_execution_failed');
|
|
157
|
+
if (execution.ok && input.verifySql) {
|
|
158
|
+
const verifyStart = Date.now();
|
|
159
|
+
readBack = await runReadBackChecks({
|
|
160
|
+
root: input.root,
|
|
161
|
+
missionId: prepared.mission_id,
|
|
162
|
+
executor,
|
|
163
|
+
checks: [readBackCheck('operator_verify_sql', input.verifySql)]
|
|
164
|
+
});
|
|
165
|
+
timings.verification_ms = Date.now() - verifyStart;
|
|
166
|
+
if (!readBack.ok)
|
|
167
|
+
blockers.push('mad_db_read_back_verification_failed');
|
|
168
|
+
if (operation) {
|
|
169
|
+
operation = await transitionMadDbOperation({
|
|
170
|
+
root: input.root,
|
|
171
|
+
missionId: prepared.mission_id,
|
|
172
|
+
toolCallId,
|
|
173
|
+
state: readBack.ok ? 'verified' : 'verification_failed',
|
|
174
|
+
verificationArtifact: readBack.proof_path || null
|
|
175
|
+
}) || operation;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
if (!blockers.length)
|
|
181
|
+
blockers.push(err instanceof Error ? err.message : String(err));
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
await executor.close();
|
|
185
|
+
}
|
|
186
|
+
const closeStart = Date.now();
|
|
187
|
+
const restoration = await closeMadDbRuntimeProfile({ root: input.root, missionId: prepared.mission_id, profile, reason: 'mad_db_cycle_finally' });
|
|
188
|
+
await closeMadDbCycle(input.root, prepared.mission_id, prepared.cycle_id, 'mad_db_cycle_finally');
|
|
189
|
+
timings.close_ms = Date.now() - closeStart;
|
|
190
|
+
timings.total_ms = Date.now() - start;
|
|
191
|
+
const result = {
|
|
192
|
+
schema: 'sks.mad-db-cycle-result.v1',
|
|
193
|
+
ok: blockers.length === 0 && execution?.ok === true && restoration.ok,
|
|
194
|
+
mission_id: prepared.mission_id,
|
|
195
|
+
cycle_id: prepared.cycle_id,
|
|
196
|
+
action: input.action,
|
|
197
|
+
target: prepared.target,
|
|
198
|
+
tool_inventory: inventory,
|
|
199
|
+
execution,
|
|
200
|
+
operation,
|
|
201
|
+
read_back: readBack,
|
|
202
|
+
read_only_restoration: restoration,
|
|
203
|
+
capability_closed: true,
|
|
204
|
+
timings_ms: timings,
|
|
205
|
+
blockers: restoration.ok ? blockers : [...blockers, ...restoration.blockers]
|
|
206
|
+
};
|
|
207
|
+
await writeJsonAtomic(path.join(missionDir(input.root, prepared.mission_id), 'mad-db-result.json'), redactCycleResult(result));
|
|
208
|
+
await clearMadDbCurrentState(input.root, prepared.mission_id, result.ok, restoration);
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
async function clearMadDbCurrentState(root, missionId, ok, restoration) {
|
|
212
|
+
const currentPath = path.join(root, '.sneakoscope', 'state', 'current.json');
|
|
213
|
+
const current = await readJson(currentPath, {});
|
|
214
|
+
if (current.mission_id !== missionId && current.mad_db_capability_mission_id !== missionId)
|
|
215
|
+
return;
|
|
216
|
+
await setCurrent(root, {
|
|
217
|
+
phase: ok ? 'MADDB_CLOSED' : 'MADDB_FAILED_CLOSED',
|
|
218
|
+
implementation_allowed: false,
|
|
219
|
+
mad_db_active: false,
|
|
220
|
+
mad_db_runtime_session_id: null,
|
|
221
|
+
mad_db_profile_sha256: null,
|
|
222
|
+
mad_db_read_only_restored: restoration.ok,
|
|
223
|
+
mad_db_closed_at: nowIso(),
|
|
224
|
+
mad_db_last_result_file: 'mad-db-result.json'
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
function redactCycleResult(result) {
|
|
228
|
+
return {
|
|
229
|
+
...result,
|
|
230
|
+
target: {
|
|
231
|
+
...result.target,
|
|
232
|
+
project_ref: result.target.project_ref ? '<redacted>' : null
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
async function resolveSqlInput(input) {
|
|
237
|
+
if (input.sql)
|
|
238
|
+
return input.sql;
|
|
239
|
+
if (input.migrationFile)
|
|
240
|
+
return readText(path.resolve(input.migrationFile), '');
|
|
241
|
+
const trimmed = String(input.task || '').trim();
|
|
242
|
+
return /^(select|with|show|explain|describe|insert|update|delete|drop|truncate|alter|create|grant|revoke)\b/i.test(trimmed) ? trimmed : null;
|
|
243
|
+
}
|
|
244
|
+
async function recreateProfileFromPrepared(root, prepared) {
|
|
245
|
+
const projectRef = prepared.target.project_ref || 'missing-project-ref';
|
|
246
|
+
return {
|
|
247
|
+
schema: 'sks.mad-db-runtime-profile.v1',
|
|
248
|
+
mission_id: prepared.mission_id,
|
|
249
|
+
cycle_id: prepared.cycle_id,
|
|
250
|
+
runtime_session_id: prepared.capability.runtime_session_id,
|
|
251
|
+
project_ref_hash: sha256(projectRef).slice(0, 16),
|
|
252
|
+
profile_path: prepared.runtime_profile.profile_path,
|
|
253
|
+
profile_sha256: prepared.runtime_profile.profile_sha256,
|
|
254
|
+
server_url_redacted: prepared.runtime_profile.server_url_redacted,
|
|
255
|
+
server_url: `https://mcp.supabase.com/mcp?project_ref=${encodeURIComponent(projectRef)}&features=database`,
|
|
256
|
+
features: ['database'],
|
|
257
|
+
write_capable: true,
|
|
258
|
+
normal_config_hash_before: prepared.runtime_profile.normal_config_hash_before,
|
|
259
|
+
created_at: prepared.runtime_profile.created_at
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
export async function madDbRouteIdentityProof(root, missionId) {
|
|
263
|
+
const capability = await readMadDbCapability(root, missionId);
|
|
264
|
+
const state = await readJson(path.join(root, '.sneakoscope', 'state', 'current.json'), {});
|
|
265
|
+
const profile = await readJson(path.join(missionDir(root, missionId), 'mad-db', 'runtime', 'runtime-profile-manifest.json'), null);
|
|
266
|
+
const sameMission = Boolean(capability && capability.mission_id === missionId && state?.mission_id === missionId);
|
|
267
|
+
return {
|
|
268
|
+
schema: 'sks.mad-db-route-identity-proof.v1',
|
|
269
|
+
ok: sameMission && state?.route === 'MadDB' && state?.route_command === '$MAD-DB',
|
|
270
|
+
mission_id: missionId,
|
|
271
|
+
capability_mission_id: capability?.mission_id || null,
|
|
272
|
+
same_mission: sameMission,
|
|
273
|
+
route: state?.route || null,
|
|
274
|
+
route_command: state?.route_command || null,
|
|
275
|
+
cycle_id: capability?.cycle_id || null,
|
|
276
|
+
project_root_hash: await projectRootHash(root),
|
|
277
|
+
runtime_profile: profile,
|
|
278
|
+
blockers: [
|
|
279
|
+
...(capability ? [] : ['capability_missing']),
|
|
280
|
+
...(profile ? [] : ['runtime_profile_manifest_missing']),
|
|
281
|
+
...(sameMission ? [] : ['mission_binding_mismatch']),
|
|
282
|
+
...(state?.route === 'MadDB' ? [] : ['route_state_not_maddb']),
|
|
283
|
+
...(state?.route_command === '$MAD-DB' ? [] : ['route_command_not_maddb'])
|
|
284
|
+
]
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=mad-db-coordinator.js.map
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { performance } from 'node:perf_hooks';
|
|
2
|
+
import { nowIso, sha256 } from '../fsx.js';
|
|
3
|
+
export class MadDbMcpExecutor {
|
|
4
|
+
profile;
|
|
5
|
+
opts;
|
|
6
|
+
client = null;
|
|
7
|
+
transport = null;
|
|
8
|
+
constructor(profile, opts = {}) {
|
|
9
|
+
this.profile = profile;
|
|
10
|
+
this.opts = opts;
|
|
11
|
+
}
|
|
12
|
+
async connect() {
|
|
13
|
+
if (this.client)
|
|
14
|
+
return;
|
|
15
|
+
const { Client, StreamableHTTPClientTransport } = await loadMcpSdk();
|
|
16
|
+
this.client = new Client({ name: 'sneakoscope-mad-db', version: '4.2.0' });
|
|
17
|
+
const headers = authHeaders();
|
|
18
|
+
const options = headers ? { requestInit: { headers } } : {};
|
|
19
|
+
this.transport = new StreamableHTTPClientTransport(new URL(this.profile.server_url), options);
|
|
20
|
+
await this.client.connect(this.transport);
|
|
21
|
+
}
|
|
22
|
+
async inventory() {
|
|
23
|
+
const started = performance.now();
|
|
24
|
+
try {
|
|
25
|
+
await this.connect();
|
|
26
|
+
const result = await this.client.listTools({}, { timeout: this.opts.timeoutMs || 10_000 });
|
|
27
|
+
const names = (result.tools || []).map((tool) => String(tool?.name || '')).filter(Boolean).sort();
|
|
28
|
+
return {
|
|
29
|
+
schema: 'sks.mad-db-tool-inventory.v1',
|
|
30
|
+
checked_at: nowIso(),
|
|
31
|
+
ok: hasTool(names, 'execute_sql') && hasTool(names, 'apply_migration'),
|
|
32
|
+
tool_names: names,
|
|
33
|
+
execute_sql_available: hasTool(names, 'execute_sql'),
|
|
34
|
+
apply_migration_available: hasTool(names, 'apply_migration'),
|
|
35
|
+
duration_ms: Math.round(performance.now() - started),
|
|
36
|
+
error_digest: null
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
return {
|
|
41
|
+
schema: 'sks.mad-db-tool-inventory.v1',
|
|
42
|
+
checked_at: nowIso(),
|
|
43
|
+
ok: false,
|
|
44
|
+
tool_names: [],
|
|
45
|
+
execute_sql_available: false,
|
|
46
|
+
apply_migration_available: false,
|
|
47
|
+
duration_ms: Math.round(performance.now() - started),
|
|
48
|
+
error_digest: sha256(redactError(err)).slice(0, 32)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async executeSql(sql) {
|
|
53
|
+
return this.callToolWithFallback(['execute_sql', 'supabase.execute_sql'], [{ query: sql }, { sql }]);
|
|
54
|
+
}
|
|
55
|
+
async applyMigration(name, sql) {
|
|
56
|
+
return this.callToolWithFallback(['apply_migration', 'supabase.apply_migration'], [{ name, query: sql }, { name, sql }]);
|
|
57
|
+
}
|
|
58
|
+
async close() {
|
|
59
|
+
await this.transport?.terminateSession().catch(() => undefined);
|
|
60
|
+
await this.client?.close().catch(() => undefined);
|
|
61
|
+
this.client = null;
|
|
62
|
+
this.transport = null;
|
|
63
|
+
}
|
|
64
|
+
async callToolWithFallback(toolNames, argsList) {
|
|
65
|
+
await this.connect();
|
|
66
|
+
const inventory = await this.inventory();
|
|
67
|
+
const tool = toolNames.find((name) => hasTool(inventory.tool_names, name)) || toolNames[0] || '';
|
|
68
|
+
let lastError = null;
|
|
69
|
+
for (const args of argsList) {
|
|
70
|
+
const started = performance.now();
|
|
71
|
+
try {
|
|
72
|
+
const result = await this.client.callTool({ name: tool, arguments: args }, undefined, { timeout: this.opts.timeoutMs || 60_000, resetTimeoutOnProgress: true, maxTotalTimeout: 10 * 60_000 });
|
|
73
|
+
return {
|
|
74
|
+
schema: 'sks.mad-db-tool-result.v1',
|
|
75
|
+
ok: result.isError !== true,
|
|
76
|
+
tool_name: tool,
|
|
77
|
+
result_digest: sha256(JSON.stringify(redactToolResult(result))).slice(0, 32),
|
|
78
|
+
row_count: extractRowCount(result),
|
|
79
|
+
is_error: result.isError === true,
|
|
80
|
+
duration_ms: Math.round(performance.now() - started)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
lastError = err;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
schema: 'sks.mad-db-tool-result.v1',
|
|
89
|
+
ok: false,
|
|
90
|
+
tool_name: tool,
|
|
91
|
+
result_digest: sha256(redactError(lastError)).slice(0, 32),
|
|
92
|
+
row_count: null,
|
|
93
|
+
is_error: true,
|
|
94
|
+
duration_ms: 0
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function loadMcpSdk() {
|
|
99
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
100
|
+
const [{ Client }, { StreamableHTTPClientTransport }] = await Promise.all([
|
|
101
|
+
dynamicImport('@modelcontextprotocol/sdk/client/index.js'),
|
|
102
|
+
dynamicImport('@modelcontextprotocol/sdk/client/streamableHttp.js')
|
|
103
|
+
]);
|
|
104
|
+
return { Client, StreamableHTTPClientTransport };
|
|
105
|
+
}
|
|
106
|
+
function hasTool(names, name) {
|
|
107
|
+
return names.some((candidate) => candidate === name || candidate.endsWith(`.${name}`) || candidate.endsWith(`__${name}`));
|
|
108
|
+
}
|
|
109
|
+
function authHeaders() {
|
|
110
|
+
const token = process.env.SUPABASE_ACCESS_TOKEN || process.env.SKS_MAD_DB_SUPABASE_ACCESS_TOKEN || '';
|
|
111
|
+
if (!token)
|
|
112
|
+
return undefined;
|
|
113
|
+
return { Authorization: `Bearer ${token}` };
|
|
114
|
+
}
|
|
115
|
+
function redactError(err) {
|
|
116
|
+
const text = err instanceof Error ? `${err.name}:${err.message}` : String(err);
|
|
117
|
+
return text.replace(/(Bearer\s+)[A-Za-z0-9._~+/=-]+/gi, '$1<redacted>').replace(/(token|password|secret|apikey)=([^&\s]+)/gi, '$1=<redacted>');
|
|
118
|
+
}
|
|
119
|
+
function redactToolResult(value) {
|
|
120
|
+
if (value == null)
|
|
121
|
+
return value;
|
|
122
|
+
if (typeof value === 'string')
|
|
123
|
+
return value.replace(/(token|password|secret|apikey)["'=:\s]+[A-Za-z0-9._~+/=-]+/gi, '$1=<redacted>');
|
|
124
|
+
if (Array.isArray(value))
|
|
125
|
+
return value.map(redactToolResult);
|
|
126
|
+
if (typeof value === 'object') {
|
|
127
|
+
const out = {};
|
|
128
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
129
|
+
if (/token|password|secret|apikey|service_role/i.test(key))
|
|
130
|
+
out[key] = '<redacted>';
|
|
131
|
+
else if (/rows|data|records/i.test(key) && Array.isArray(entry))
|
|
132
|
+
out[key] = `<redacted:${entry.length}:rows>`;
|
|
133
|
+
else
|
|
134
|
+
out[key] = redactToolResult(entry);
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
function extractRowCount(value) {
|
|
141
|
+
const candidates = [
|
|
142
|
+
value?.row_count,
|
|
143
|
+
value?.rowCount,
|
|
144
|
+
value?.rows_affected,
|
|
145
|
+
value?.structuredContent?.row_count,
|
|
146
|
+
value?.structuredContent?.rowCount,
|
|
147
|
+
value?.structuredContent?.rows_affected
|
|
148
|
+
];
|
|
149
|
+
for (const candidate of candidates) {
|
|
150
|
+
const n = Number(candidate);
|
|
151
|
+
if (Number.isFinite(n))
|
|
152
|
+
return n;
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=mad-db-executor.js.map
|
|
@@ -23,7 +23,7 @@ export async function appendMadDbOperationLifecycle(root, missionId, input) {
|
|
|
23
23
|
tool_name: input.toolName || null,
|
|
24
24
|
sql_hash: input.sqlHash || null,
|
|
25
25
|
destructive: input.destructive === true,
|
|
26
|
-
result_status: input.resultStatus || '
|
|
26
|
+
result_status: input.resultStatus || 'pending_tool_result',
|
|
27
27
|
row_count: input.rowCount ?? null,
|
|
28
28
|
error: input.error || null
|
|
29
29
|
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ensureDir, nowIso, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { missionDir } from '../mission.js';
|
|
5
|
+
export async function withMadDbLock(root, missionId, name, fn) {
|
|
6
|
+
const dir = path.join(missionDir(root, missionId), 'mad-db', 'runtime', 'locks');
|
|
7
|
+
await ensureDir(dir);
|
|
8
|
+
const lockDir = path.join(dir, `${safeName(name)}.lock`);
|
|
9
|
+
const deadline = Date.now() + 10_000;
|
|
10
|
+
while (true) {
|
|
11
|
+
try {
|
|
12
|
+
await fs.mkdir(lockDir);
|
|
13
|
+
await writeJsonAtomic(path.join(lockDir, 'owner.json'), {
|
|
14
|
+
schema: 'sks.mad-db-lock.v1',
|
|
15
|
+
pid: process.pid,
|
|
16
|
+
name,
|
|
17
|
+
acquired_at: nowIso()
|
|
18
|
+
});
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
if (Date.now() > deadline)
|
|
23
|
+
throw new Error(`mad_db_lock_timeout:${name}`);
|
|
24
|
+
await sleep(25 + Math.floor(Math.random() * 25));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return await fn();
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
await fs.rm(lockDir, { recursive: true, force: true }).catch(() => undefined);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function sleep(ms) {
|
|
35
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
36
|
+
}
|
|
37
|
+
function safeName(value) {
|
|
38
|
+
return value.replace(/[^a-z0-9_.-]+/gi, '_').slice(0, 80) || 'lock';
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=mad-db-lock.js.map
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { ensureDir, nowIso, readJson, sha256, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { missionDir } from '../mission.js';
|
|
4
|
+
import { appendMadDbLedgerEvent } from './mad-db-ledger.js';
|
|
5
|
+
import { withMadDbLock } from './mad-db-lock.js';
|
|
6
|
+
import { updateMadDbCapabilityCounters } from './mad-db-capability.js';
|
|
7
|
+
export async function reserveMadDbOperation(input) {
|
|
8
|
+
return withMadDbLock(input.root, input.missionId, `operation-${safeKey(input.toolCallId)}`, async () => {
|
|
9
|
+
const file = operationFile(input.root, input.missionId, input.toolCallId);
|
|
10
|
+
const existing = await readJson(file, null);
|
|
11
|
+
if (existing?.schema === 'sks.mad-db-operation.v2') {
|
|
12
|
+
return { operation: existing, reused: true, capability: input.capability };
|
|
13
|
+
}
|
|
14
|
+
const operationId = `mad-db-op-${sha256(`${input.missionId}:${input.capability.cycle_id}:${input.toolCallId}`).slice(0, 16)}`;
|
|
15
|
+
const operation = {
|
|
16
|
+
schema: 'sks.mad-db-operation.v2',
|
|
17
|
+
operation_id: operationId,
|
|
18
|
+
tool_call_id: input.toolCallId,
|
|
19
|
+
mission_id: input.missionId,
|
|
20
|
+
cycle_id: input.capability.cycle_id,
|
|
21
|
+
project_ref_hash: sha256(input.capability.project_ref).slice(0, 16),
|
|
22
|
+
tool_name: input.toolName,
|
|
23
|
+
sql_sha256: input.sql ? sha256(input.sql) : null,
|
|
24
|
+
migration_name: input.migrationName || null,
|
|
25
|
+
operation_classes: input.operationClasses,
|
|
26
|
+
state: 'reserved',
|
|
27
|
+
attempt: 1,
|
|
28
|
+
started_at: null,
|
|
29
|
+
finished_at: null,
|
|
30
|
+
result_digest: null,
|
|
31
|
+
verification_artifact: null,
|
|
32
|
+
error_code: null
|
|
33
|
+
};
|
|
34
|
+
await ensureDir(path.dirname(file));
|
|
35
|
+
await writeJsonAtomic(file, operation);
|
|
36
|
+
const capability = await updateMadDbCapabilityCounters(input.root, input.missionId, {
|
|
37
|
+
attemptsDelta: 1,
|
|
38
|
+
reservedDelta: 1
|
|
39
|
+
});
|
|
40
|
+
await appendMadDbLedgerEvent(input.root, input.missionId, {
|
|
41
|
+
type: 'db_operation.reserved',
|
|
42
|
+
operation_id: operation.operation_id,
|
|
43
|
+
tool_call_id: operation.tool_call_id,
|
|
44
|
+
cycle_id: operation.cycle_id,
|
|
45
|
+
tool_name: operation.tool_name,
|
|
46
|
+
sql_sha256: operation.sql_sha256,
|
|
47
|
+
operation_classes: operation.operation_classes
|
|
48
|
+
});
|
|
49
|
+
return { operation, reused: false, capability: capability || input.capability };
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export async function transitionMadDbOperation(input) {
|
|
53
|
+
return withMadDbLock(input.root, input.missionId, `operation-${safeKey(input.toolCallId)}`, async () => {
|
|
54
|
+
const file = operationFile(input.root, input.missionId, input.toolCallId);
|
|
55
|
+
const existing = await readJson(file, null);
|
|
56
|
+
if (!existing)
|
|
57
|
+
return null;
|
|
58
|
+
const terminalSuccess = input.state === 'succeeded' || input.state === 'verified';
|
|
59
|
+
const terminalFailure = input.state === 'failed' || input.state === 'verification_failed' || input.state === 'unknown';
|
|
60
|
+
const updated = {
|
|
61
|
+
...existing,
|
|
62
|
+
state: input.state,
|
|
63
|
+
started_at: existing.started_at || (input.state === 'started' ? nowIso() : existing.started_at),
|
|
64
|
+
finished_at: terminalSuccess || terminalFailure ? nowIso() : existing.finished_at,
|
|
65
|
+
result_digest: input.result === undefined ? existing.result_digest : sha256(safeResultDigestInput(input.result)).slice(0, 32),
|
|
66
|
+
verification_artifact: input.verificationArtifact || existing.verification_artifact,
|
|
67
|
+
error_code: input.errorCode || existing.error_code
|
|
68
|
+
};
|
|
69
|
+
await writeJsonAtomic(file, updated);
|
|
70
|
+
if (input.state === 'succeeded')
|
|
71
|
+
await updateMadDbCapabilityCounters(input.root, input.missionId, { succeededDelta: 1 });
|
|
72
|
+
if (input.state === 'failed')
|
|
73
|
+
await updateMadDbCapabilityCounters(input.root, input.missionId, { failedDelta: 1 });
|
|
74
|
+
await appendMadDbLedgerEvent(input.root, input.missionId, {
|
|
75
|
+
type: `db_operation.${input.state}`,
|
|
76
|
+
operation_id: updated.operation_id,
|
|
77
|
+
tool_call_id: updated.tool_call_id,
|
|
78
|
+
cycle_id: updated.cycle_id,
|
|
79
|
+
tool_name: updated.tool_name,
|
|
80
|
+
result_digest: updated.result_digest,
|
|
81
|
+
verification_artifact: updated.verification_artifact,
|
|
82
|
+
error_code: updated.error_code
|
|
83
|
+
});
|
|
84
|
+
return updated;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
export function extractCanonicalToolCallId(payload = {}) {
|
|
88
|
+
const candidates = [
|
|
89
|
+
payload.tool_call_id,
|
|
90
|
+
payload.toolCallId,
|
|
91
|
+
payload.call_id,
|
|
92
|
+
payload.callId,
|
|
93
|
+
payload.id,
|
|
94
|
+
payload.request_id,
|
|
95
|
+
payload.requestId,
|
|
96
|
+
payload.tool?.id,
|
|
97
|
+
payload.tool?.call_id,
|
|
98
|
+
payload.metadata?.tool_call_id,
|
|
99
|
+
payload.context?.tool_call_id
|
|
100
|
+
];
|
|
101
|
+
const found = candidates.find((value) => typeof value === 'string' && value.trim());
|
|
102
|
+
return found ? String(found).trim() : null;
|
|
103
|
+
}
|
|
104
|
+
export function operationFile(root, missionId, toolCallId) {
|
|
105
|
+
return path.join(missionDir(root, missionId), 'mad-db', 'runtime', 'operations', `${safeKey(toolCallId)}.json`);
|
|
106
|
+
}
|
|
107
|
+
function safeKey(value) {
|
|
108
|
+
return sha256(value).slice(0, 24);
|
|
109
|
+
}
|
|
110
|
+
function safeResultDigestInput(value) {
|
|
111
|
+
try {
|
|
112
|
+
return JSON.stringify(redactSensitive(value));
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return String(value);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function redactSensitive(value, depth = 0) {
|
|
119
|
+
if (depth > 8 || value == null)
|
|
120
|
+
return value;
|
|
121
|
+
if (typeof value === 'string') {
|
|
122
|
+
return value.replace(/(access_token|refresh_token|password|apikey|service_role|secret)["'=:\s]+[A-Za-z0-9._~+/=-]+/gi, '$1=<redacted>');
|
|
123
|
+
}
|
|
124
|
+
if (Array.isArray(value))
|
|
125
|
+
return value.map((entry) => redactSensitive(entry, depth + 1));
|
|
126
|
+
if (typeof value === 'object') {
|
|
127
|
+
const out = {};
|
|
128
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
129
|
+
if (/token|password|secret|apikey|service_role/i.test(key))
|
|
130
|
+
out[key] = '<redacted>';
|
|
131
|
+
else if (/rows|data|records/i.test(key) && Array.isArray(entry))
|
|
132
|
+
out[key] = `<redacted:${entry.length}:rows>`;
|
|
133
|
+
else
|
|
134
|
+
out[key] = redactSensitive(entry, depth + 1);
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=mad-db-operation-store.js.map
|