sneakoscope 4.1.1 → 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.
Files changed (59) hide show
  1. package/README.md +12 -9
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/cli/command-registry.js +1 -1
  7. package/dist/core/auto-review.js +1 -1
  8. package/dist/core/commands/mad-db-command.js +146 -51
  9. package/dist/core/commands/mad-sks-command.js +15 -31
  10. package/dist/core/db-safety.js +35 -37
  11. package/dist/core/doctor/supabase-mcp-repair.js +2 -2
  12. package/dist/core/feature-registry.js +1 -1
  13. package/dist/core/fsx.js +1 -1
  14. package/dist/core/init.js +5 -4
  15. package/dist/core/mad-db/mad-db-capability.js +203 -74
  16. package/dist/core/mad-db/mad-db-coordinator.js +287 -0
  17. package/dist/core/mad-db/mad-db-executor.js +156 -0
  18. package/dist/core/mad-db/mad-db-ledger.js +1 -1
  19. package/dist/core/mad-db/mad-db-lock.js +40 -0
  20. package/dist/core/mad-db/mad-db-operation-store.js +140 -0
  21. package/dist/core/mad-db/mad-db-policy-resolver.js +42 -22
  22. package/dist/core/mad-db/mad-db-policy.js +195 -0
  23. package/dist/core/mad-db/mad-db-postconditions.js +30 -0
  24. package/dist/core/mad-db/mad-db-recovery.js +27 -0
  25. package/dist/core/mad-db/mad-db-result-lifecycle.js +31 -102
  26. package/dist/core/mad-db/mad-db-runtime-profile.js +121 -0
  27. package/dist/core/mad-db/mad-db-target.js +64 -0
  28. package/dist/core/managed-assets/managed-assets-manifest.js +1 -1
  29. package/dist/core/pipeline-internals/runtime-core.js +40 -0
  30. package/dist/core/providers/glm/bench/glm-benchmark-types.js +1 -1
  31. package/dist/core/release/release-gate-dag.js +6 -5
  32. package/dist/core/routes.js +23 -8
  33. package/dist/core/version.js +1 -1
  34. package/dist/core/zellij/zellij-slot-column-anchor.js +5 -1
  35. package/dist/scripts/check-dist-runtime.js +3 -2
  36. package/dist/scripts/codex-0142-manifest-check.js +2 -1
  37. package/dist/scripts/mad-db-capability-check.js +13 -2
  38. package/dist/scripts/mad-db-command-check.js +7 -5
  39. package/dist/scripts/mad-db-hook-idempotency-check.js +21 -0
  40. package/dist/scripts/mad-db-ledger-check.js +2 -1
  41. package/dist/scripts/mad-db-lifecycle-hook-decision-check.js +5 -4
  42. package/dist/scripts/mad-db-mad-command-check.js +29 -16
  43. package/dist/scripts/mad-db-mcp-result-lifecycle-check.js +11 -10
  44. package/dist/scripts/mad-db-one-cycle-bounded-check.js +15 -18
  45. package/dist/scripts/mad-db-one-cycle-consumption-check.js +3 -3
  46. package/dist/scripts/mad-db-operation-lifecycle-blackbox.js +9 -9
  47. package/dist/scripts/mad-db-operation-lifecycle-ledger-check.js +6 -6
  48. package/dist/scripts/mad-db-parallel-lifecycle-check.js +24 -0
  49. package/dist/scripts/mad-db-policy-v2-check.js +20 -0
  50. package/dist/scripts/mad-db-priority-resolver-check.js +5 -5
  51. package/dist/scripts/mad-db-real-supabase-e2e.js +166 -0
  52. package/dist/scripts/mad-db-route-identity-check.js +28 -0
  53. package/dist/scripts/mad-db-runtime-profile-lifecycle-check.js +24 -0
  54. package/dist/scripts/mad-db-safety-conflict-matrix-check.js +3 -3
  55. package/dist/scripts/mad-db-skill-policy-snapshot-check.js +15 -0
  56. package/dist/scripts/release-dag-full-coverage-check.js +6 -0
  57. package/dist/scripts/release-triwiki-first-runner-blackbox.js +5 -1
  58. package/package.json +13 -3
  59. package/schemas/mad-db/mad-db-capability.schema.json +92 -19
@@ -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 || 'unknown_pending_tool_result',
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