The store's databases and query are registered under mesh-store, so the runtime serves them on the seat's subjects wherever postgres holds the seat and never lists them as postgres's own; the claim names them, so the mesh can judge the holder without postgres listing the seat's verbs among its tools. Scoped to what the store enables: creating a database stays postgres's tool.
84 lines
3.4 KiB
TypeScript
84 lines
3.4 KiB
TypeScript
// postgres's tools — postgres's own code (novox/hq ADR 0039), importing postgres's own client. They
|
|
// return structured data; the mesh serves them through the sdk's tool harness. Both call through
|
|
// PostgresClient.query(), the module's one pending execution boundary (see client.ts): the tool
|
|
// shapes are fixed and correct, and surface the TODO honestly until that boundary is backed.
|
|
|
|
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
|
|
import { PostgresClient } from "../client.js";
|
|
|
|
export function getPostgresTools(postgres: PostgresClient): ToolDefinition[] {
|
|
return [
|
|
{
|
|
name: "postgres_list_databases",
|
|
description: "List the databases on the postgres server, with their on-disk size.",
|
|
input: {},
|
|
run: async () => ({ databases: await postgres.listDatabases() }),
|
|
},
|
|
{
|
|
name: "postgres_query",
|
|
description: "Run a read-only SQL query against a named database (wrapped in a read-only transaction).",
|
|
input: {
|
|
database: { type: "string", description: "the database to query" },
|
|
sql: { type: "string", description: "the SELECT (or other read-only) statement" },
|
|
},
|
|
run: async (args) => {
|
|
const database = String(args.database ?? "");
|
|
const sql = String(args.sql ?? "");
|
|
if (!database) throw new Error("postgres_query: database is required");
|
|
if (!sql) throw new Error("postgres_query: sql is required");
|
|
const result = await postgres.readOnlyQuery(database, sql);
|
|
return { database, command: result.command, rows: result.rows };
|
|
},
|
|
},
|
|
];
|
|
}
|
|
|
|
// The store seat's verbs (novox/hq ADR 0159, 0160): the role's, not postgres's. Registered under the
|
|
// seat's name, so the runtime serves them on the seat's subjects wherever this module holds the
|
|
// seat and never lists them as postgres's own; scoped to what the store enables — asking what it
|
|
// holds and reading from it — so creating a database is postgres's tool and not the store's.
|
|
export function getStoreVerbs(postgres: PostgresClient): ToolDefinition[] {
|
|
return [
|
|
{
|
|
name: "databases",
|
|
description: "Every database the store holds, with its on-disk size.",
|
|
input: {},
|
|
run: async () => ({ databases: await postgres.listDatabases() }),
|
|
},
|
|
{
|
|
name: "query",
|
|
description: "One read-only statement against one database the store holds.",
|
|
input: {
|
|
database: { type: "string", description: "the database to query" },
|
|
sql: { type: "string", description: "the SELECT (or other read-only) statement" },
|
|
},
|
|
run: async (args) => {
|
|
const database = String(args.database ?? "");
|
|
const sql = String(args.sql ?? "");
|
|
if (!database) throw new Error("query: database is required");
|
|
if (!sql) throw new Error("query: sql is required");
|
|
const result = await postgres.readOnlyQuery(database, sql);
|
|
return { database, command: result.command, rows: result.rows };
|
|
},
|
|
},
|
|
];
|
|
}
|
|
|
|
// The tools exist only when the server can be reached from the environment; without it, postgres
|
|
// contributes none rather than failing the whole tool runtime.
|
|
registerModuleTools("postgres", (env) => {
|
|
try {
|
|
return getPostgresTools(PostgresClient.fromEnv(env));
|
|
} catch {
|
|
return [];
|
|
}
|
|
});
|
|
|
|
registerModuleTools("mesh-store", (env) => {
|
|
try {
|
|
return getStoreVerbs(PostgresClient.fromEnv(env));
|
|
} catch {
|
|
return [];
|
|
}
|
|
});
|