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.
Files changed (94) hide show
  1. package/README.md +16 -3
  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/cli/router.js +6 -1
  8. package/dist/commands/doctor.js +272 -127
  9. package/dist/core/auto-review.js +1 -1
  10. package/dist/core/codex/agent-config-file-repair.js +43 -2
  11. package/dist/core/codex-app/codex-agent-role-sync.js +4 -4
  12. package/dist/core/codex-control/codex-0142-capability.js +51 -6
  13. package/dist/core/codex-control/codex-app-server-v2-client.js +2 -2
  14. package/dist/core/codex-native/codex-native-feature-broker.js +50 -0
  15. package/dist/core/codex-native/native-capability-postcheck.js +59 -16
  16. package/dist/core/codex-native/native-capability-repair-matrix.js +77 -13
  17. package/dist/core/commands/mad-db-command.js +146 -51
  18. package/dist/core/commands/mad-sks-command.js +51 -61
  19. package/dist/core/db-safety.js +35 -37
  20. package/dist/core/doctor/doctor-dirty-planner.js +9 -4
  21. package/dist/core/doctor/doctor-native-capability-repair.js +42 -7
  22. package/dist/core/doctor/doctor-readiness-matrix.js +9 -5
  23. package/dist/core/doctor/doctor-repair-postcheck.js +10 -1
  24. package/dist/core/doctor/doctor-transaction.js +1 -1
  25. package/dist/core/doctor/supabase-mcp-repair.js +2 -2
  26. package/dist/core/feature-registry.js +1 -1
  27. package/dist/core/fsx.js +1 -1
  28. package/dist/core/init.js +5 -4
  29. package/dist/core/mad-db/mad-db-capability.js +203 -74
  30. package/dist/core/mad-db/mad-db-coordinator.js +287 -0
  31. package/dist/core/mad-db/mad-db-executor.js +156 -0
  32. package/dist/core/mad-db/mad-db-ledger.js +1 -1
  33. package/dist/core/mad-db/mad-db-lock.js +40 -0
  34. package/dist/core/mad-db/mad-db-operation-store.js +140 -0
  35. package/dist/core/mad-db/mad-db-policy-resolver.js +42 -22
  36. package/dist/core/mad-db/mad-db-policy.js +195 -0
  37. package/dist/core/mad-db/mad-db-postconditions.js +30 -0
  38. package/dist/core/mad-db/mad-db-recovery.js +27 -0
  39. package/dist/core/mad-db/mad-db-result-lifecycle.js +31 -102
  40. package/dist/core/mad-db/mad-db-runtime-profile.js +121 -0
  41. package/dist/core/mad-db/mad-db-target.js +64 -0
  42. package/dist/core/managed-assets/managed-assets-manifest.js +14 -4
  43. package/dist/core/pipeline-internals/runtime-core.js +40 -0
  44. package/dist/core/providers/glm/bench/glm-benchmark-runner.js +4 -3
  45. package/dist/core/providers/glm/bench/glm-benchmark-types.js +1 -1
  46. package/dist/core/release/release-gate-dag.js +6 -5
  47. package/dist/core/routes.js +23 -8
  48. package/dist/core/update/update-migration-state.js +265 -50
  49. package/dist/core/update-check.js +6 -6
  50. package/dist/core/version.js +1 -1
  51. package/dist/core/zellij/zellij-launcher.js +17 -5
  52. package/dist/core/zellij/zellij-slot-column-anchor.js +5 -1
  53. package/dist/scripts/check-dist-runtime.js +3 -2
  54. package/dist/scripts/codex-0142-manifest-check.js +2 -1
  55. package/dist/scripts/config-managed-merge-callsite-coverage-check.js +6 -0
  56. package/dist/scripts/doctor-dirty-plan-check.js +1 -1
  57. package/dist/scripts/doctor-transaction-engine-check.js +1 -0
  58. package/dist/scripts/doctor-warning-only-not-blocker-check.js +18 -1
  59. package/dist/scripts/loop-directive-check-lib.js +2 -1
  60. package/dist/scripts/mad-db-capability-check.js +13 -2
  61. package/dist/scripts/mad-db-command-check.js +7 -5
  62. package/dist/scripts/mad-db-hook-idempotency-check.js +21 -0
  63. package/dist/scripts/mad-db-ledger-check.js +2 -1
  64. package/dist/scripts/mad-db-lifecycle-hook-decision-check.js +5 -4
  65. package/dist/scripts/mad-db-mad-command-check.js +29 -16
  66. package/dist/scripts/mad-db-mcp-result-lifecycle-check.js +11 -10
  67. package/dist/scripts/mad-db-one-cycle-bounded-check.js +15 -18
  68. package/dist/scripts/mad-db-one-cycle-consumption-check.js +3 -3
  69. package/dist/scripts/mad-db-operation-lifecycle-blackbox.js +9 -9
  70. package/dist/scripts/mad-db-operation-lifecycle-ledger-check.js +6 -6
  71. package/dist/scripts/mad-db-parallel-lifecycle-check.js +24 -0
  72. package/dist/scripts/mad-db-policy-v2-check.js +20 -0
  73. package/dist/scripts/mad-db-priority-resolver-check.js +5 -5
  74. package/dist/scripts/mad-db-real-supabase-e2e.js +166 -0
  75. package/dist/scripts/mad-db-route-identity-check.js +28 -0
  76. package/dist/scripts/mad-db-runtime-profile-lifecycle-check.js +24 -0
  77. package/dist/scripts/mad-db-safety-conflict-matrix-check.js +3 -3
  78. package/dist/scripts/mad-db-skill-policy-snapshot-check.js +15 -0
  79. package/dist/scripts/mad-sks-zellij-launch-check.js +7 -1
  80. package/dist/scripts/managed-role-manifest-parity-check.js +4 -1
  81. package/dist/scripts/naruto-real-parallelism-blackbox.js +17 -4
  82. package/dist/scripts/native-capability-postcheck-check.js +1 -0
  83. package/dist/scripts/native-capability-repair-matrix-check.js +2 -0
  84. package/dist/scripts/native-chrome-web-review-repair-check.js +1 -0
  85. package/dist/scripts/native-computer-use-repair-check.js +1 -0
  86. package/dist/scripts/release-dag-full-coverage-check.js +6 -0
  87. package/dist/scripts/release-triwiki-first-runner-blackbox.js +5 -1
  88. package/dist/scripts/sks-3-1-5-directive-check-lib.js +1 -1
  89. package/dist/scripts/sks-401-all-feature-regression-blackbox.js +1 -1
  90. package/dist/scripts/update-concurrent-lock-check.js +1 -0
  91. package/dist/scripts/update-first-command-migration-check.js +4 -3
  92. package/package.json +13 -2
  93. package/schemas/mad-db/mad-db-capability.schema.json +92 -19
  94. package/schemas/update-migration.schema.json +13 -1
