sneakoscope 4.1.1 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +12 -9
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/cli/command-registry.js +1 -1
  7. package/dist/core/auto-review.js +1 -1
  8. package/dist/core/commands/mad-db-command.js +146 -51
  9. package/dist/core/commands/mad-sks-command.js +15 -31
  10. package/dist/core/db-safety.js +35 -37
  11. package/dist/core/doctor/supabase-mcp-repair.js +2 -2
  12. package/dist/core/feature-registry.js +1 -1
  13. package/dist/core/fsx.js +1 -1
  14. package/dist/core/init.js +5 -4
  15. package/dist/core/mad-db/mad-db-capability.js +203 -74
  16. package/dist/core/mad-db/mad-db-coordinator.js +287 -0
  17. package/dist/core/mad-db/mad-db-executor.js +156 -0
  18. package/dist/core/mad-db/mad-db-ledger.js +1 -1
  19. package/dist/core/mad-db/mad-db-lock.js +40 -0
  20. package/dist/core/mad-db/mad-db-operation-store.js +140 -0
  21. package/dist/core/mad-db/mad-db-policy-resolver.js +42 -22
  22. package/dist/core/mad-db/mad-db-policy.js +195 -0
  23. package/dist/core/mad-db/mad-db-postconditions.js +30 -0
  24. package/dist/core/mad-db/mad-db-recovery.js +27 -0
  25. package/dist/core/mad-db/mad-db-result-lifecycle.js +31 -102
  26. package/dist/core/mad-db/mad-db-runtime-profile.js +121 -0
  27. package/dist/core/mad-db/mad-db-target.js +64 -0
  28. package/dist/core/managed-assets/managed-assets-manifest.js +1 -1
  29. package/dist/core/pipeline-internals/runtime-core.js +40 -0
  30. package/dist/core/providers/glm/bench/glm-benchmark-types.js +1 -1
  31. package/dist/core/release/release-gate-dag.js +6 -5
  32. package/dist/core/routes.js +23 -8
  33. package/dist/core/version.js +1 -1
  34. package/dist/core/zellij/zellij-slot-column-anchor.js +5 -1
  35. package/dist/scripts/check-dist-runtime.js +3 -2
  36. package/dist/scripts/codex-0142-manifest-check.js +2 -1
  37. package/dist/scripts/mad-db-capability-check.js +13 -2
  38. package/dist/scripts/mad-db-command-check.js +7 -5
  39. package/dist/scripts/mad-db-hook-idempotency-check.js +21 -0
  40. package/dist/scripts/mad-db-ledger-check.js +2 -1
  41. package/dist/scripts/mad-db-lifecycle-hook-decision-check.js +5 -4
  42. package/dist/scripts/mad-db-mad-command-check.js +29 -16
  43. package/dist/scripts/mad-db-mcp-result-lifecycle-check.js +11 -10
  44. package/dist/scripts/mad-db-one-cycle-bounded-check.js +15 -18
  45. package/dist/scripts/mad-db-one-cycle-consumption-check.js +3 -3
  46. package/dist/scripts/mad-db-operation-lifecycle-blackbox.js +9 -9
  47. package/dist/scripts/mad-db-operation-lifecycle-ledger-check.js +6 -6
  48. package/dist/scripts/mad-db-parallel-lifecycle-check.js +24 -0
  49. package/dist/scripts/mad-db-policy-v2-check.js +20 -0
  50. package/dist/scripts/mad-db-priority-resolver-check.js +5 -5
  51. package/dist/scripts/mad-db-real-supabase-e2e.js +166 -0
  52. package/dist/scripts/mad-db-route-identity-check.js +28 -0
  53. package/dist/scripts/mad-db-runtime-profile-lifecycle-check.js +24 -0
  54. package/dist/scripts/mad-db-safety-conflict-matrix-check.js +3 -3
  55. package/dist/scripts/mad-db-skill-policy-snapshot-check.js +15 -0
  56. package/dist/scripts/release-dag-full-coverage-check.js +6 -0
  57. package/dist/scripts/release-triwiki-first-runner-blackbox.js +5 -1
  58. package/package.json +13 -3
  59. package/schemas/mad-db/mad-db-capability.schema.json +92 -19
