sneakoscope 4.1.1 → 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +13 -10
  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/codex-control/codex-app-server-v2-client.js +86 -2
  9. package/dist/core/codex-control/codex-reliability-shield.js +26 -5
  10. package/dist/core/codex-control/codex-task-runner.js +7 -1
  11. package/dist/core/codex-control/model-call-concurrency.js +1 -1
  12. package/dist/core/commands/mad-db-command.js +146 -51
  13. package/dist/core/commands/mad-sks-command.js +15 -31
  14. package/dist/core/commands/qa-loop-command.js +23 -7
  15. package/dist/core/db-safety.js +35 -37
  16. package/dist/core/doctor/supabase-mcp-repair.js +2 -2
  17. package/dist/core/feature-registry.js +1 -1
  18. package/dist/core/fsx.js +1 -1
  19. package/dist/core/hooks-runtime.js +1 -1
  20. package/dist/core/init.js +5 -4
  21. package/dist/core/mad-db/mad-db-capability.js +203 -74
  22. package/dist/core/mad-db/mad-db-coordinator.js +287 -0
  23. package/dist/core/mad-db/mad-db-executor.js +156 -0
  24. package/dist/core/mad-db/mad-db-ledger.js +1 -1
  25. package/dist/core/mad-db/mad-db-lock.js +40 -0
  26. package/dist/core/mad-db/mad-db-operation-store.js +140 -0
  27. package/dist/core/mad-db/mad-db-policy-resolver.js +42 -22
  28. package/dist/core/mad-db/mad-db-policy.js +195 -0
  29. package/dist/core/mad-db/mad-db-postconditions.js +30 -0
  30. package/dist/core/mad-db/mad-db-recovery.js +27 -0
  31. package/dist/core/mad-db/mad-db-result-lifecycle.js +31 -102
  32. package/dist/core/mad-db/mad-db-runtime-profile.js +121 -0
  33. package/dist/core/mad-db/mad-db-target.js +64 -0
  34. package/dist/core/managed-assets/managed-assets-manifest.js +1 -1
  35. package/dist/core/pipeline-internals/runtime-core.js +40 -0
  36. package/dist/core/providers/glm/bench/glm-benchmark-types.js +1 -1
  37. package/dist/core/qa-loop/qa-app-server-driver.js +134 -0
  38. package/dist/core/qa-loop/qa-contract-v2.js +231 -0
  39. package/dist/core/qa-loop/qa-gate-v2.js +132 -0
  40. package/dist/core/qa-loop/qa-runtime-artifacts.js +53 -0
  41. package/dist/core/qa-loop/qa-surface-router.js +114 -0
  42. package/dist/core/qa-loop/qa-types.js +18 -0
  43. package/dist/core/qa-loop.js +83 -26
  44. package/dist/core/release/gate-manifest.js +1 -0
  45. package/dist/core/release/release-gate-dag.js +6 -5
  46. package/dist/core/release/sla-scheduler.js +1 -1
  47. package/dist/core/routes.js +42 -12
  48. package/dist/core/triwiki/triwiki-affected-graph.js +3 -2
  49. package/dist/core/version.js +1 -1
  50. package/dist/core/zellij/zellij-slot-column-anchor.js +5 -1
  51. package/dist/scripts/check-dist-runtime.js +3 -2
  52. package/dist/scripts/codex-0142-manifest-check.js +2 -1
  53. package/dist/scripts/codex-control-all-pipelines-check.js +1 -0
  54. package/dist/scripts/codex-control-model-capacity-fallback-check.js +53 -0
  55. package/dist/scripts/config-managed-merge-callsite-coverage-check.js +7 -1
  56. package/dist/scripts/loop-directive-check-lib.js +78 -1
  57. package/dist/scripts/mad-db-capability-check.js +13 -2
  58. package/dist/scripts/mad-db-command-check.js +7 -5
  59. package/dist/scripts/mad-db-hook-idempotency-check.js +21 -0
  60. package/dist/scripts/mad-db-ledger-check.js +2 -1
  61. package/dist/scripts/mad-db-lifecycle-hook-decision-check.js +5 -4
  62. package/dist/scripts/mad-db-mad-command-check.js +29 -16
  63. package/dist/scripts/mad-db-mcp-result-lifecycle-check.js +11 -10
  64. package/dist/scripts/mad-db-one-cycle-bounded-check.js +15 -18
  65. package/dist/scripts/mad-db-one-cycle-consumption-check.js +3 -3
  66. package/dist/scripts/mad-db-operation-lifecycle-blackbox.js +9 -9
  67. package/dist/scripts/mad-db-operation-lifecycle-ledger-check.js +6 -6
  68. package/dist/scripts/mad-db-parallel-lifecycle-check.js +24 -0
  69. package/dist/scripts/mad-db-policy-v2-check.js +20 -0
  70. package/dist/scripts/mad-db-priority-resolver-check.js +5 -5
  71. package/dist/scripts/mad-db-real-supabase-e2e.js +166 -0
  72. package/dist/scripts/mad-db-route-identity-check.js +28 -0
  73. package/dist/scripts/mad-db-runtime-profile-lifecycle-check.js +24 -0
  74. package/dist/scripts/mad-db-safety-conflict-matrix-check.js +3 -3
  75. package/dist/scripts/mad-db-skill-policy-snapshot-check.js +15 -0
  76. package/dist/scripts/qa-loop-app-server-driver-check.js +74 -0
  77. package/dist/scripts/qa-loop-surface-router-check.js +49 -0
  78. package/dist/scripts/release-check-dynamic-execute.js +1 -1
  79. package/dist/scripts/release-dag-full-coverage-check.js +6 -0
  80. package/dist/scripts/release-triwiki-first-runner-blackbox.js +5 -1
  81. package/dist/scripts/runtime-ts-rust-boundary-check.js +1 -1
  82. package/dist/scripts/triwiki-affected-graph-check.js +2 -2
  83. package/package.json +18 -5
  84. package/schemas/mad-db/mad-db-capability.schema.json +92 -19
