sneakoscope 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -3
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/cli/command-registry.js +1 -1
- package/dist/cli/router.js +6 -1
- package/dist/commands/doctor.js +272 -127
- package/dist/core/auto-review.js +1 -1
- package/dist/core/codex/agent-config-file-repair.js +43 -2
- package/dist/core/codex-app/codex-agent-role-sync.js +4 -4
- package/dist/core/codex-control/codex-0142-capability.js +51 -6
- package/dist/core/codex-control/codex-app-server-v2-client.js +2 -2
- package/dist/core/codex-native/codex-native-feature-broker.js +50 -0
- package/dist/core/codex-native/native-capability-postcheck.js +59 -16
- package/dist/core/codex-native/native-capability-repair-matrix.js +77 -13
- package/dist/core/commands/mad-db-command.js +146 -51
- package/dist/core/commands/mad-sks-command.js +51 -61
- package/dist/core/db-safety.js +35 -37
- package/dist/core/doctor/doctor-dirty-planner.js +9 -4
- package/dist/core/doctor/doctor-native-capability-repair.js +42 -7
- package/dist/core/doctor/doctor-readiness-matrix.js +9 -5
- package/dist/core/doctor/doctor-repair-postcheck.js +10 -1
- package/dist/core/doctor/doctor-transaction.js +1 -1
- package/dist/core/doctor/supabase-mcp-repair.js +2 -2
- package/dist/core/feature-registry.js +1 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/init.js +5 -4
- package/dist/core/mad-db/mad-db-capability.js +203 -74
- package/dist/core/mad-db/mad-db-coordinator.js +287 -0
- package/dist/core/mad-db/mad-db-executor.js +156 -0
- package/dist/core/mad-db/mad-db-ledger.js +1 -1
- package/dist/core/mad-db/mad-db-lock.js +40 -0
- package/dist/core/mad-db/mad-db-operation-store.js +140 -0
- package/dist/core/mad-db/mad-db-policy-resolver.js +42 -22
- package/dist/core/mad-db/mad-db-policy.js +195 -0
- package/dist/core/mad-db/mad-db-postconditions.js +30 -0
- package/dist/core/mad-db/mad-db-recovery.js +27 -0
- package/dist/core/mad-db/mad-db-result-lifecycle.js +31 -102
- package/dist/core/mad-db/mad-db-runtime-profile.js +121 -0
- package/dist/core/mad-db/mad-db-target.js +64 -0
- package/dist/core/managed-assets/managed-assets-manifest.js +14 -4
- package/dist/core/pipeline-internals/runtime-core.js +40 -0
- package/dist/core/providers/glm/bench/glm-benchmark-runner.js +4 -3
- package/dist/core/providers/glm/bench/glm-benchmark-types.js +1 -1
- package/dist/core/release/release-gate-dag.js +6 -5
- package/dist/core/routes.js +23 -8
- package/dist/core/update/update-migration-state.js +265 -50
- package/dist/core/update-check.js +6 -6
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-launcher.js +17 -5
- package/dist/core/zellij/zellij-slot-column-anchor.js +5 -1
- package/dist/scripts/check-dist-runtime.js +3 -2
- package/dist/scripts/codex-0142-manifest-check.js +2 -1
- package/dist/scripts/config-managed-merge-callsite-coverage-check.js +6 -0
- package/dist/scripts/doctor-dirty-plan-check.js +1 -1
- package/dist/scripts/doctor-transaction-engine-check.js +1 -0
- package/dist/scripts/doctor-warning-only-not-blocker-check.js +18 -1
- package/dist/scripts/loop-directive-check-lib.js +2 -1
- package/dist/scripts/mad-db-capability-check.js +13 -2
- package/dist/scripts/mad-db-command-check.js +7 -5
- package/dist/scripts/mad-db-hook-idempotency-check.js +21 -0
- package/dist/scripts/mad-db-ledger-check.js +2 -1
- package/dist/scripts/mad-db-lifecycle-hook-decision-check.js +5 -4
- package/dist/scripts/mad-db-mad-command-check.js +29 -16
- package/dist/scripts/mad-db-mcp-result-lifecycle-check.js +11 -10
- package/dist/scripts/mad-db-one-cycle-bounded-check.js +15 -18
- package/dist/scripts/mad-db-one-cycle-consumption-check.js +3 -3
- package/dist/scripts/mad-db-operation-lifecycle-blackbox.js +9 -9
- package/dist/scripts/mad-db-operation-lifecycle-ledger-check.js +6 -6
- package/dist/scripts/mad-db-parallel-lifecycle-check.js +24 -0
- package/dist/scripts/mad-db-policy-v2-check.js +20 -0
- package/dist/scripts/mad-db-priority-resolver-check.js +5 -5
- package/dist/scripts/mad-db-real-supabase-e2e.js +166 -0
- package/dist/scripts/mad-db-route-identity-check.js +28 -0
- package/dist/scripts/mad-db-runtime-profile-lifecycle-check.js +24 -0
- package/dist/scripts/mad-db-safety-conflict-matrix-check.js +3 -3
- package/dist/scripts/mad-db-skill-policy-snapshot-check.js +15 -0
- package/dist/scripts/mad-sks-zellij-launch-check.js +7 -1
- package/dist/scripts/managed-role-manifest-parity-check.js +4 -1
- package/dist/scripts/naruto-real-parallelism-blackbox.js +17 -4
- package/dist/scripts/native-capability-postcheck-check.js +1 -0
- package/dist/scripts/native-capability-repair-matrix-check.js +2 -0
- package/dist/scripts/native-chrome-web-review-repair-check.js +1 -0
- package/dist/scripts/native-computer-use-repair-check.js +1 -0
- package/dist/scripts/release-dag-full-coverage-check.js +6 -0
- package/dist/scripts/release-triwiki-first-runner-blackbox.js +5 -1
- package/dist/scripts/sks-3-1-5-directive-check-lib.js +1 -1
- package/dist/scripts/sks-401-all-feature-regression-blackbox.js +1 -1
- package/dist/scripts/update-concurrent-lock-check.js +1 -0
- package/dist/scripts/update-first-command-migration-check.js +4 -3
- package/package.json +13 -2
- package/schemas/mad-db/mad-db-capability.schema.json +92 -19
- package/schemas/update-migration.schema.json +13 -1
|
@@ -1,30 +1,56 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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-
|
|
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: ['
|
|
24
|
+
reasons: ['mad_db_capability_v2_bound_sql_plane_authorized'],
|
|
20
25
|
audit_required: true,
|
|
21
|
-
mission_id:
|
|
26
|
+
mission_id: capability.mission_id,
|
|
22
27
|
cycle_id: capability.cycle_id,
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
40
|
-
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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(
|
|
21
|
-
|
|
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
|
-
|
|
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.
|
|
49
|
-
ok:
|
|
15
|
+
schema: 'sks.mad-db-tool-result-lifecycle.v2',
|
|
16
|
+
ok: false,
|
|
50
17
|
skipped: true,
|
|
51
|
-
reason: '
|
|
18
|
+
reason: 'tool_call_id_required_for_result_correlation',
|
|
52
19
|
operation_id: input.hook.operation_id
|
|
53
20
|
};
|
|
54
21
|
}
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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.
|
|
70
|
-
ok:
|
|
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
|
-
|
|
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
|
-
|
|
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
|