package/dist/core/init.js CHANGED
@@ -13,6 +13,7 @@ import { CODEX_HOOK_EVENT_STATE_KEYS } from './codex-compat/codex-hook-events.js
13
13
  import { codexCommandHookCurrentHash } from './codex-hooks/codex-hook-hash.js';
14
14
  import { buildSksCoreSkillManifest, isCoreSkillName } from './codex-native/core-skill-manifest.js';
15
15
  import { syncCoreSkillsIntegrity } from './codex-native/core-skill-integrity.js';
16
+ import { dbSafetyGuardSkillText, madDbSkillText } from './mad-db/mad-db-policy.js';
16
17
  const REFLECTION_MEMORY_PATH = '.sneakoscope/memory/q2_facts/post-route-reflection.md';
17
18
  const SKS_GENERATED_GIT_PATTERNS = [
18
19
  '.sneakoscope/missions/',
@@ -351,7 +352,7 @@ export async function initProject(root, opts = {}) {
351
352
  state: 'git-common-dir/sks-version-state.json'
352
353
  }
353
354
  },
354
- database_safety: 'default_safe; $MAD-SKS scoped permission profile is centralized in src/core/permission-gates.ts (emitted as dist/core/permission-gates.js) and keeps catastrophic safeguards active',
355
+ database_safety: 'default_safe; $MAD-SKS scoped permission profile keeps catastrophic safeguards active; first-class $MAD-DB is the explicit SQL-plane execution exception with mission-local write transport and read-back proof',
355
356
  gx_renderer: 'deterministic_svg_html'
356
357
  };
357
358
  await writeJsonAtomic(manifestPath, manifest);
@@ -826,7 +827,7 @@ export async function initProject(root, opts = {}) {
826
827
  // them relocated into the home config by the splitter, re-triggering the warning.
827
828
  {
828
829
  table: 'auto_review',
829
- text: '[auto_review]\npolicy = "In MAD launches, allow live-server work, normal DB writes, Supabase MCP DB writes, direct execute SQL, schema cleanup, and migration application for the active invocation. Deny only catastrophic database wipes, all-row value deletion/update, dangerous project or branch management, credential exfiltration, persistent security weakening, broad unrelated file deletion, and unrequested fallback implementation code."'
830
+ text: '[auto_review]\npolicy = "In MAD-SKS launches, allow only the scoped non-MadDB high-risk surfaces approved for the active invocation and keep catastrophic DB wipe/all-row safeguards active. In first-class MAD-DB cycles, the explicit $MAD-DB or sks mad-db run|exec|apply-migration invocation is the SQL-plane approval boundary: execute requested execute_sql/apply_migration mutations with mission-local write transport, read-back proof, and final read-only restoration. Supabase project/account/billing/credential control-plane actions remain denied."'
830
831
  }
831
832
  ];
832
833
  }
