The graph holds what a module declares, not only what it was built on
Build edges are discovered by building; requires and provides are stated by the module about itself. Both belong in the graph and answer different questions — and "what provides postgres-database" needed a sweep over every manifest, which only something holding all of them can do.
This commit is contained in:
@@ -87,6 +87,28 @@ CREATE TABLE IF NOT EXISTS module_artifact (
|
||||
|
||||
CREATE INDEX IF NOT EXISTS module_artifact_reference ON module_artifact (reference);
|
||||
|
||||
-- What a module-version declares. Separate from the build edges above and never mixed with them:
|
||||
-- a build edge is a fact about what was compiled, discovered by building it, and these are
|
||||
-- intentions the module states about itself (ADR 0072). They answer different questions and go
|
||||
-- stale for different reasons.
|
||||
CREATE TABLE IF NOT EXISTS module_requires (
|
||||
module text NOT NULL,
|
||||
commit_sha text NOT NULL,
|
||||
requirement text NOT NULL,
|
||||
PRIMARY KEY (module, commit_sha, requirement)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS module_provides (
|
||||
module text NOT NULL,
|
||||
commit_sha text NOT NULL,
|
||||
provision text NOT NULL,
|
||||
scope text NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (module, commit_sha, provision)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS module_provides_provision ON module_provides (provision);
|
||||
CREATE INDEX IF NOT EXISTS module_requires_requirement ON module_requires (requirement);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS built_against (
|
||||
module text NOT NULL,
|
||||
commit_sha text NOT NULL,
|
||||
@@ -154,6 +176,34 @@ export class Graph {
|
||||
|
||||
// Both are replaced rather than added to: they describe this build, and a previous build of
|
||||
// the same commit that saw different artifacts was wrong about one of them.
|
||||
// Read from the manifest rather than taken as a separate argument: the manifest the builder
|
||||
// resolved IS the module's declaration, and a second copy passed alongside it could disagree
|
||||
// with it. Replaced wholesale, because a version declares exactly one set of these.
|
||||
const declared = (version.manifest ?? {}) as {
|
||||
requires?: unknown[];
|
||||
provides?: unknown[];
|
||||
};
|
||||
await client.query(`DELETE FROM module_requires WHERE module = $1 AND commit_sha = $2`,
|
||||
[version.module, version.commit]);
|
||||
for (const r of declared.requires ?? []) {
|
||||
const name = typeof r === "string" ? r : String((r as { name?: unknown }).name ?? "");
|
||||
if (!name) continue;
|
||||
await client.query(
|
||||
`INSERT INTO module_requires (module, commit_sha, requirement) VALUES ($1,$2,$3)
|
||||
ON CONFLICT DO NOTHING`, [version.module, version.commit, name]);
|
||||
}
|
||||
await client.query(`DELETE FROM module_provides WHERE module = $1 AND commit_sha = $2`,
|
||||
[version.module, version.commit]);
|
||||
for (const pv of declared.provides ?? []) {
|
||||
const name = typeof pv === "string" ? pv : String((pv as { name?: unknown }).name ?? "");
|
||||
const scope = typeof pv === "string" ? "" : String((pv as { scope?: unknown }).scope ?? "");
|
||||
if (!name) continue;
|
||||
await client.query(
|
||||
`INSERT INTO module_provides (module, commit_sha, provision, scope) VALUES ($1,$2,$3,$4)
|
||||
ON CONFLICT (module, commit_sha, provision) DO UPDATE SET scope = excluded.scope`,
|
||||
[version.module, version.commit, name, scope]);
|
||||
}
|
||||
|
||||
await client.query(`DELETE FROM module_artifact WHERE module = $1 AND commit_sha = $2`,
|
||||
[version.module, version.commit]);
|
||||
for (const a of made) {
|
||||
@@ -285,6 +335,35 @@ export class Graph {
|
||||
return stale.filter((s) => !s.because.some((b) => waiting.has(b.module)));
|
||||
}
|
||||
|
||||
/**
|
||||
* What provides a given provision, and what needs it.
|
||||
*
|
||||
* **Answered from the graph rather than by scanning manifests**, which is how it is answered
|
||||
* today — a sweep over every module's declaration, which only whoever holds every declaration
|
||||
* can do and which is wrong the moment one of them changes.
|
||||
*/
|
||||
async whoProvides(provision: string): Promise<{
|
||||
provides: { module: string; commit: string; scope: string }[];
|
||||
requires: { module: string; commit: string }[];
|
||||
}> {
|
||||
const provides = await this.pool.query(
|
||||
`SELECT p.module, p.commit_sha, p.scope
|
||||
FROM module_provides p
|
||||
JOIN module_current c ON c.module = p.module AND c.commit_sha = p.commit_sha
|
||||
WHERE p.provision = $1 ORDER BY p.module`, [provision]);
|
||||
const requires = await this.pool.query(
|
||||
`SELECT r.module, r.commit_sha
|
||||
FROM module_requires r
|
||||
JOIN module_current c ON c.module = r.module AND c.commit_sha = r.commit_sha
|
||||
WHERE r.requirement = $1 ORDER BY r.module`, [provision]);
|
||||
return {
|
||||
provides: provides.rows.map((r) => ({
|
||||
module: r.module as string, commit: r.commit_sha as string, scope: r.scope as string })),
|
||||
requires: requires.rows.map((r) => ({
|
||||
module: r.module as string, commit: r.commit_sha as string })),
|
||||
};
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
await this.pool.end();
|
||||
}
|
||||
|
||||
@@ -31,6 +31,17 @@ export function getCatalogueTools(graph: Graph): ToolDefinition[] {
|
||||
return found ? { module: found } : { error: `the catalogue holds no ${module}` };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "catalog_provides",
|
||||
description:
|
||||
"What provides a provision, and what needs it. Answered from what modules declare about themselves, so it does not require holding every manifest at once.",
|
||||
input: { provision: { type: "string", description: "the provision, e.g. postgres-database" } },
|
||||
run: async (args) => {
|
||||
const provision = String(args.provision ?? "");
|
||||
if (!provision) return { error: "name a provision" };
|
||||
return await graph.whoProvides(provision);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "catalog_dependents",
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user