@jhizzard/termdeck 0.18.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/packages/cli/src/init-mnestra.js +40 -1
- package/packages/cli/src/init-rumen.js +337 -29
- package/packages/client/public/app.js +131 -1
- package/packages/server/src/agent-adapters/claude.js +55 -0
- package/packages/server/src/agent-adapters/codex.js +82 -0
- package/packages/server/src/agent-adapters/gemini.js +57 -0
- package/packages/server/src/agent-adapters/grok.js +82 -0
- package/packages/server/src/index.js +132 -0
- package/packages/server/src/setup/audit-upgrade.js +302 -0
- package/packages/server/src/setup/index.js +2 -1
- package/packages/server/src/setup/mnestra-migrations/013_reclassify_uncertain.sql +39 -0
- package/packages/server/src/setup/mnestra-migrations/014_explicit_grants.sql +46 -0
- package/packages/server/src/setup/mnestra-migrations/015_source_agent.sql +51 -0
- package/packages/server/src/setup/mnestra-migrations/016_mnestra_doctor_probes.sql +117 -0
- package/packages/server/src/setup/preconditions.js +36 -4
- package/packages/server/src/setup/rumen/functions/graph-inference/index.ts +6 -2
- package/packages/server/src/setup/rumen/functions/rumen-tick/index.ts +6 -2
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
-- Mnestra v0.4.1 — `mnestra doctor` SECURITY DEFINER probe wrappers
|
|
2
|
+
--
|
|
3
|
+
-- Sprint 51.5 T2 (TermDeck). Adds five SECURITY DEFINER helper functions
|
|
4
|
+
-- so the `mnestra doctor` subcommand (running under the supabase
|
|
5
|
+
-- service_role) can probe `cron.job_run_details`, `cron.job`, `vault.secrets`,
|
|
6
|
+
-- `information_schema.columns`, and `pg_proc` without granting raw schema
|
|
7
|
+
-- access to service_role.
|
|
8
|
+
--
|
|
9
|
+
-- Why SECURITY DEFINER: by default `cron.*` and `vault.*` are owned by
|
|
10
|
+
-- the `postgres` role and unreadable from service_role. The classical
|
|
11
|
+
-- alternative is to `grant usage on schema cron to service_role; grant
|
|
12
|
+
-- select on cron.job_run_details to service_role; …` — broader privilege
|
|
13
|
+
-- expansion than this lane needs. SECURITY DEFINER lets us read exactly
|
|
14
|
+
-- what the doctor needs without exposing the entire cron/vault surface.
|
|
15
|
+
--
|
|
16
|
+
-- Idempotent: every function uses CREATE OR REPLACE; every GRANT is
|
|
17
|
+
-- safe to re-run. No data mutation. Safe to re-apply on every install.
|
|
18
|
+
|
|
19
|
+
-- ── 1. cron.job_run_details lookup ───────────────────────────────────────
|
|
20
|
+
--
|
|
21
|
+
-- Returns the most recent N runs of a named cron job, projecting only
|
|
22
|
+
-- the columns the doctor needs (status, start/end timestamps, return
|
|
23
|
+
-- message). The doctor parses `return_message` for the rumen-tick /
|
|
24
|
+
-- graph-inference all-zeros pattern.
|
|
25
|
+
|
|
26
|
+
create or replace function mnestra_doctor_cron_runs(
|
|
27
|
+
p_jobname text,
|
|
28
|
+
p_limit int default 10
|
|
29
|
+
)
|
|
30
|
+
returns table (
|
|
31
|
+
jobname text,
|
|
32
|
+
status text,
|
|
33
|
+
start_time timestamptz,
|
|
34
|
+
end_time timestamptz,
|
|
35
|
+
return_message text
|
|
36
|
+
)
|
|
37
|
+
language sql
|
|
38
|
+
security definer
|
|
39
|
+
set search_path = cron, public
|
|
40
|
+
as $$
|
|
41
|
+
select j.jobname, d.status, d.start_time, d.end_time, d.return_message
|
|
42
|
+
from cron.job_run_details d
|
|
43
|
+
join cron.job j on j.jobid = d.jobid
|
|
44
|
+
where j.jobname = p_jobname
|
|
45
|
+
order by d.start_time desc
|
|
46
|
+
limit greatest(coalesce(p_limit, 10), 1);
|
|
47
|
+
$$;
|
|
48
|
+
|
|
49
|
+
-- ── 2. column existence probe (public schema only) ───────────────────────
|
|
50
|
+
|
|
51
|
+
create or replace function mnestra_doctor_column_exists(
|
|
52
|
+
p_table text,
|
|
53
|
+
p_column text
|
|
54
|
+
)
|
|
55
|
+
returns boolean
|
|
56
|
+
language sql
|
|
57
|
+
security definer
|
|
58
|
+
set search_path = public
|
|
59
|
+
as $$
|
|
60
|
+
select exists (
|
|
61
|
+
select 1
|
|
62
|
+
from information_schema.columns
|
|
63
|
+
where table_schema = 'public'
|
|
64
|
+
and table_name = p_table
|
|
65
|
+
and column_name = p_column
|
|
66
|
+
);
|
|
67
|
+
$$;
|
|
68
|
+
|
|
69
|
+
-- ── 3. RPC / function existence probe ────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
create or replace function mnestra_doctor_rpc_exists(p_name text)
|
|
72
|
+
returns boolean
|
|
73
|
+
language sql
|
|
74
|
+
security definer
|
|
75
|
+
set search_path = public
|
|
76
|
+
as $$
|
|
77
|
+
select exists (
|
|
78
|
+
select 1
|
|
79
|
+
from pg_proc p
|
|
80
|
+
join pg_namespace n on n.oid = p.pronamespace
|
|
81
|
+
where p.proname = p_name
|
|
82
|
+
and n.nspname = 'public'
|
|
83
|
+
);
|
|
84
|
+
$$;
|
|
85
|
+
|
|
86
|
+
-- ── 4. cron.job existence probe (does the named job exist at all) ───────
|
|
87
|
+
|
|
88
|
+
create or replace function mnestra_doctor_cron_job_exists(p_jobname text)
|
|
89
|
+
returns boolean
|
|
90
|
+
language sql
|
|
91
|
+
security definer
|
|
92
|
+
set search_path = cron, public
|
|
93
|
+
as $$
|
|
94
|
+
select exists (select 1 from cron.job where jobname = p_jobname);
|
|
95
|
+
$$;
|
|
96
|
+
|
|
97
|
+
-- ── 5. vault.secrets existence probe (no value disclosure) ──────────────
|
|
98
|
+
--
|
|
99
|
+
-- Existence-only — never returns the secret value. The doctor only needs
|
|
100
|
+
-- to know whether the named vault entry was created during stack install.
|
|
101
|
+
|
|
102
|
+
create or replace function mnestra_doctor_vault_secret_exists(p_name text)
|
|
103
|
+
returns boolean
|
|
104
|
+
language sql
|
|
105
|
+
security definer
|
|
106
|
+
set search_path = vault, public
|
|
107
|
+
as $$
|
|
108
|
+
select exists (select 1 from vault.secrets where name = p_name);
|
|
109
|
+
$$;
|
|
110
|
+
|
|
111
|
+
-- ── 6. Grants ────────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
grant execute on function mnestra_doctor_cron_runs(text, int) to service_role;
|
|
114
|
+
grant execute on function mnestra_doctor_column_exists(text, text) to service_role;
|
|
115
|
+
grant execute on function mnestra_doctor_rpc_exists(text) to service_role;
|
|
116
|
+
grant execute on function mnestra_doctor_cron_job_exists(text) to service_role;
|
|
117
|
+
grant execute on function mnestra_doctor_vault_secret_exists(text) to service_role;
|
|
@@ -52,6 +52,23 @@ function extensionsDashboardUrl(secrets) {
|
|
|
52
52
|
return `https://supabase.com/dashboard/project/${parsed.projectRef}/database/extensions`;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// Sprint 51.5 T3: Build a SQL-Editor deeplink that pre-fills a
|
|
56
|
+
// vault.create_secret() call. Used when the audit's vault probe finds the
|
|
57
|
+
// secret missing and the wizard's auto-apply step (init-rumen.js
|
|
58
|
+
// `ensureVaultSecrets`) was unable to create it via pgRunner. The Vault
|
|
59
|
+
// dashboard panel was quietly removed/relocated in current Supabase UIs
|
|
60
|
+
// (Brad 2026-05-03 takeaway #2; INSTALLER-PITFALLS.md Class B), so SQL
|
|
61
|
+
// Editor is the working manual surface.
|
|
62
|
+
function vaultSqlEditorUrl(secrets, secretName, secretValue) {
|
|
63
|
+
if (!secrets || !secrets.SUPABASE_URL) return null;
|
|
64
|
+
const parsed = supabaseUrlHelper.parseProjectUrl(secrets.SUPABASE_URL);
|
|
65
|
+
if (!parsed.ok) return null;
|
|
66
|
+
const value = String(secretValue == null ? '' : secretValue).replace(/'/g, "''");
|
|
67
|
+
const name = String(secretName == null ? '' : secretName).replace(/'/g, "''");
|
|
68
|
+
const sql = `select vault.create_secret('${value}', '${name}');`;
|
|
69
|
+
return `https://supabase.com/dashboard/project/${parsed.projectRef}/sql/new?content=${encodeURIComponent(sql)}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
55
72
|
// Render a single gap into 2-3 lines of CLI output (one indented hint per
|
|
56
73
|
// non-empty `hint` line). Format aligned with the rest of the wizard's
|
|
57
74
|
// step lines.
|
|
@@ -206,14 +223,28 @@ async function auditRumenPreconditions({ secrets, env, _pgClient } = {}) {
|
|
|
206
223
|
'If permission is denied, the Vault is not accessible to this connection — double-check secrets.env.'
|
|
207
224
|
});
|
|
208
225
|
} else if (!vault.ok) {
|
|
226
|
+
// Sprint 51.5 T3: the Supabase Vault dashboard panel was quietly removed
|
|
227
|
+
// / relocated in current Supabase UIs (Brad 2026-05-03 takeaway #2;
|
|
228
|
+
// INSTALLER-PITFALLS.md Class B). The wizard's `ensureVaultSecrets`
|
|
229
|
+
// step (init-rumen.js) auto-creates this key via pgRunner when the
|
|
230
|
+
// user's connection has vault.create_secret privileges; this hint only
|
|
231
|
+
// fires when auto-apply also failed, in which case the SQL Editor is
|
|
232
|
+
// the working manual surface. Build a project-specific deeplink when
|
|
233
|
+
// we can derive the project ref so the user gets one click instead of
|
|
234
|
+
// a "find the SQL Editor yourself" instruction.
|
|
235
|
+
const sqlEditorUrl = vaultSqlEditorUrl(secrets, 'rumen_service_role_key',
|
|
236
|
+
'<paste your service_role JWT from Project Settings → API>');
|
|
237
|
+
const sqlEditorLine = sqlEditorUrl
|
|
238
|
+
? ` Open: ${sqlEditorUrl}\n Replace the placeholder with your service_role key from Project Settings → API, then click Run.`
|
|
239
|
+
: ' Open the Supabase SQL Editor and run:\n' +
|
|
240
|
+
" select vault.create_secret('<service_role JWT>', 'rumen_service_role_key');";
|
|
209
241
|
gaps.push({
|
|
210
242
|
key: 'rumen_service_role_key',
|
|
211
243
|
message: 'Vault secret "rumen_service_role_key" is missing',
|
|
212
244
|
hint:
|
|
213
|
-
|
|
214
|
-
'
|
|
215
|
-
|
|
216
|
-
' Value: your service_role key from Project Settings → API\n' +
|
|
245
|
+
"The wizard tries to auto-create this via vault.create_secret() and only surfaces this hint when auto-apply also failed.\n" +
|
|
246
|
+
'Create it manually via the SQL Editor (the Vault dashboard panel was removed in current Supabase UIs):\n' +
|
|
247
|
+
sqlEditorLine + '\n' +
|
|
217
248
|
'(The pg_cron schedule calls the Edge Function with this key as the bearer token.)'
|
|
218
249
|
});
|
|
219
250
|
}
|
|
@@ -384,6 +415,7 @@ module.exports = {
|
|
|
384
415
|
printAuditReport,
|
|
385
416
|
printVerifyReport,
|
|
386
417
|
extensionsDashboardUrl,
|
|
418
|
+
vaultSqlEditorUrl,
|
|
387
419
|
// Test surface
|
|
388
420
|
_probeSupabaseAuth: probeSupabaseAuth,
|
|
389
421
|
_safeQuery: safeQuery
|
|
@@ -343,9 +343,13 @@ export async function runGraphInference(sql: Sql): Promise<InferenceSummary> {
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
serve(async (_req: Request) => {
|
|
346
|
-
|
|
346
|
+
// Supabase Edge Runtime auto-injects SUPABASE_DB_URL as a built-in env var.
|
|
347
|
+
// Falling back to it removes one whole category of "where do I get the DB
|
|
348
|
+
// connection string" from the install wizard. Brad surfaced this 2026-05-03
|
|
349
|
+
// after hand-patching all four of his deployed copies.
|
|
350
|
+
const url = Deno.env.get('DATABASE_URL') ?? Deno.env.get('SUPABASE_DB_URL');
|
|
347
351
|
if (!url) {
|
|
348
|
-
console.error('[graph-inference] DATABASE_URL not set in Edge Function secrets');
|
|
352
|
+
console.error('[graph-inference] DATABASE_URL / SUPABASE_DB_URL not set in Edge Function secrets');
|
|
349
353
|
return new Response(
|
|
350
354
|
JSON.stringify({ ok: false, error: 'DATABASE_URL not set' }),
|
|
351
355
|
{ status: 500, headers: { 'Content-Type': 'application/json' } },
|
|
@@ -31,9 +31,13 @@ import { runRumenJob, createPoolFromUrl } from 'npm:@jhizzard/rumen@__RUMEN_VERS
|
|
|
31
31
|
declare const Deno: { env: { get: (k: string) => string | undefined } };
|
|
32
32
|
|
|
33
33
|
serve(async (_req: Request) => {
|
|
34
|
-
|
|
34
|
+
// Supabase Edge Runtime auto-injects SUPABASE_DB_URL as a built-in env var.
|
|
35
|
+
// Falling back to it removes one whole category of "where do I get the DB
|
|
36
|
+
// connection string" from the install wizard. Brad surfaced this 2026-05-03
|
|
37
|
+
// after hand-patching all four of his deployed copies.
|
|
38
|
+
const url = Deno.env.get('DATABASE_URL') ?? Deno.env.get('SUPABASE_DB_URL');
|
|
35
39
|
if (!url) {
|
|
36
|
-
console.error('[rumen] DATABASE_URL not set in Edge Function secrets');
|
|
40
|
+
console.error('[rumen] DATABASE_URL / SUPABASE_DB_URL not set in Edge Function secrets');
|
|
37
41
|
return new Response(
|
|
38
42
|
JSON.stringify({
|
|
39
43
|
ok: false,
|