@@ -1058,7 +1059,7 @@ export async function installSkills(root) {
1058
1059
  'research': `---\nname: research\ndescription: Dollar-command route for $Research or $research frontier discovery workflows.\n---\n\nUse when the user invokes $Research/$research or asks for research, hypotheses, new mechanisms, falsification, or testable predictions. Prefer sks research prepare and sks research run. Research is not an implementation route: do not edit repository source, docs, package metadata, generated skills, or harness files; write only route-local mission artifacts under .sneakoscope/missions/<mission-id>/. Run the genius-lens agent council with named persona-inspired cognitive roles: Einstein Agent, Feynman Agent, Turing Agent, von Neumann Agent, and Skeptic Agent. These are lenses only; do not impersonate the historical people. Every Research agent ledger row must include display_name, persona, persona_boundary, effort=xhigh, reasoning_effort=xhigh, service_tier when available, one literal "Eureka!" idea, falsifiers, cheap_probes, and challenge_or_response before synthesis. This is not a fixed three-cycle route: repeat source gathering, Eureka ideas, evidence-bound debate, falsification, and synthesis pressure until every agent records final agreement, or until the explicit max-cycle safety cap pauses with an unpassed gate. Create research-source-skill.md as a route-local Skill Creator artifact, then maximize layered public web/source search across latest papers, official/government or leading-institution data, standards/primary docs, current news, public discourse, developer/practitioner sources, traditional background sources, and counterevidence before synthesis. Record research-source-skill.md, source-ledger.json, agent-ledger.json, debate-ledger.json, novelty-ledger.json, falsification-ledger.json, research-report.md, research-paper.md, genius-opinion-summary.md, and research-gate.json. debate-ledger.json must include consensus_iterations, unanimous_consensus, and per-agent agreements; research-gate.json cannot pass until unanimous_consensus=true with every agent agreement recorded. Context7 is optional and only needed when the research topic depends on external package/API/framework docs; do not use it as the default research evidence layer. Normal Research may take one or two hours when needed; favor real source collection, cross-layer comparison, falsification, and a concise paper manuscript over speed. Do not use --mock except for selftests or dry harness checks; if live source execution is unavailable, record a blocker and keep the gate unpassed. Do not use for ordinary code edits.\n`,
1059
1060
  'autoresearch': `---\nname: autoresearch\ndescription: Dollar-command route for $AutoResearch or $autoresearch iterative experiment loops.\n---\n\nUse for $AutoResearch, iterative improvement, SEO/GEO, ranking, workflow, benchmark, or experiments. Define program, hypothesis, experiment, metric, keep/discard, falsification, next step, and Honest Mode. Load seo-geo-optimizer for README/npm/GitHub/schema/AI-search work.\n`,
1060
1061
  'db': `---\nname: db\ndescription: Dollar-command route for $DB or $db database and Supabase safety checks.\n---\n\nUse when the user invokes $DB/$db or the task touches SQL, Supabase, Postgres, migrations, Prisma, Drizzle, Knex, MCP database tools, or production data. Run or follow sks db policy, sks db scan, sks db classify, and sks db check. Destructive database operations remain forbidden.\n`,
1061
- 'mad-db': `---\nname: mad-db\ndescription: One-cycle Mad-DB break-glass route alias for $MAD-DB and $mad-db database safety work.\n---\n\nUse only when the user explicitly invokes $MAD-DB/$mad-db or asks for Mad-DB visibility. Treat it as a DB safety route with one-cycle break-glass controls, not as a general permanent DB unlock. Prefer \`sks mad-db status --json\` for inspection, \`sks mad-db enable --ack "I AUTHORIZE ONE-CYCLE DB BREAK-GLASS" [--mission latest|new|M-...] --json\` only after explicit user authorization, and \`sks mad-db revoke --mission <id|latest> --json\` to close the cycle. Keep catastrophic safeguards active: whole database/schema/table removal, truncate, all-row delete/update, reset, dangerous project/branch management, credential exfiltration, persistent security weakening, and unrequested fallback implementation remain blocked. Pair with db-safety-guard, Context7 evidence when external DB/API docs are involved, route-local reflection, and Honest Mode.\n`,
1062
+ 'mad-db': `${madDbSkillText()}\n`,
1062
1063
  'mad-sks': `---\nname: mad-sks\ndescription: Explicit high-risk authorization modifier for $MAD-SKS scoped permission widening across approved target-project surfaces.\n---\n\nUse only when the user explicitly invokes $MAD-SKS or top-level sks --mad. It can be combined with another route, such as $MAD-SKS $Team or $DB ... $MAD-SKS; in that case the other command remains the primary workflow and MAD-SKS is only the temporary permission grant. The widened permission applies only while the active mission gate is open, must be deactivated when the task ends, and can open approved scopes such as target-project file writes, shell commands, package installs, local service control, network operations, browser/Computer Use workflows, generated assets, file permissions, migrations, Supabase MCP database writes, column/schema cleanup, direct execute SQL, and normal targeted DB writes. Keep catastrophic safeguards active: whole database/schema/table removal, truncate, all-row delete/update, reset, dangerous project/branch management, credential exfiltration, persistent security weakening, destructive delete without explicit confirmation, and unrequested fallback implementation remain blocked. Do not carry MAD-SKS permission into later prompts or routes. The permission profile source is centralized in src/core/permission-gates.ts and emitted as dist/core/permission-gates.js so skill/hook/MCP-style gates share one decision function.\n`,
1063
1064
  'gx': `---\nname: gx\ndescription: Dollar-command route for $GX or $gx deterministic GX visual context cartridges.\n---\n\nUse when the user invokes $GX/$gx or asks for architecture/context visualization through SKS. Prefer sks gx init, render, validate, drift, and snapshot. vgraph.json remains the source of truth.\n`,
1064
1065
  'help': `---\nname: help\ndescription: Dollar-command route for $Help or $help explaining installed SKS commands and workflows.\n---\n\nUse when the user invokes $Help/$help or asks what commands exist. Prefer concise output from sks commands, sks usage <topic>, sks quickstart, sks aliases, and sks codex-app.\n`,
@@ -1073,7 +1074,7 @@ export async function installSkills(root) {
1073
1074
  'autoresearch-loop': `---\nname: autoresearch-loop\ndescription: Iterative AutoResearch-style loop for open-ended improvement, discovery, prompt, ranking, SEO/GEO, and workflow-quality tasks.\n---\n\nUse for research, ranking, prompt/workflow improvement, benchmark gains, or repeated refinement. Loop: program, hypothesis, smallest falsifying experiment, metric, keep/discard, falsify, next step. Keep a ledger and do not claim improvement without evidence.\n`,
1074
1075
  'hproof-claim-ledger': `---\nname: hproof-claim-ledger\ndescription: Extract atomic claims and classify support status.\n---\n\nEvery factual statement must become an atomic claim. Unsupported critical claims cannot be used for implementation or final answer. Database claims require DB safety evidence.\n`,
1075
1076
  'hproof-evidence-bind': `---\nname: hproof-evidence-bind\ndescription: Bind claims to code, tests, decision contract, vgraph, beta, wiki, or GX render evidence.\n---\n\nEvidence priority: current code/tests, decision-contract.json, vgraph.json, beta.json, GX snapshot/render metadata, LLM Wiki coordinate index, user prompt. Database claims must respect .sneakoscope/db-safety.json. Wiki claims should carry id, hash, source path, and RGBA/trig coordinate anchors so they can be hydrated instead of treated as unsupported summaries.\n`,
1076
- 'db-safety-guard': `---\nname: db-safety-guard\ndescription: Enforce Sneakoscope Codex database safety before using SQL, Supabase MCP, Postgres, Prisma, Drizzle, Knex, or migration commands.\n---\n\nRules:\n- Never run DROP, TRUNCATE, mass DELETE/UPDATE, db reset, db push, project deletion, branch reset/merge/delete, or RLS-disabling operations.\n- Supabase MCP must be read-only and project-scoped by default.\n- Live writes through execute_sql are blocked; use migration files and only local/preview branches if explicitly allowed.\n- Production writes are forbidden.\n- If unsure, read-only only.\n`,
1077
+ 'db-safety-guard': `${dbSafetyGuardSkillText()}\n`,
1077
1078
  'gx-visual-generate': `---\nname: gx-visual-generate\ndescription: Render a deterministic SVG/HTML visual sheet from vgraph.json and beta.json.\n---\n\nUse sks gx render. vgraph.json is source of truth; renders embed source hash and RGBA wiki anchors.\n`,
1078
1079
  'gx-visual-read': `---\nname: gx-visual-read\ndescription: Read a Sneakoscope Codex deterministic visual sheet and produce context notes.\n---\n\nExtract nodes, edges, invariants, tests, risks, uncertainties, and RGBA anchors from source/render/snapshot. Do not infer hidden nodes.\n`,
1079
1080
  'gx-visual-validate': `---\nname: gx-visual-validate\ndescription: Validate render metadata against vgraph.json and beta.json.\n---\n\nRun sks gx validate and drift; fail stale or incomplete hashes, nodes, edges, invariants, or anchors.\n`,
@@ -1,48 +1,91 @@
1
1
  import path from 'node:path';
2
- import { appendJsonlBounded, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
2
+ import { appendJsonlBounded, nowIso, readJson, sha256, writeJsonAtomic } from '../fsx.js';
3
3
  import { findLatestMission, missionDir } from '../mission.js';
4
- export const MAD_DB_CAPABILITY_SCHEMA = 'sks.mad-db-capability.v1';
4
+ import { withMadDbLock } from './mad-db-lock.js';
5
+ import { MAD_DB_POLICY } from './mad-db-policy.js';
6
+ export const MAD_DB_CAPABILITY_SCHEMA_V1 = 'sks.mad-db-capability.v1';
7
+ export const MAD_DB_CAPABILITY_SCHEMA = 'sks.mad-db-capability.v2';
5
8
  export const MAD_DB_CAPABILITY_FILE = 'mad-db-capability.json';
6
9
  export const MAD_DB_ACK = 'I AUTHORIZE ONE-CYCLE DB BREAK-GLASS';
7
- export const MAD_DB_MAX_TTL_MS = 2 * 60 * 60 * 1000;
10
+ export const MAD_DB_DEFAULT_TTL_MS = MAD_DB_POLICY.ttl.default_ms;
11
+ export const MAD_DB_MAX_TTL_MS = MAD_DB_POLICY.ttl.hard_max_ms;
8
12
  export async function createMadDbCapability(root, input) {
9
13
  if (input.ack !== MAD_DB_ACK)
10
14
  throw new Error('mad_db_ack_phrase_mismatch');
11
15
  const createdAt = nowIso();
12
- const ttlMs = Math.min(MAD_DB_MAX_TTL_MS, Math.max(1, Math.floor(Number(input.ttlMs || MAD_DB_MAX_TTL_MS))));
16
+ const ttlMs = Math.min(MAD_DB_MAX_TTL_MS, Math.max(1, Math.floor(Number(input.ttlMs || MAD_DB_DEFAULT_TTL_MS))));
17
+ const projectRef = String(input.projectRef || process.env.SKS_MAD_DB_PROJECT_REF || process.env.SKS_MAD_DB_E2E_PROJECT_REF || 'fixture-project-ref').trim();
18
+ const profilePath = input.profilePath || '.sneakoscope/missions/<mission>/mad-db/runtime/codex-mad-db.config.toml';
19
+ const profileSha256 = input.profileSha256 || sha256(`${input.missionId}:${projectRef}:placeholder-profile`);
13
20
  const capability = {
14
21
  schema: MAD_DB_CAPABILITY_SCHEMA,
22
+ revision: 1,
15
23
  mission_id: input.missionId,
16
24
  cycle_id: input.cycleId || `mad-db-${Date.now().toString(36)}`,
17
- enabled: true,
18
- created_at: createdAt,
25
+ project_root_hash: sha256(path.resolve(input.cwd || root)).slice(0, 24),
26
+ project_ref: projectRef,
27
+ target_environment: input.targetEnvironment || 'production',
28
+ allowed_schemas: input.allowedSchemas?.length ? input.allowedSchemas : ['public'],
29
+ codex_thread_id: input.codexThreadId ?? null,
30
+ runtime_session_id: input.runtimeSessionId || `mad-db-session-${Date.now().toString(36)}`,
31
+ operator_intent_hash: sha256(input.operatorIntent || input.ack || 'mad-db').slice(0, 32),
32
+ operator_ack_hash: sha256(input.ack).slice(0, 32),
33
+ scope: {
34
+ sql_plane: 'all_mutations',
35
+ control_plane: 'deny',
36
+ operations: input.operations?.length ? input.operations : [...MAD_DB_POLICY.sql_plane_allowed]
37
+ },
38
+ transport: {
39
+ profile_path: profilePath,
40
+ profile_sha256: profileSha256,
41
+ server_url_redacted: input.serverUrlRedacted || 'https://mcp.supabase.com/mcp?project_ref=<redacted>&features=database',
42
+ features: ['database'],
43
+ write_capable: true
44
+ },
45
+ issued_at: createdAt,
19
46
  expires_at: new Date(Date.now() + ttlMs).toISOString(),
20
- one_cycle_only: true,
21
- priority: 'highest',
22
- scope: 'all_database_mutations',
23
- operator_ack: {
24
- phrase: MAD_DB_ACK,
25
- accepted_at: createdAt,
26
- cwd: path.resolve(input.cwd || process.cwd())
47
+ closed_at: null,
48
+ status: input.status || 'issued',
49
+ counters: {
50
+ attempts: 0,
51
+ reserved: 0,
52
+ succeeded: 0,
53
+ failed: 0
27
54
  },
28
- consumed: false,
29
- consumed_at: null,
30
- consumed_by: null,
31
- max_operations: Math.max(1, Math.floor(Number(process.env.SKS_MAD_DB_MAX_OPERATIONS || 20))),
32
- operation_count: 0
55
+ legacy_compat: {
56
+ one_cycle_only: true,
57
+ priority: 'highest',
58
+ scope: 'all_database_mutations'
59
+ }
33
60
  };
34
61
  const dir = missionDir(root, input.missionId);
35
62
  await writeJsonAtomic(path.join(dir, MAD_DB_CAPABILITY_FILE), capability);
36
- await appendJsonlBounded(path.join(dir, 'mad-db-ledger.jsonl'), { ts: nowIso(), type: 'capability.created', mission_id: capability.mission_id, cycle_id: capability.cycle_id, expires_at: capability.expires_at });
63
+ await appendJsonlBounded(path.join(dir, 'mad-db-ledger.jsonl'), {
64
+ ts: nowIso(),
65
+ type: 'capability.created',
66
+ schema: capability.schema,
67
+ mission_id: capability.mission_id,
68
+ cycle_id: capability.cycle_id,
69
+ project_ref_hash: sha256(capability.project_ref).slice(0, 16),
70
+ runtime_session_id: capability.runtime_session_id,
71
+ expires_at: capability.expires_at,
72
+ status: capability.status
73
+ });
37
74
  return capability;
38
75
  }
39
76
  export async function readMadDbCapability(root, missionId) {
40
- const capability = await readJson(path.join(missionDir(root, missionId), MAD_DB_CAPABILITY_FILE), null);
41
- return capability?.schema === MAD_DB_CAPABILITY_SCHEMA ? capability : null;
77
+ const value = await readJson(path.join(missionDir(root, missionId), MAD_DB_CAPABILITY_FILE), null);
78
+ if (value?.schema === MAD_DB_CAPABILITY_SCHEMA)
79
+ return value;
80
+ if (value?.schema === MAD_DB_CAPABILITY_SCHEMA_V1)
81
+ return migrateV1Capability(value, missionId);
82
+ return null;
42
83
  }
43
84
  export async function resolveMadDbMissionId(root, state = {}, explicitMissionId = null) {
44
85
  if (explicitMissionId && explicitMissionId !== 'latest')
45
86
  return explicitMissionId;
87
+ if (state?.mad_db_capability_mission_id)
88
+ return String(state.mad_db_capability_mission_id);
46
89
  if (state?.mission_id)
47
90
  return String(state.mission_id);
48
91
  return findLatestMission(root);
@@ -51,74 +94,160 @@ export function isMadDbCapabilityActive(capability, nowMs = Date.now()) {
51
94
  if (!capability)
52
95
  return false;
53
96
  const expires = Date.parse(capability.expires_at || '');
54
- return capability.enabled === true
55
- && capability.consumed !== true
56
- && capability.one_cycle_only === true
57
- && Number(capability.operation_count || 0) < Number(capability.max_operations || 20)
97
+ return capability.schema === MAD_DB_CAPABILITY_SCHEMA
98
+ && ['transport_ready', 'active'].includes(capability.status)
99
+ && Boolean(capability.project_ref)
100
+ && capability.transport?.write_capable === true
101
+ && capability.transport?.features?.[0] === 'database'
58
102
  && Number.isFinite(expires)
59
103
  && expires > nowMs;
60
104
  }
105
+ export async function activateMadDbCapability(root, missionId) {
106
+ return updateMadDbCapability(root, missionId, (capability) => ({
107
+ ...capability,
108
+ status: capability.status === 'issued' ? 'active' : capability.status
109
+ }));
110
+ }
111
+ export async function markMadDbTransportReady(root, missionId) {
112
+ return updateMadDbCapability(root, missionId, (capability) => ({
113
+ ...capability,
114
+ status: capability.status === 'issued' ? 'transport_ready' : capability.status
115
+ }));
116
+ }
117
+ export async function updateMadDbCapabilityCounters(root, missionId, delta) {
118
+ return updateMadDbCapability(root, missionId, (capability) => ({
119
+ ...capability,
120
+ counters: {
121
+ attempts: capability.counters.attempts + Number(delta.attemptsDelta || 0),
122
+ reserved: capability.counters.reserved + Number(delta.reservedDelta || 0),
123
+ succeeded: capability.counters.succeeded + Number(delta.succeededDelta || 0),
124
+ failed: capability.counters.failed + Number(delta.failedDelta || 0)
125
+ }
126
+ }));
127
+ }
128
+ export async function updateMadDbCapability(root, missionId, mutator) {
129
+ return withMadDbLock(root, missionId, 'capability', async () => {
130
+ const current = await readMadDbCapability(root, missionId);
131
+ if (!current)
132
+ return null;
133
+ const next = mutator(current);
134
+ const updated = {
135
+ ...next,
136
+ revision: Number(current.revision || 0) + 1
137
+ };
138
+ await writeJsonAtomic(path.join(missionDir(root, missionId), MAD_DB_CAPABILITY_FILE), updated);
139
+ await appendJsonlBounded(path.join(missionDir(root, missionId), 'mad-db-ledger.jsonl'), {
140
+ ts: nowIso(),
141
+ type: 'capability.updated',
142
+ mission_id: missionId,
143
+ cycle_id: updated.cycle_id,
144
+ revision: updated.revision,
145
+ status: updated.status,
146
+ counters: updated.counters
147
+ });
148
+ return updated;
149
+ });
150
+ }
61
151
  export async function recordMadDbOperation(root, missionId, input = {}) {
62
152
  const capability = await readMadDbCapability(root, missionId);
63
- if (!isMadDbCapabilityActive(capability))
64
- return capability;
65
- const operationCount = Number(capability.operation_count || 0) + 1;
66
- const maxOperations = Math.max(1, Number(capability.max_operations || 20));
67
- const updated = {
68
- ...capability,
69
- operation_count: operationCount,
70
- max_operations: maxOperations
71
- };
72
- const dir = missionDir(root, missionId);
73
- await writeJsonAtomic(path.join(dir, MAD_DB_CAPABILITY_FILE), updated);
74
- await appendJsonlBounded(path.join(dir, 'mad-db-ledger.jsonl'), {
153
+ if (!capability)
154
+ return null;
155
+ await appendJsonlBounded(path.join(missionDir(root, missionId), 'mad-db-ledger.jsonl'), {
75
156
  ts: nowIso(),
76
- type: 'db_operation.counted',
157
+ type: 'db_operation.legacy_recorded',
77
158
  mission_id: missionId,
78
- cycle_id: updated.cycle_id,
159
+ cycle_id: capability.cycle_id,
79
160
  operation_id: input.operationId || null,
80
161
  tool_name: input.toolName || null,
81
- sql_hash: input.sqlHash || null,
82
- operation_count: operationCount,
83
- max_operations: maxOperations
162
+ sql_hash: input.sqlHash || null
84
163
  });
85
- if (operationCount >= maxOperations) {
86
- return consumeMadDbCapability(root, missionId, { consumedBy: 'db-safety-checkDbOperation', reason: 'mad_db_max_operations_reached' });
87
- }
88
- return updated;
164
+ return capability;
89
165
  }
90
166
  export async function consumeMadDbCapability(root, missionId, input = {}) {
91
- const capability = await readMadDbCapability(root, missionId);
92
- if (!capability || capability.consumed === true)
93
- return capability;
94
- const consumed = {
95
- ...capability,
96
- consumed: true,
97
- consumed_at: nowIso(),
98
- consumed_by: input.consumedBy || input.reason || 'db-safety-policy-resolver'
99
- };
100
- const dir = missionDir(root, missionId);
101
- await writeJsonAtomic(path.join(dir, MAD_DB_CAPABILITY_FILE), consumed);
102
- await writeJsonAtomic(path.join(dir, 'mad-db-capability.consumed.json'), consumed);
103
- await appendJsonlBounded(path.join(dir, 'mad-db-ledger.jsonl'), { ts: nowIso(), type: 'capability.consumed', mission_id: missionId, cycle_id: consumed.cycle_id, consumed_by: consumed.consumed_by });
104
- return consumed;
167
+ return closeMadDbCycle(root, missionId, '', input.consumedBy || input.reason || 'mad_db_cycle_closed');
105
168
  }
106
- export async function closeMadDbCycle(root, missionId, cycleId) {
107
- const capability = await readMadDbCapability(root, missionId);
108
- if (!capability || capability.cycle_id !== cycleId)
109
- return capability;
110
- if (capability.consumed === true)
111
- return capability;
112
- return consumeMadDbCapability(root, missionId, { consumedBy: 'mad-db-cycle-close', reason: 'mad_db_cycle_closed' });
169
+ export async function closeMadDbCycle(root, missionId, cycleId = '', reason = 'mad_db_cycle_closed') {
170
+ const closed = await updateMadDbCapability(root, missionId, (capability) => {
171
+ if (cycleId && capability.cycle_id !== cycleId)
172
+ return capability;
173
+ return {
174
+ ...capability,
175
+ status: capability.status === 'revoked' ? 'revoked' : 'closed',
176
+ closed_at: nowIso()
177
+ };
178
+ });
179
+ if (closed) {
180
+ await writeJsonAtomic(path.join(missionDir(root, missionId), 'mad-db-capability.closed.json'), {
181
+ schema: closed.schema,
182
+ mission_id: closed.mission_id,
183
+ cycle_id: closed.cycle_id,
184
+ closed_at: closed.closed_at,
185
+ close_reason: reason,
186
+ counters: closed.counters,
187
+ project_ref_hash: sha256(closed.project_ref).slice(0, 16)
188
+ });
189
+ }
190
+ return closed;
113
191
  }
114
192
  export async function revokeMadDbCapability(root, missionId, reason = 'operator_revoked') {
115
- const capability = await readMadDbCapability(root, missionId);
116
- if (!capability)
117
- return null;
118
- const revoked = { ...capability, enabled: false, revoked_at: nowIso(), revoke_reason: reason };
119
- const dir = missionDir(root, missionId);
120
- await writeJsonAtomic(path.join(dir, MAD_DB_CAPABILITY_FILE), revoked);
121
- await appendJsonlBounded(path.join(dir, 'mad-db-ledger.jsonl'), { ts: nowIso(), type: 'capability.revoked', mission_id: missionId, cycle_id: capability.cycle_id, reason });
193
+ const revoked = await updateMadDbCapability(root, missionId, (capability) => ({
194
+ ...capability,
195
+ status: 'revoked',
196
+ closed_at: nowIso()
197
+ }));
198
+ if (revoked) {
199
+ await appendJsonlBounded(path.join(missionDir(root, missionId), 'mad-db-ledger.jsonl'), {
200
+ ts: nowIso(),
201
+ type: 'capability.revoked',
202
+ mission_id: missionId,
203
+ cycle_id: revoked.cycle_id,
204
+ reason
205
+ });
206
+ }
122
207
  return revoked;
123
208
  }
209
+ function migrateV1Capability(value, missionId) {
210
+ const projectRef = process.env.SKS_MAD_DB_PROJECT_REF || process.env.SKS_MAD_DB_E2E_PROJECT_REF || 'legacy-v1-missing-project-ref';
211
+ return {
212
+ schema: MAD_DB_CAPABILITY_SCHEMA,
213
+ revision: 0,
214
+ mission_id: String(value.mission_id || missionId),
215
+ cycle_id: String(value.cycle_id || `mad-db-${Date.now().toString(36)}`),
216
+ project_root_hash: sha256(String(value.operator_ack?.cwd || process.cwd())).slice(0, 24),
217
+ project_ref: projectRef,
218
+ target_environment: 'production',
219
+ allowed_schemas: ['public'],
220
+ codex_thread_id: null,
221
+ runtime_session_id: `legacy-v1-${Date.now().toString(36)}`,
222
+ operator_intent_hash: sha256('legacy-v1-migrated').slice(0, 32),
223
+ operator_ack_hash: sha256(MAD_DB_ACK).slice(0, 32),
224
+ scope: {
225
+ sql_plane: 'all_mutations',
226
+ control_plane: 'deny',
227
+ operations: [...MAD_DB_POLICY.sql_plane_allowed]
228
+ },
229
+ transport: {
230
+ profile_path: '.sneakoscope/missions/<mission>/mad-db/runtime/codex-mad-db.config.toml',
231
+ profile_sha256: sha256('legacy-v1-missing-profile'),
232
+ server_url_redacted: 'https://mcp.supabase.com/mcp?project_ref=<legacy-redacted>&features=database',
233
+ features: ['database'],
234
+ write_capable: true
235
+ },
236
+ issued_at: String(value.created_at || nowIso()),
237
+ expires_at: String(value.expires_at || new Date(Date.now() - 1000).toISOString()),
238
+ closed_at: value.consumed ? String(value.consumed_at || nowIso()) : null,
239
+ status: value.enabled === false ? 'revoked' : value.consumed ? 'closed' : 'quarantined',
240
+ counters: {
241
+ attempts: Number(value.operation_count || 0),
242
+ reserved: Number(value.operation_count || 0),
243
+ succeeded: 0,
244
+ failed: 0
245
+ },
246
+ legacy_compat: {
247
+ one_cycle_only: true,
248
+ priority: 'highest',
249
+ scope: 'all_database_mutations'
250
+ }
251
+ };
252
+ }
124
253
  //# sourceMappingURL=mad-db-capability.js.map