@@ -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
@@ -1,30 +1,56 @@
1
- import { readMadDbCapability, isMadDbCapabilityActive } from './mad-db-capability.js';
2
- export const MAD_DB_POLICY_DECISION_SCHEMA = 'sks.mad-db-policy-decision.v1';
3
- export async function resolveMadDbMutationPolicy(root, state = {}, classification = {}) {
4
- const missionId = state?.mission_id ? String(state.mission_id) : null;
1
+ import { isMadDbCapabilityActive, readMadDbCapability } from './mad-db-capability.js';
2
+ import { activeMadDbAllowsSqlPlane, isMadDbControlPlaneDeniedTool, madDbOperationClassesFromClassification } from './mad-db-policy.js';
3
+ import { sha256 } from '../fsx.js';
4
+ export const MAD_DB_POLICY_DECISION_SCHEMA = 'sks.mad-db-policy-decision.v2';
5
+ export async function resolveMadDbMutationPolicy(root, state = {}, classification = {}, explicitCapability) {
6
+ const missionId = explicitCapability?.mission_id || state?.mad_db_capability_mission_id || state?.mission_id;
5
7
  if (!missionId)
6
8
  return inactive('mission_id_missing');
7
- const capability = await readMadDbCapability(root, missionId);
8
- if (!isMadDbCapabilityActive(capability))
9
- return inactive(capability?.consumed ? 'mad_db_capability_consumed' : 'mad_db_capability_inactive');
10
- if (!isDbMutationOrDbTool(classification))
11
- return inactive('not_a_database_mutation');
9
+ const capability = explicitCapability || await readMadDbCapability(root, String(missionId));
10
+ const validation = validateCapabilityBinding(capability, state, classification);
11
+ if (!validation.ok)
12
+ return inactive(validation.reason);
13
+ if (isMadDbControlPlaneDeniedTool(classification.toolName || classification.tool_name))
14
+ return inactive('mad_db_control_plane_tool_denied');
15
+ if (!activeMadDbAllowsSqlPlane(classification))
16
+ return inactive('not_a_database_sql_plane_mutation');
12
17
  return {
13
18
  schema: MAD_DB_POLICY_DECISION_SCHEMA,
14
19
  allowed: true,
15
20
  action: 'allow',
16
- mode: 'mad-db-break-glass',
21
+ mode: 'mad-db-sql-plane-active',
17
22
  priority: 0,
18
23
  priority_order: ['mad-db', 'mad-sks', 'sealed-contract', 'default-db-safety'],
19
- reasons: ['mad_db_one_cycle_break_glass_capability_active'],
24
+ reasons: ['mad_db_capability_v2_bound_sql_plane_authorized'],
20
25
  audit_required: true,
21
- mission_id: missionId,
26
+ mission_id: capability.mission_id,
22
27
  cycle_id: capability.cycle_id,
23
- operation_count: capability.operation_count || 0,
24
- max_operations: capability.max_operations || 20,
28
+ runtime_session_id: capability.runtime_session_id,
29
+ project_ref_hash: hashRef(capability.project_ref),
30
+ operation_classes: madDbOperationClassesFromClassification(classification),
31
+ counters: capability.counters,
25
32
  capability
26
33
  };
27
34
  }
35
+ export function validateCapabilityBinding(capability, state = {}, classification = {}) {
36
+ if (!capability)
37
+ return { ok: false, reason: 'mad_db_capability_missing' };
38
+ if (!isMadDbCapabilityActive(capability))
39
+ return { ok: false, reason: `mad_db_capability_${capability.status || 'inactive'}` };
40
+ if (!capability.project_ref)
41
+ return { ok: false, reason: 'mad_db_project_ref_missing' };
42
+ if (state?.mission_id && String(state.mission_id) !== capability.mission_id)
43
+ return { ok: false, reason: 'mad_db_mission_binding_mismatch' };
44
+ if (state?.mad_db_cycle_id && String(state.mad_db_cycle_id) !== capability.cycle_id)
45
+ return { ok: false, reason: 'mad_db_cycle_binding_mismatch' };
46
+ if (state?.mad_db_runtime_session_id && String(state.mad_db_runtime_session_id) !== capability.runtime_session_id)
47
+ return { ok: false, reason: 'mad_db_runtime_session_binding_mismatch' };
48
+ if (state?.mad_db_profile_sha256 && String(state.mad_db_profile_sha256) !== capability.transport.profile_sha256)
49
+ return { ok: false, reason: 'mad_db_profile_hash_mismatch' };
50
+ if (classification.toolReasons?.includes?.('dangerous_supabase_management_tool'))
51
+ return { ok: false, reason: 'mad_db_control_plane_tool_denied' };
52
+ return { ok: true, reason: 'ok' };
53
+ }
28
54
  function inactive(reason) {
29
55
  return {
30
56
  schema: MAD_DB_POLICY_DECISION_SCHEMA,
@@ -36,13 +62,7 @@ function inactive(reason) {
36
62
  audit_required: false
37
63
  };
38
64
  }
39
- function isDbMutationOrDbTool(classification = {}) {
40
- if (classification.level === 'write' || classification.level === 'destructive')
41
- return true;
42
- if (classification.toolReasons?.includes?.('database_tool'))
43
- return true;
44
- if (classification.toolReasons?.includes?.('migration_apply_tool'))
45
- return true;
46
- return false;
65
+ function hashRef(projectRef) {
66
+ return sha256(projectRef).slice(0, 16);
47
67
  }
48
68
  //# sourceMappingURL=mad-db-policy-resolver.js.map
@@ -0,0 +1,195 @@
1
+ export const MAD_DB_POLICY_SCHEMA = 'sks.mad-db-policy.v2';
2
+ export const MAD_DB_OPERATION_CLASSES = [
3
+ 'create',
4
+ 'alter',
5
+ 'drop',
6
+ 'drop_database_sql',
7
+ 'insert',
8
+ 'update',
9
+ 'delete',
10
+ 'all_row_update',
11
+ 'all_row_delete',
12
+ 'truncate',
13
+ 'migration_apply',
14
+ 'direct_execute_sql',
15
+ 'rls_policy_change',
16
+ 'function_or_trigger_change',
17
+ 'index_change',
18
+ 'unknown_sql_mutation'
19
+ ];
20
+ export const MAD_DB_SQL_PLANE_TOOL_NAMES = Object.freeze([
21
+ 'execute_sql',
22
+ 'apply_migration',
23
+ 'supabase.execute_sql',
24
+ 'supabase.apply_migration',
25
+ 'mcp__supabase__execute_sql',
26
+ 'mcp__supabase__apply_migration',
27
+ 'supabase_mad_db.execute_sql',
28
+ 'supabase_mad_db.apply_migration',
29
+ 'mcp__supabase_mad_db__execute_sql',
30
+ 'mcp__supabase_mad_db__apply_migration'
31
+ ]);
32
+ export const MAD_DB_CONTROL_PLANE_DENIED_TOOL_PATTERNS = Object.freeze([
33
+ 'delete_project',
34
+ 'pause_project',
35
+ 'restore_project',
36
+ 'create_project',
37
+ 'list_organizations',
38
+ 'get_organization',
39
+ 'billing',
40
+ 'organization',
41
+ 'credential',
42
+ 'access_token',
43
+ 'service_role',
44
+ 'delete_branch',
45
+ 'reset_branch',
46
+ 'merge_branch'
47
+ ]);
48
+ export const MAD_DB_POLICY = Object.freeze({
49
+ schema: MAD_DB_POLICY_SCHEMA,
50
+ default_mode: 'deny_mutations',
51
+ active_mode: {
52
+ sql_plane: 'allow_all_mutations',
53
+ control_plane: 'deny',
54
+ requires: [
55
+ 'capability_v2',
56
+ 'project_binding',
57
+ 'session_binding',
58
+ 'write_transport_ready',
59
+ 'not_expired'
60
+ ]
61
+ },
62
+ sql_plane_allowed: MAD_DB_OPERATION_CLASSES,
63
+ sql_plane_tools: MAD_DB_SQL_PLANE_TOOL_NAMES,
64
+ control_plane_denied: MAD_DB_CONTROL_PLANE_DENIED_TOOL_PATTERNS,
65
+ normal_supabase_mcp: {
66
+ read_only_required: true,
67
+ project_ref_required: true
68
+ },
69
+ runtime_profile: {
70
+ mission_local_only: true,
71
+ read_only_omitted_only_for_active_capability: true,
72
+ features: ['database']
73
+ },
74
+ ttl: {
75
+ default_ms: 15 * 60 * 1000,
76
+ hard_max_ms: 30 * 60 * 1000
77
+ }
78
+ });
79
+ export function madDbPolicySnapshot() {
80
+ return MAD_DB_POLICY;
81
+ }
82
+ export function isMadDbSqlPlaneToolName(toolName) {
83
+ const normalized = normalizeToolName(toolName);
84
+ if (!normalized)
85
+ return false;
86
+ return MAD_DB_SQL_PLANE_TOOL_NAMES.some((name) => normalized.endsWith(normalizeToolName(name)) || normalized.includes(normalizeToolName(name)));
87
+ }
88
+ export function isMadDbControlPlaneDeniedTool(toolName) {
89
+ const normalized = normalizeToolName(toolName);
90
+ if (!normalized)
91
+ return false;
92
+ return MAD_DB_CONTROL_PLANE_DENIED_TOOL_PATTERNS.some((pattern) => normalized.includes(normalizeToolName(pattern)));
93
+ }
94
+ export function normalizeToolName(toolName) {
95
+ return String(toolName || '')
96
+ .trim()
97
+ .toLowerCase()
98
+ .replace(/[^a-z0-9_.-]+/g, '_');
99
+ }
100
+ export function madDbOperationClassesFromClassification(classification = {}) {
101
+ const reasons = new Set([
102
+ ...stringArray(classification.reasons),
103
+ ...stringArray(classification.sql?.reasons),
104
+ ...stringArray(classification.command?.reasons),
105
+ ...stringArray(classification.toolReasons)
106
+ ]);
107
+ const out = new Set();
108
+ const toolName = classification.toolName || classification.tool_name || '';
109
+ if (isMadDbSqlPlaneToolName(toolName))
110
+ out.add('direct_execute_sql');
111
+ if (reasons.has('migration_apply_tool') || reasons.has('supabase_migration_apply'))
112
+ out.add('migration_apply');
113
+ if (hasAny(reasons, ['drop_database']))
114
+ out.add('drop_database_sql');
115
+ if (hasAny(reasons, ['drop_schema', 'drop_table', 'drop_view', 'drop_materialized_view', 'drop_extension', 'drop_policy', 'drop_statement', 'alter_table_drop']))
116
+ out.add('drop');
117
+ if (hasAny(reasons, ['truncate']))
118
+ out.add('truncate');
119
+ if (hasAny(reasons, ['insert_or_upsert']))
120
+ out.add('insert');
121
+ if (hasAny(reasons, ['update_with_where']))
122
+ out.add('update');
123
+ if (hasAny(reasons, ['update_without_where']))
124
+ out.add('all_row_update');
125
+ if (hasAny(reasons, ['delete_with_where']))
126
+ out.add('delete');
127
+ if (hasAny(reasons, ['delete_without_where']))
128
+ out.add('all_row_delete');
129
+ if (hasAny(reasons, ['schema_change', 'alter_table_rename']))
130
+ out.add('alter');
131
+ if (hasAny(reasons, ['create_or_replace']))
132
+ out.add('function_or_trigger_change');
133
+ const statements = stringArray(classification.sql?.statements);
134
+ for (const statement of statements) {
135
+ const normalized = statement.trim().toLowerCase();
136
+ if (/^create\s+(table|schema|view|materialized\s+view)/.test(normalized))
137
+ out.add('create');
138
+ if (/^create\s+(index|unique\s+index)/.test(normalized))
139
+ out.add('index_change');
140
+ if (/^drop\s+index/.test(normalized))
141
+ out.add('index_change');
142
+ if (/policy|row\s+level\s+security|rls/.test(normalized))
143
+ out.add('rls_policy_change');
144
+ if (/function|trigger|procedure/.test(normalized))
145
+ out.add('function_or_trigger_change');
146
+ }
147
+ if (!out.size && ['write', 'destructive', 'possible_db'].includes(String(classification.level || '')))
148
+ out.add('unknown_sql_mutation');
149
+ return [...out];
150
+ }
151
+ export function activeMadDbAllowsSqlPlane(classification = {}) {
152
+ const toolName = classification.toolName || classification.tool_name || '';
153
+ if (isMadDbControlPlaneDeniedTool(toolName))
154
+ return false;
155
+ if (classification.toolReasons?.includes?.('dangerous_supabase_management_tool'))
156
+ return false;
157
+ if (isMadDbSqlPlaneToolName(toolName))
158
+ return true;
159
+ return ['write', 'destructive', 'possible_db'].includes(String(classification.level || ''));
160
+ }
161
+ export function madDbSkillText(commandPrefix = 'sks') {
162
+ return `---
163
+ name: mad-db
164
+ description: First-class MadDB SQL-plane execution route for explicit $MAD-DB and ${commandPrefix} mad-db run|exec|apply-migration.
165
+ ---
166
+
167
+ Use only when the operator explicitly invokes $MAD-DB/$mad-db or ${commandPrefix} mad-db run|exec|apply-migration. This is the single approval boundary for the active MadDB cycle: execute SQL-plane mutations that the operator requested, including CREATE, ALTER, table/schema DROP, column add/drop/rename, INSERT, UPDATE, DELETE including all-row mutations, TRUNCATE, execute_sql, and apply_migration. Do not ask again for each DROP/TRUNCATE/all-row DELETE inside the same bound cycle.
168
+
169
+ Keep normal Supabase MCP configuration read-only. MadDB must create a mission-local ephemeral write-capable Supabase MCP profile bound to capability v2, project_ref, root, mission, thread/session, intent, runtime profile hash, TTL, and SQL-plane operation classes. Verify execute_sql and apply_migration availability before claiming readiness. Require actual tool results plus independent read-back verification before claiming success. Close/revoke the capability and runtime profile in finally and prove read-only restoration.
170
+
171
+ Still deny Supabase account/project/billing/credential control-plane actions, credential exfiltration, unrelated storage/edge-function/admin changes, persistent security weakening, and unrequested fallback implementation. Pair with db-safety-guard, Context7 evidence for MCP/API docs, route-local reflection, and Honest Mode.`;
172
+ }
173
+ export function dbSafetyGuardSkillText() {
174
+ return `---
175
+ name: db-safety-guard
176
+ description: Enforce Sneakoscope Codex database safety before using SQL, Supabase MCP, Postgres, Prisma, Drizzle, Knex, or migration commands.
177
+ ---
178
+
179
+ Rules:
180
+ - Default mode is read-only: do not run DROP, TRUNCATE, mass DELETE/UPDATE, db reset, db push, project deletion, branch reset/merge/delete, or RLS-disabling operations.
181
+ - Supabase MCP must be read-only and project-scoped by default.
182
+ - Live execute_sql writes are blocked unless a bound active MadDB capability v2 is present.
183
+ - Active MadDB is the explicit exception: SQL-plane mutations requested by $MAD-DB or sks mad-db run|exec|apply-migration are allowed and must be executed with read-back verification.
184
+ - Supabase project/account/billing/credential control-plane actions remain denied even in MadDB.
185
+ - If no active bound MadDB cycle exists, fall back to read-only only.`;
186
+ }
187
+ function stringArray(value) {
188
+ if (!Array.isArray(value))
189
+ return [];
190
+ return value.map((entry) => String(entry || '')).filter(Boolean);
191
+ }
192
+ function hasAny(values, keys) {
193
+ return keys.some((key) => values.has(key));
194
+ }
195
+ //# sourceMappingURL=mad-db-policy.js.map
@@ -0,0 +1,30 @@
1
+ import path from 'node:path';
2
+ import { nowIso, sha256, writeJsonAtomic } from '../fsx.js';
3
+ import { missionDir } from '../mission.js';
4
+ export async function runReadBackChecks(input) {
5
+ const rows = [];
6
+ for (const check of input.checks) {
7
+ const result = await input.executor.executeSql(check.query);
8
+ rows.push({
9
+ id: check.id,
10
+ query_sha256: sha256(check.query),
11
+ ok: result.ok === (check.expectOk ?? true),
12
+ result_digest: result.result_digest,
13
+ row_count: result.row_count
14
+ });
15
+ }
16
+ const proof = {
17
+ schema: 'sks.mad-db-read-back-proof.v1',
18
+ generated_at: nowIso(),
19
+ ok: rows.every((row) => row.ok),
20
+ checks: rows,
21
+ raw_rows_recorded: false
22
+ };
23
+ const proofPath = path.join(missionDir(input.root, input.missionId), 'mad-db', 'read-back-proof.json');
24
+ await writeJsonAtomic(proofPath, proof);
25
+ return { ...proof, proof_path: path.relative(input.root, proofPath).split(path.sep).join('/') };
26
+ }
27
+ export function readBackCheck(id, query) {
28
+ return { id, query, expectOk: true };
29
+ }
30
+ //# sourceMappingURL=mad-db-postconditions.js.map