@@ -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
@@ -0,0 +1,27 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { exists, nowIso, writeJsonAtomic } from '../fsx.js';
4
+ export async function quarantineStaleMadDbRuntimeProfiles(root) {
5
+ const missionsDir = path.join(root, '.sneakoscope', 'missions');
6
+ const quarantined = [];
7
+ if (!(await exists(missionsDir)))
8
+ return { schema: 'sks.mad-db-recovery.v1', ok: true, quarantined, checked_at: nowIso() };
9
+ const missions = await fs.readdir(missionsDir, { withFileTypes: true }).catch(() => []);
10
+ for (const mission of missions) {
11
+ if (!mission.isDirectory())
12
+ continue;
13
+ const runtime = path.join(missionsDir, mission.name, 'mad-db', 'runtime');
14
+ const profile = path.join(runtime, 'codex-mad-db.config.toml');
15
+ if (!(await exists(profile)))
16
+ continue;
17
+ const dest = `${profile}.quarantined-${Date.now()}`;
18
+ await fs.rename(profile, dest).catch(async () => {
19
+ await fs.rm(profile, { force: true }).catch(() => undefined);
20
+ });
21
+ quarantined.push(path.relative(root, dest).split(path.sep).join('/'));
22
+ }
23
+ const report = { schema: 'sks.mad-db-recovery.v1', ok: true, quarantined, checked_at: nowIso() };
24
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'mad-db-recovery.json'), report).catch(() => undefined);
25
+ return report;
26
+ }
27
+ //# sourceMappingURL=mad-db-recovery.js.map
@@ -1,92 +1,59 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { appendJsonlBounded, nowIso, readJson, readText, writeJsonAtomic } from '../fsx.js';
4
- import { missionDir } from '../mission.js';
5
- import { appendMadDbOperationLifecycle } from './mad-db-ledger.js';
6
- const PENDING_FILE = 'mad-db-lifecycle-pending.jsonl';
7
- const PENDING_LATEST_FILE = 'mad-db-lifecycle-pending.latest.json';
8
- export async function recordPendingMadDbLifecycleHook(root, missionId, hook) {
9
- const dir = missionDir(root, missionId);
10
- const row = {
11
- schema: 'sks.mad-db-lifecycle-pending.v1',
12
- ts: nowIso(),
13
- mission_id: missionId,
1
+ import { extractCanonicalToolCallId, transitionMadDbOperation } from './mad-db-operation-store.js';
2
+ export async function recordPendingMadDbLifecycleHook(_root, _missionId, hook) {
3
+ return {
4
+ schema: 'sks.mad-db-lifecycle-pending.v2',
5
+ pending_latest_removed: true,
14
6
  hook
15
7
  };
16
- await appendJsonlBounded(path.join(dir, PENDING_FILE), row);
17
- await writeJsonAtomic(path.join(dir, PENDING_LATEST_FILE), row).catch(() => undefined);
18
- return row;
19
8
  }
20
- export async function readLatestPendingMadDbLifecycleHook(root, missionId, payload = {}) {
21
- const dir = missionDir(root, missionId);
22
- const embedded = lifecycleHookFromUnknown(payload);
23
- if (embedded)
24
- return embedded;
25
- const latest = await readJson(path.join(dir, PENDING_LATEST_FILE), null).catch(() => null);
26
- const latestHook = lifecycleHookFromUnknown(latest?.hook);
27
- if (latestHook && hookMatchesPayload(latestHook, payload))
28
- return latestHook;
29
- const text = await readText(path.join(dir, PENDING_FILE), '').catch(() => '');
30
- const rows = String(text).split(/\r?\n/).map((line) => line.trim()).filter(Boolean).reverse();
31
- for (const line of rows.slice(0, 50)) {
32
- try {
33
- const row = JSON.parse(line);
34
- const hook = lifecycleHookFromUnknown(row?.hook);
35
- if (hook && hookMatchesPayload(hook, payload))
36
- return hook;
37
- }
38
- catch {
39
- // Ignore malformed pending rows.
40
- }
41
- }
42
- return null;
9
+ export async function readLatestPendingMadDbLifecycleHook(_root, _missionId, payload = {}) {
10
+ return lifecycleHookFromUnknown(payload);
43
11
  }
44
12
  export async function recordMadDbToolResult(input) {
45
- const terminalType = input.ok ? 'db_operation.succeeded' : 'db_operation.failed';
46
- if (await hasTerminalLifecycleEvent(input.root, input.missionId, input.hook.operation_id)) {
13
+ if (!input.hook.tool_call_id) {
47
14
  return {
48
- schema: 'sks.mad-db-tool-result-lifecycle.v1',
49
- ok: true,
15
+ schema: 'sks.mad-db-tool-result-lifecycle.v2',
16
+ ok: false,
50
17
  skipped: true,
51
- reason: 'mad_db_operation_terminal_event_already_recorded',
18
+ reason: 'tool_call_id_required_for_result_correlation',
52
19
  operation_id: input.hook.operation_id
53
20
  };
54
21
  }
55
- const event = await appendMadDbOperationLifecycle(input.root, input.missionId, {
56
- type: terminalType,
57
- operationId: input.hook.operation_id,
58
- cycleId: input.hook.cycle_id || null,
59
- mcpServer: input.hook.mcp_server || null,
60
- toolName: input.hook.tool_name || null,
61
- sqlHash: input.hook.sql_hash || null,
62
- destructive: input.hook.destructive === true,
63
- resultStatus: input.ok ? 'succeeded' : 'failed',
64
- rowCount: input.rowCount ?? null,
65
- error: input.error || null
22
+ const operation = await transitionMadDbOperation({
23
+ root: input.root,
24
+ missionId: input.missionId,
25
+ toolCallId: input.hook.tool_call_id,
26
+ state: input.ok ? 'succeeded' : 'failed',
27
+ result: { ok: input.ok, row_count: input.rowCount ?? null },
28
+ errorCode: input.ok ? null : input.error || 'tool_failed'
66
29
  });
67
- await markPendingHookResolved(input.root, input.missionId, input.hook, input.ok);
68
30
  return {
69
- schema: 'sks.mad-db-tool-result-lifecycle.v1',
70
- ok: true,
31
+ schema: 'sks.mad-db-tool-result-lifecycle.v2',
32
+ ok: Boolean(operation),
71
33
  skipped: false,
72
34
  operation_id: input.hook.operation_id,
35
+ tool_call_id: input.hook.tool_call_id,
73
36
  result_status: input.ok ? 'succeeded' : 'failed',
74
- event
37
+ operation
75
38
  };
76
39
  }
77
40
  export async function maybeRecordMadDbToolResultFromToolUse(input) {
78
41
  const payload = input.toolResult ?? input.toolCallPayload ?? {};
79
42
  const hook = lifecycleHookFromUnknown(input.decision)
80
43
  || lifecycleHookFromUnknown(input.toolCallPayload)
81
- || lifecycleHookFromUnknown(input.toolResult)
82
- || await readLatestPendingMadDbLifecycleHook(input.root, input.missionId, input.toolCallPayload || payload);
83
- if (!hook)
44
+ || lifecycleHookFromUnknown(input.toolResult);
45
+ const toolCallId = extractCanonicalToolCallId(payload) || hook?.tool_call_id || null;
46
+ if (!toolCallId && !hook)
84
47
  return null;
85
48
  const ok = !madDbToolUseFailed(payload);
86
49
  return recordMadDbToolResult({
87
50
  root: input.root,
88
51
  missionId: input.missionId,
89
- hook,
52
+ hook: hook || {
53
+ mission_id: input.missionId,
54
+ operation_id: `unknown-${toolCallId}`,
55
+ tool_call_id: toolCallId
56
+ },
90
57
  ok,
91
58
  rowCount: extractRowCount(payload),
92
59
  error: ok ? null : extractToolError(payload)
@@ -101,6 +68,7 @@ export function lifecycleHookFromUnknown(value) {
101
68
  return {
102
69
  mission_id: missionId,
103
70
  operation_id: operationId,
71
+ tool_call_id: stringOrNull(candidate?.tool_call_id || candidate?.toolCallId),
104
72
  cycle_id: stringOrNull(candidate?.cycle_id || candidate?.cycleId),
105
73
  tool_name: stringOrNull(candidate?.tool_name || candidate?.toolName),
106
74
  sql_hash: stringOrNull(candidate?.sql_hash || candidate?.sqlHash),
@@ -108,23 +76,6 @@ export function lifecycleHookFromUnknown(value) {
108
76
  destructive: candidate?.destructive === true
109
77
  };
110
78
  }
111
- function hookMatchesPayload(hook, payload) {
112
- if (!hook.tool_name)
113
- return true;
114
- const toolText = [
115
- payload.tool_name,
116
- payload.toolName,
117
- payload.name,
118
- payload.tool?.name,
119
- payload.server,
120
- payload.mcp_tool,
121
- payload.tool,
122
- payload.type
123
- ].filter(Boolean).join(' ').toLowerCase();
124
- if (!toolText)
125
- return true;
126
- return toolText.includes(String(hook.tool_name).toLowerCase()) || String(hook.tool_name).toLowerCase().includes(toolText);
127
- }
128
79
  function madDbToolUseFailed(payload = {}) {
129
80
  if (payload?.isError === true || payload?.tool_response?.isError === true || payload?.toolResponse?.isError === true || payload?.result?.isError === true)
130
81
  return true;
@@ -178,28 +129,6 @@ function extractToolError(payload = {}) {
178
129
  }
179
130
  return String(payload.error || payload.message || payload.stderr || payload.tool_response?.stderr || payload.toolResponse?.stderr || payload.result?.stderr || payload.result?.error || 'tool_failed');
180
131
  }
181
- async function hasTerminalLifecycleEvent(root, missionId, operationId) {
182
- const ledger = path.join(missionDir(root, missionId), 'mad-db-ledger.jsonl');
183
- const text = await readText(ledger, '').catch(() => '');
184
- return String(text).split(/\r?\n/).some((line) => {
185
- if (!line.includes(operationId))
186
- return false;
187
- return line.includes('db_operation.succeeded') || line.includes('db_operation.failed');
188
- });
189
- }
190
- async function markPendingHookResolved(root, missionId, hook, ok) {
191
- const dir = missionDir(root, missionId);
192
- const row = {
193
- schema: 'sks.mad-db-lifecycle-pending-resolution.v1',
194
- ts: nowIso(),
195
- mission_id: missionId,
196
- operation_id: hook.operation_id,
197
- cycle_id: hook.cycle_id || null,
198
- result_status: ok ? 'succeeded' : 'failed'
199
- };
200
- await appendJsonlBounded(path.join(dir, 'mad-db-lifecycle-resolved.jsonl'), row).catch(() => undefined);
201
- await fs.rm(path.join(dir, PENDING_LATEST_FILE), { force: true }).catch(() => undefined);
202
- }
203
132
  function stringOrNull(value) {
204
133
  const text = String(value || '').trim();
205
134
  return text ? text : null;
@@ -0,0 +1,121 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { exists, nowIso, readText, sha256, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
4
+ import { missionDir } from '../mission.js';
5
+ export async function createMadDbRuntimeProfile(input) {
6
+ const dir = path.join(missionDir(input.root, input.missionId), 'mad-db', 'runtime');
7
+ await fs.mkdir(dir, { recursive: true });
8
+ const url = madDbMcpUrl(input.projectRef);
9
+ const text = [
10
+ '[mcp_servers.supabase_mad_db]',
11
+ `url = "${url}"`,
12
+ 'enabled = true',
13
+ ''
14
+ ].join('\n');
15
+ const profilePath = path.join(dir, 'codex-mad-db.config.toml');
16
+ await writeTextAtomic(profilePath, text);
17
+ const profileHash = sha256(text);
18
+ const normalHash = await normalCodexConfigHash(input.root);
19
+ const profile = {
20
+ schema: 'sks.mad-db-runtime-profile.v1',
21
+ mission_id: input.missionId,
22
+ cycle_id: input.cycleId,
23
+ runtime_session_id: input.runtimeSessionId,
24
+ project_ref_hash: sha256(input.projectRef).slice(0, 16),
25
+ profile_path: path.relative(input.root, profilePath).split(path.sep).join('/'),
26
+ profile_sha256: profileHash,
27
+ server_url_redacted: redactSupabaseUrl(url),
28
+ server_url: url,
29
+ features: ['database'],
30
+ write_capable: true,
31
+ normal_config_hash_before: normalHash,
32
+ created_at: nowIso()
33
+ };
34
+ await writeJsonAtomic(path.join(dir, 'runtime-profile-manifest.json'), redactedRuntimeProfile(profile));
35
+ return profile;
36
+ }
37
+ export async function closeMadDbRuntimeProfile(input) {
38
+ const profilePath = input.profile?.profile_path ? path.join(input.root, input.profile.profile_path) : path.join(missionDir(input.root, input.missionId), 'mad-db', 'runtime', 'codex-mad-db.config.toml');
39
+ if (await exists(profilePath)) {
40
+ const quarantine = `${profilePath}.closed`;
41
+ await fs.rename(profilePath, quarantine).catch(async () => {
42
+ await fs.rm(profilePath, { force: true }).catch(() => undefined);
43
+ });
44
+ }
45
+ const proof = await verifyReadOnlyRestored(input.root, input.profile?.normal_config_hash_before || null, profilePath);
46
+ await writeJsonAtomic(path.join(missionDir(input.root, input.missionId), 'mad-db', 'runtime', 'read-only-restoration.json'), {
47
+ ...proof,
48
+ close_reason: input.reason || 'cycle_finally'
49
+ });
50
+ return proof;
51
+ }
52
+ export async function verifyReadOnlyRestored(root, normalConfigHashBefore, profilePath) {
53
+ const after = await normalCodexConfigHash(root);
54
+ const text = await readText(path.join(root, '.codex', 'config.toml'), '');
55
+ const persistentSupabaseReadOnly = persistentSupabaseConfigReadOnly(text);
56
+ const runtimeExists = profilePath ? await exists(profilePath) : false;
57
+ const blockers = [
58
+ ...(normalConfigHashBefore && after && normalConfigHashBefore !== after ? ['normal_codex_config_hash_changed'] : []),
59
+ ...(persistentSupabaseReadOnly ? [] : ['persistent_supabase_mcp_not_read_only']),
60
+ ...(runtimeExists ? ['runtime_write_profile_still_exists'] : [])
61
+ ];
62
+ return {
63
+ schema: 'sks.mad-db-read-only-restoration.v1',
64
+ checked_at: nowIso(),
65
+ ok: blockers.length === 0,
66
+ normal_config_hash_before: normalConfigHashBefore,
67
+ normal_config_hash_after: after,
68
+ persistent_supabase_read_only: persistentSupabaseReadOnly,
69
+ runtime_profile_exists: runtimeExists,
70
+ blockers
71
+ };
72
+ }
73
+ export function redactedRuntimeProfile(profile) {
74
+ const { server_url: _serverUrl, ...rest } = profile;
75
+ return rest;
76
+ }
77
+ export function madDbMcpUrl(projectRef) {
78
+ const params = new URLSearchParams();
79
+ params.set('project_ref', projectRef);
80
+ params.set('features', 'database');
81
+ return `https://mcp.supabase.com/mcp?${params.toString()}`;
82
+ }
83
+ export function redactSupabaseUrl(url) {
84
+ try {
85
+ const parsed = new URL(url);
86
+ const ref = parsed.searchParams.get('project_ref') || '';
87
+ if (ref)
88
+ parsed.searchParams.set('project_ref', `<hash:${sha256(ref).slice(0, 12)}>`);
89
+ for (const key of ['access_token', 'token', 'apikey', 'key', 'password']) {
90
+ if (parsed.searchParams.has(key))
91
+ parsed.searchParams.set(key, '<redacted>');
92
+ }
93
+ return parsed.toString();
94
+ }
95
+ catch {
96
+ return '<redacted-invalid-url>';
97
+ }
98
+ }
99
+ async function normalCodexConfigHash(root) {
100
+ const file = path.join(root, '.codex', 'config.toml');
101
+ if (!(await exists(file)))
102
+ return null;
103
+ return sha256(await readText(file, ''));
104
+ }
105
+ function persistentSupabaseConfigReadOnly(text) {
106
+ if (!/supabase|mcp\.supabase\.com/i.test(text))
107
+ return true;
108
+ const urls = [...String(text).matchAll(/https:\/\/mcp\.supabase\.com\/mcp[^"'\s)>,]*/gi)].map((match) => match[0] || '');
109
+ if (!urls.length)
110
+ return /read[_-]?only\s*=\s*true|access_mode\s*=\s*"read-only"|--read-only/.test(text);
111
+ return urls.every((url) => {
112
+ try {
113
+ const parsed = new URL(url);
114
+ return parsed.searchParams.get('read_only') === 'true';
115
+ }
116
+ catch {
117
+ return false;
118
+ }
119
+ });
120
+ }
121
+ //# sourceMappingURL=mad-db-runtime-profile.js.map