diff --git a/modules/mesh-catalog/store.ts b/modules/mesh-catalog/store.ts index 8e1f87f..5b187ef 100644 --- a/modules/mesh-catalog/store.ts +++ b/modules/mesh-catalog/store.ts @@ -279,11 +279,15 @@ export class Graph { * Resolved through the artifacts each version published, because that is what an edge names. */ async dependents(module: string): Promise<{ module: string; commit: string; againstCommit: string }[]> { + // Distinct, because one artifact may have been published by more than one version of the + // module that made it — several commits producing a byte-identical image — and a dependent + // would otherwise be listed once per such commit, as though it were several dependents. const { rows } = await this.pool.query( - `SELECT b.module, b.commit_sha, a.commit_sha AS against_commit + `SELECT DISTINCT b.module, b.commit_sha, c2.commit_sha AS against_commit FROM built_against b JOIN module_current c ON c.module = b.module AND c.commit_sha = b.commit_sha JOIN module_artifact a ON a.reference = b.against_reference + JOIN module_current c2 ON c2.module = a.module WHERE a.module = $1 ORDER BY b.module`, [module]); return rows.map((r) => ({ @@ -301,14 +305,32 @@ export class Graph { * chain and a diamond need no special handling and nothing has to know the shape in advance. */ async stale(): Promise { + // **Compared by artifact, not by commit.** A commit moving is not the same as the thing it + // produces moving: a change to a comment in a build recipe is a new commit and a byte-identical + // image, and calling everything built on it stale would mean rebuilding the whole mesh to + // arrive back exactly where it started. What makes a module stale is that the artifact it was + // built against is no longer one the current version publishes. + // + // The producing version is picked as the earliest that published this artifact, because when + // several commits produce the identical image, the first one is where it actually came from. const { rows } = await this.pool.query( - `SELECT b.module, b.commit_sha, a.module AS against_module, a.commit_sha AS against_commit, + `SELECT b.module, b.commit_sha, p.module AS against_module, p.commit_sha AS against_commit, c2.commit_sha AS now_at FROM built_against b JOIN module_current c1 ON c1.module = b.module AND c1.commit_sha = b.commit_sha - JOIN module_artifact a ON a.reference = b.against_reference - JOIN module_current c2 ON c2.module = a.module - WHERE c2.commit_sha <> a.commit_sha + JOIN LATERAL ( + SELECT a.module, a.commit_sha + FROM module_artifact a + JOIN module_version v ON v.module = a.module AND v.commit_sha = a.commit_sha + WHERE a.reference = b.against_reference + ORDER BY v.registered + LIMIT 1 + ) p ON true + JOIN module_current c2 ON c2.module = p.module + WHERE NOT EXISTS ( + SELECT 1 FROM module_artifact now + WHERE now.module = c2.module AND now.commit_sha = c2.commit_sha + AND now.reference = b.against_reference) ORDER BY b.module`); const by = new Map(); for (const r of rows) {