diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0b8040..f1798ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,5 +99,8 @@ jobs: - name: agents-audit packed-artifact parity run: migration/parity-agents-audit-pack.sh - - name: DataHub adapter parity - run: node migration/parity-datahub-shim.mjs + # The DataHub adapter parity step was removed under META-248. The adapter + # was extracted to workspacejson/datahub-agent, and OWNERSHIP.md records + # that its harness travels with it; it now runs there against the + # DataHub-owned candidate (35/35, plus per-file source identity against + # this repository's own frozen pre-migration source). diff --git a/AGENTS.md b/AGENTS.md index 448e508..2a0094f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,7 @@ # Workspace Notes -- This repository holds three packages. `./packages/cli/` is `@workspacejson/cli`, the neutral workspace.json producer — generation lives in `./packages/cli/src/producer/` and command routing in `./packages/cli/src/commands/`. `./packages/agents-audit-compat/` is the published `agents-audit` compatibility package. `./packages/datahub-adapter/` is a private DataHub/dbt adapter staged here pending extraction to `workspacejson/datahub-agent`. +- This repository holds two packages. `./packages/cli/` is `@workspacejson/cli`, the neutral workspace.json producer — generation lives in `./packages/cli/src/producer/` and command routing in `./packages/cli/src/commands/`. `./packages/agents-audit-compat/` is the published `agents-audit` compatibility package. +- The DataHub/dbt adapter that was staged here was extracted to `workspacejson/datahub-agent` under META-248. Do not re-add DataHub-, dbt- or vendor-specific consumer logic in any form — not as a sibling package, not inside the producer. `scripts/check-architecture.mjs` rejects it and is red-tested. (Its former path is deliberately not written here: `agents-audit` scans this file and resolves referenced paths, so naming a deleted directory fails the repository's own audit.) - Keep package entry points aligned with `./packages/cli/src/index.ts` and `./packages/agents-audit-compat/src/index.ts`. - `agents-audit` is a frozen compatibility bridge. Do not add features to it; both binaries route through `./packages/cli/src/commands/generate.ts` so they cannot drift. - `@workspacejson/spec` and `@workspacejson/rules` are consumed as released packages from `workspacejson/standard`; never vendor, copy or workspace-link them here. diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ed5fa2..e332592 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,8 @@ release notes live with their packages: `packages/agents-audit-compat/`, a frozen compatibility bridge that keeps the `agents-audit` package name, binary, commands and all nine public exports and delegates generation to the neutral package. The migrated DataHub/dbt adapter - moved to `packages/datahub-adapter/` and is staged pending extraction to - `workspacejson/datahub-agent` (META-248) — it is not durable architecture + was staged at `packages/datahub-adapter/` and has since been **extracted to + `workspacejson/datahub-agent`** (META-248) — it was never durable architecture here. Compatibility was proven by the parity harnesses; the only behavioral difference is the ratified vendor-notice change, recorded in `migration/parity-expected-differences.txt`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bdb8d82..b35488c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ released packages — do not vendor or edit them here. - Know which package you are changing: - `packages/cli/` — `@workspacejson/cli`, the neutral producer (`src/producer/`) and its commands (`src/commands/`) - `packages/agents-audit-compat/` — published `agents-audit`, a **frozen** compatibility bridge; do not add features to it - - `packages/datahub-adapter/` — private DataHub/dbt adapter, staged here pending extraction to `workspacejson/datahub-agent`; do not build on it + - DataHub, dbt or other vendor-specific consumer logic belongs in `workspacejson/datahub-agent`, not here. The adapter that was staged in `packages/datahub-adapter/` was extracted under META-248, and `scripts/check-architecture.mjs` rejects its return - Keep changes within the owning package when possible - Avoid changing package entrypoints unless the public surface changes diff --git a/OWNERSHIP.md b/OWNERSHIP.md index 4b2834d..869a397 100644 --- a/OWNERSHIP.md +++ b/OWNERSHIP.md @@ -10,7 +10,6 @@ script is what enforces it. | -- | -- | -- | -- | | `packages/cli/` | `@workspacejson/cli` | public, **not yet published** | the neutral workspace.json producer and its `workspacejson` binary | | `packages/agents-audit-compat/` | `agents-audit` | public, published `0.4.4` | frozen compatibility bridge for the historical `agents-audit` command and API | -| `packages/datahub-adapter/` | `@workspacejson/datahub-adapter` | **private, never published** | **migration staging only** — see below | ## Owns @@ -37,29 +36,52 @@ script is what enforces it. `workspacejson/datahub-agent` * Private product behavior of any kind -## `packages/datahub-adapter/` is staging, not architecture +## `packages/datahub-adapter/` was extracted — META-248, 2026-07-26 -This package is **not durable CLI architecture**. It is the DataHub/dbt adapter -migrated intact from the `agents-audit` monorepo, parked here only because -META-240 had to preserve it somewhere while its permanent owner was decided. +The DataHub/dbt adapter is **gone from this repository**. It was never durable +CLI architecture: it was migrated intact from the `agents-audit` monorepo and +parked here only because META-240 had to preserve it somewhere while its +permanent owner was decided. It is a **consumer** adapter — it reads an existing `.agents/workspace.json` and joins dbt models against `generated.fileIndex`. That is DataHub consumer logic, -not neutral producer logic, and it belongs to `workspacejson/datahub-agent`. - -Until it is extracted: - -* it stays `private: true` and is never published; -* the neutral CLI **must not depend on it** — the dependency direction is - one-way and the guard enforces it; -* it is not polished, documented or advertised as a CLI-owned package; -* it does not appear in the durable CLI package map. - -Its 35/35 parity harness (`migration/parity-datahub-shim.mjs`) travels with it -and must pass against the DataHub-owned candidate after extraction. - -**Do not confuse this adapter with a hypothetical `workspacejson signals -datahub` producer surface.** They run in opposite directions: this adapter +not neutral producer logic, and it belongs to `workspacejson/datahub-agent`, +which now owns it as an internal module at `src/adapters/workspacejson/`. + +Its parity harness travelled with it, as this document required. At the +DataHub-owned candidate it reports **35/35**, plus per-file source identity +against this repository's own frozen pre-migration source +(`workspace-json/agents-audit@e47eb1b8`): four of five files byte-identical, +with one documented type-only deviation. Provenance is recorded in +`docs/provenance.md` there. The `DataHub adapter parity` CI step was removed +here because the artifact it measured is no longer here. + +The extraction is machine-enforced, not merely documented: + +* `repository-boundary` lists `packages/datahub-adapter` as owned by + `workspacejson/datahub-agent`, so re-adding the directory fails the check — + `neutral-producer-purity` alone would not catch it, since that rule only + scans `packages/cli/`; +* redefining the `@workspacejson/datahub-adapter` package name in any manifest + fails, whether or not it is marked private; +* a workflow referencing that package name for publication fails. + +All three are red-tested in `scripts/check-architecture.test.mjs`. + +### One deviation found on extraction, still unfixed here + +The adapter typechecked in this repository only because +`packages/datahub-adapter/tsconfig.json` included `types/ambient.d.ts`, which +**shadows `node:fs`** with a hand-written `Dirent`. Against real +`@types/node@22.19.17`, `ReturnType` selects the Buffer +overload and `findDbtProjects` does not compile (4 errors). + +The shadowing pattern is the same class of defect META-244 already fixed once +here for `@workspacejson/spec`. `types/ambient.d.ts` still shadows `node:fs` +for the remaining packages and is worth auditing on the same grounds. + +**Do not confuse the extracted adapter with a hypothetical `workspacejson +signals datahub` producer surface.** They run in opposite directions: this adapter consumes the artifact; a signals surface would produce DataHub-specific evidence. No such producer surface is admitted, and none may be added without an explicit ADR-002 Gate A ruling. @@ -78,7 +100,8 @@ workspacejson/integrations workspacejson/datahub-agent ``` Within this repository: `agents-audit` depends on `@workspacejson/cli`. Never -the reverse. `@workspacejson/datahub-adapter` depends on neither. +the reverse. `workspacejson/datahub-agent` sits downstream of both and consumes +released contracts only — nothing here depends on it. ## Clean-room boundary @@ -100,7 +123,7 @@ Guard: `neutral-producer-purity`. | -- | -- | -- | | `agents-audit` | metadata says yes; **workflow disabled** | `workspace-json/agents-audit` until META-243 | | `@workspacejson/cli` | metadata says yes; **workflow disabled, never published** | none yet — META-243 | -| `@workspacejson/datahub-adapter` | **No** — private, and leaving this repository | none | +| `@workspacejson/datahub-adapter` | **No** — extracted under META-248; redefining it here is a guard failure | `workspacejson/datahub-agent` (internal module, unpublished) | | `@workspacejson/spec`, `@workspacejson/rules` | **Never** — not owned here | `workspacejson/standard` | This repository holds no publish-capable secret. diff --git a/README.md b/README.md index f710e1e..3ec41b8 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,10 @@ consumed here as released packages. | [`packages/cli/`](./packages/cli/) | `@workspacejson/cli` | **No — not yet on npm** | the neutral producer and its `workspacejson` binary | | [`packages/agents-audit-compat/`](./packages/agents-audit-compat/) | `agents-audit` | **Yes — `0.4.4`** | frozen compatibility bridge; preserves the historical command and API | -`packages/datahub-adapter/` also exists but is **not part of this repository's -architecture** — it is a private DataHub/dbt adapter staged here pending -extraction to `workspacejson/datahub-agent`, which owns DataHub consumption. See +Those two packages are the whole repository. The private DataHub/dbt adapter +that was staged here has been **extracted to `workspacejson/datahub-agent`** +(META-248), which owns DataHub consumption; it was never durable architecture +here. The boundary is machine-enforced and red-tested — see [`OWNERSHIP.md`](./OWNERSHIP.md). ## Generating the artifact diff --git a/migration/parity-datahub-shim.mjs b/migration/parity-datahub-shim.mjs deleted file mode 100644 index 2c72968..0000000 --- a/migration/parity-datahub-shim.mjs +++ /dev/null @@ -1,268 +0,0 @@ -#!/usr/bin/env node -// Independent parity proof for the PRIVATE DataHub/dbt shim (@workspacejson/cli). -// -// This deliberately shares nothing with the agents-audit parity run. A green -// producer result is not evidence about this package: it is a different package, -// with different code, different dependencies and a different contract. - -import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, existsSync } from "node:fs"; -import { homedir, tmpdir } from "node:os"; -import { dirname, join, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; -import { spawnSync } from "node:child_process"; - -// Paths are derived, never hardcoded. The old side is a clone of the frozen -// pre-migration source; migration/parity-lib.sh resolves, pins and builds it, -// and caches it under ~/.cache/workspacejson/cli-parity so repeat runs are -// cheap. The cache lives OUTSIDE the repository on purpose: the frozen source -// contains content the architecture guard exists to reject, so caching it in -// the working tree turns scripts/check-architecture.mjs red. -const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -const XDG_CACHE = process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache"); -const PARITY_CACHE = process.env.WORKSPACEJSON_PARITY_CACHE ?? join(XDG_CACHE, "workspacejson", "cli-parity"); -const OLD_CHECKOUT = process.env.WORKSPACEJSON_OLD_CHECKOUT ?? join(PARITY_CACHE, "source-agents-audit"); - -const SIDES = { - old: join(OLD_CHECKOUT, "packages/cli"), - new: join(REPO_ROOT, "packages/datahub-adapter"), -}; - -for (const [side, dir] of Object.entries(SIDES)) { - if (!existsSync(join(dir, "package.json"))) { - console.error(`\nERROR: missing the ${side} side at ${dir}\n`); - console.error(side === "old" - ? "Run migration/parity-lib.sh's bootstrap first — the simplest way is:\n" - + " bash -c 'source migration/parity-lib.sh && parity_resolve_old_checkout && parity_build_old'\n" - + "or point WORKSPACEJSON_OLD_CHECKOUT at an existing clone of the frozen source.\n" - : "The DataHub adapter has been extracted from this repository (META-248).\n" - + "Re-run this harness from workspacejson/datahub-agent against its candidate.\n"); - process.exit(1); - } - if (!existsSync(join(dir, "dist/index.js"))) { - console.error(`\nERROR: ${side} side is not built (${join(dir, "dist/index.js")} missing).`); - console.error(side === "old" - ? "Build the frozen source: (cd " + OLD_CHECKOUT + " && pnpm install --no-frozen-lockfile && pnpm -r build)\n" - : "Build this repository: pnpm install && pnpm -r build\n"); - process.exit(1); - } -} - -let pass = 0, fail = 0; -const failures = []; - -function check(label, condition, detail = "") { - if (condition) { console.log(` PASS ${label}`); pass += 1; } - else { console.log(` FAIL ${label}${detail ? `\n ${detail}` : ""}`); fail += 1; failures.push(label); } -} - -function equalish(a, b) { return JSON.stringify(a) === JSON.stringify(b); } - -// --------------------------------------------------------------------------- -console.log("=============================================================="); -console.log(" 1. PACKAGE IDENTITY AND PRIVACY"); -console.log("=============================================================="); - -const manifests = {}; -for (const [side, dir] of Object.entries(SIDES)) { - manifests[side] = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")); -} -// META-247 INTENTIONAL DIFFERENCE: the package was renamed so the neutral CLI -// could take the `@workspacejson/cli` name. The rename is the ratified change; -// everything below still asserts that BEHAVIOR is untouched. -check("renamed to an accurate identity, old name released for the neutral CLI", - manifests.old.name === "@workspacejson/cli" && manifests.new.name === "@workspacejson/datahub-adapter", - `old=${manifests.old.name} new=${manifests.new.name}`); -check("version unchanged: 0.0.1", - manifests.old.version === manifests.new.version && manifests.new.version === "0.0.1"); -check("STILL PRIVATE (private:true) — must never be published", - manifests.old.private === true && manifests.new.private === true, - `old=${manifests.old.private} new=${manifests.new.private}`); -// META-247 INTENTIONAL DIFFERENCE: the `workspacejson` bin now belongs to the -// neutral CLI, so this private adapter had to surrender it or collide. -check("bin surrendered `workspacejson` to the neutral CLI", - manifests.old.bin.workspacejson === "./dist/cli.js" - && manifests.new.bin["workspacejson-datahub-adapter"] === "./dist/cli.js" - && manifests.new.bin.workspacejson === undefined); -check("exports/main/types unchanged", - equalish(manifests.old.exports, manifests.new.exports) - && manifests.old.main === manifests.new.main - && manifests.old.types === manifests.new.types); -check("declares NO generate command (it is not the producer)", - !JSON.stringify(manifests.new).includes("generate") - && !readFileSync(join(SIDES.new, "src/cli.ts"), "utf8").includes("generateWorkspaceJson")); -check("does not depend on agents-audit or @workspacejson/rules", - !("agents-audit" in (manifests.new.dependencies ?? {})) - && !("@workspacejson/rules" in (manifests.new.dependencies ?? {}))); - -// --------------------------------------------------------------------------- -console.log("\n=============================================================="); -console.log(" 2. PATH NORMALIZATION AND KEY CONSTRUCTION (old vs new)"); -console.log("=============================================================="); - -const mods = {}; -for (const [side, dir] of Object.entries(SIDES)) { - mods[side] = await import(join(dir, "dist/index.js")); -} - -const normalizeCases = [ - ["./src/models/a.sql", "src/models/a.sql"], - ["src/models/a.sql/", "src/models/a.sql"], - ["src/models/a.sql", "src/models/a.sql"], - ["./a//b/", "a//b"], -]; -for (const [input, expected] of normalizeCases) { - const o = mods.old.canonical(input), n = mods.new.canonical(input); - check(`canonical(${JSON.stringify(input)}) => ${JSON.stringify(n)}`, o === n && n === expected, `old=${o} new=${n} expected=${expected}`); -} - -const prefixCases = [ - ["/repo", "/repo", ""], // dbt project IS the git root - ["/repo", "/repo/analytics", "analytics"], // nested one level - ["/repo", "/repo/sub/warehouse", "sub/warehouse"], - ["/repo", "/elsewhere", null], // escapes the git root -]; -for (const [root, proj, expected] of prefixCases) { - const o = mods.old.computeProjectPrefix(root, proj), n = mods.new.computeProjectPrefix(root, proj); - check(`computeProjectPrefix(${root}, ${proj}) => ${JSON.stringify(n)}`, o === n && n === expected, `old=${o} new=${n} expected=${expected}`); -} - -const keyCases = [ - ["", "models/customers.sql", "models/customers.sql"], - ["analytics", "models/customers.sql", "analytics/models/customers.sql"], - ["sub/warehouse", "./models/x.sql", "sub/warehouse/models/x.sql"], -]; -for (const [prefix, original, expected] of keyCases) { - const o = mods.old.normalizeModelPath(prefix, original), n = mods.new.normalizeModelPath(prefix, original); - check(`normalizeModelPath(${JSON.stringify(prefix)}, ${JSON.stringify(original)}) => ${JSON.stringify(n)}`, - o === n && n === expected, `old=${o} new=${n} expected=${expected}`); -} - -// --------------------------------------------------------------------------- -console.log("\n=============================================================="); -console.log(" 3. dbt PROJECT DISCOVERY AND MANIFEST EXTRACTION"); -console.log("=============================================================="); - -const repo = mkdtempSync(join(tmpdir(), "shim-parity-")); -mkdirSync(join(repo, "analytics/models"), { recursive: true }); -mkdirSync(join(repo, "sub/warehouse/models"), { recursive: true }); -mkdirSync(join(repo, "node_modules/decoy"), { recursive: true }); -writeFileSync(join(repo, "analytics/dbt_project.yml"), "name: analytics\n"); -writeFileSync(join(repo, "sub/warehouse/dbt_project.yml"), "name: warehouse\n"); -writeFileSync(join(repo, "node_modules/decoy/dbt_project.yml"), "name: decoy\n"); // must be ignored - -const foundOld = mods.old.findDbtProjects(repo); -const foundNew = mods.new.findDbtProjects(repo); -check("findDbtProjects discovers BOTH dbt projects (multi-project guard)", - foundNew.length === 2 && equalish(foundOld, foundNew), `old=${JSON.stringify(foundOld)} new=${JSON.stringify(foundNew)}`); -check("findDbtProjects ignores node_modules (would otherwise inflate the count)", - !foundNew.some((p) => p.includes("node_modules"))); -check("findDbtProjects output is deterministic (sorted)", - equalish(foundNew, [...foundNew].sort())); - -const manifest = { - nodes: { - "model.analytics.customers": { resource_type: "model", unique_id: "model.analytics.customers", original_file_path: "models/customers.sql" }, - "model.analytics.orders": { resource_type: "model", unique_id: "model.analytics.orders", original_file_path: "models/orders.sql" }, - "test.analytics.not_a_model":{ resource_type: "test", unique_id: "test.analytics.not_a_model", original_file_path: "tests/t.sql" }, - "model.analytics.nopath": { resource_type: "model", unique_id: "model.analytics.nopath" }, - }, -}; -const modelsOld = mods.old.extractModels(manifest), modelsNew = mods.new.extractModels(manifest); -check("extractModels returns only resource_type=model with a path", - modelsNew.length === 2 && equalish(modelsOld, modelsNew), `new=${JSON.stringify(modelsNew)}`); -check("extractModels tolerates an empty manifest", - equalish(mods.old.extractModels({}), mods.new.extractModels({})) && mods.new.extractModels({}).length === 0); - -// --------------------------------------------------------------------------- -console.log("\n=============================================================="); -console.log(" 4. JOIN AGAINST fileIndex (the actual DataHub fix)"); -console.log("=============================================================="); - -const fileIndex = { "analytics/models/customers.sql": { fragility: 0.5 }, "analytics/models/orders.sql": { fragility: 0.1 } }; - -const joinedOld = mods.old.joinModels(modelsNew, "analytics", fileIndex); -const joinedNew = mods.new.joinModels(modelsNew, "analytics", fileIndex); -check("nested dbt project joins 2/2 after prefix normalization", - joinedNew.matched === 2 && joinedNew.total === 2 && equalish(joinedOld, joinedNew)); - -// The regression this shim exists to prevent: WITHOUT the prefix, a nested -// project silently matches nothing. -const naiveOld = mods.old.joinModels(modelsNew, "", fileIndex); -const naiveNew = mods.new.joinModels(modelsNew, "", fileIndex); -check("PERTURBED: without the project prefix the same join collapses to 0/2", - naiveNew.matched === 0 && naiveNew.total === 2 && equalish(naiveOld, naiveNew), - `new matched=${naiveNew.matched}/${naiveNew.total}`); - -const partial = mods.new.joinModels(modelsNew, "analytics", { "analytics/models/customers.sql": {} }); -check("PERTURBED: partial fileIndex yields a partial match (1/2), not all-or-nothing", - partial.matched === 1 && partial.total === 2); -check("join rows expose normalizedKey and matched for every model", - joinedNew.rows.length === 2 && joinedNew.rows.every((r) => typeof r.normalizedKey === "string" && typeof r.matched === "boolean")); - -// --------------------------------------------------------------------------- -console.log("\n=============================================================="); -console.log(" 5. CLI RUNTIME: fileIndex shapes and zero-join exit code"); -console.log("=============================================================="); - -function runCli(side, { workspace, manifestNodes, projectDir = "analytics" }) { - const dir = mkdtempSync(join(tmpdir(), `shim-cli-${side}-`)); - mkdirSync(join(dir, projectDir, "target"), { recursive: true }); - writeFileSync(join(dir, projectDir, "dbt_project.yml"), "name: analytics\n"); - writeFileSync(join(dir, projectDir, "target/manifest.json"), JSON.stringify({ nodes: manifestNodes })); - mkdirSync(join(dir, ".agents"), { recursive: true }); - writeFileSync(join(dir, ".agents/workspace.json"), JSON.stringify(workspace)); - const result = spawnSync(process.execPath, [ - join(SIDES[side], "dist/cli.js"), - "--git-root", dir, - "--manifest", join(dir, projectDir, "target/manifest.json"), - "--workspace-json", join(dir, ".agents/workspace.json"), - ], { encoding: "utf8" }); - rmSync(dir, { recursive: true, force: true }); - return { status: result.status, out: `${result.stdout}${result.stderr}` }; -} - -const nodes = { - "model.a.customers": { resource_type: "model", unique_id: "model.a.customers", original_file_path: "models/customers.sql" }, -}; -const nestedGenerated = { generated: { fileIndex: { "analytics/models/customers.sql": {} } } }; -const legacyTopLevel = { fileIndex: { "analytics/models/customers.sql": {} } }; -const noMatch = { generated: { fileIndex: { "totally/other/path.sql": {} } } }; - -for (const [label, workspace, expectStatus, expectPattern] of [ - ["generated.fileIndex joins and exits 0", nestedGenerated, 0, /1\/1 models matched/], - ["legacy top-level fileIndex fallback still supported", legacyTopLevel, 0, /1\/1 models matched/], - ["ZERO-JOIN exits non-zero (the silent failure HAC-75 surfaces)", noMatch, 1, /0\/1 models matched/], -]) { - for (const side of ["old", "new"]) { - const r = runCli(side, { workspace, manifestNodes: nodes }); - check(`[${side}] ${label}`, r.status === expectStatus && expectPattern.test(r.out), - `status=${r.status} (expected ${expectStatus})\n ${r.out.split("\n").slice(0, 3).join("\n ")}`); - } -} - -// dbt project outside the git root must refuse rather than emit bogus keys -for (const side of ["old", "new"]) { - const dir = mkdtempSync(join(tmpdir(), `shim-outside-${side}-`)); - const outside = mkdtempSync(join(tmpdir(), `shim-elsewhere-${side}-`)); - mkdirSync(join(outside, "target"), { recursive: true }); - writeFileSync(join(outside, "target/manifest.json"), JSON.stringify({ nodes })); - mkdirSync(join(dir, ".agents"), { recursive: true }); - writeFileSync(join(dir, ".agents/workspace.json"), JSON.stringify(nestedGenerated)); - const r = spawnSync(process.execPath, [ - join(SIDES[side], "dist/cli.js"), - "--git-root", dir, "--manifest", join(outside, "target/manifest.json"), - "--workspace-json", join(dir, ".agents/workspace.json"), - ], { encoding: "utf8" }); - check(`[${side}] PERTURBED: dbt project outside git root refuses with exit 2`, - r.status === 2 && /is not inside git root/.test(`${r.stdout}${r.stderr}`), - `status=${r.status}`); - rmSync(dir, { recursive: true, force: true }); rmSync(outside, { recursive: true, force: true }); -} - -rmSync(repo, { recursive: true, force: true }); - -console.log("\n=============================================================="); -console.log(` RESULT: ${pass} passed, ${fail} failed (total ${pass + fail})`); -if (fail) console.log(` FAILED: ${failures.join(", ")}`); -console.log("=============================================================="); -process.exit(fail ? 1 : 0); diff --git a/packages/agents-audit-compat/src/package-metadata.test.ts b/packages/agents-audit-compat/src/package-metadata.test.ts index 1474436..460817e 100644 --- a/packages/agents-audit-compat/src/package-metadata.test.ts +++ b/packages/agents-audit-compat/src/package-metadata.test.ts @@ -1,4 +1,4 @@ -import { readFileSync } from 'node:fs'; +import { existsSync, readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { describe, expect, it } from 'vitest'; @@ -54,13 +54,11 @@ describe('package metadata', () => { } }); - it('keeps the DataHub adapter private and separate from the producer', () => { - const pkg = readPackageJson('packages/datahub-adapter/package.json'); - // The two CLI packages must not converge during migration. META-236 owns - // any future decision about this package's identity; until then it is a - // private dbt/DataHub adapter and must never be published. - expect(pkg.name).toBe('@workspacejson/datahub-adapter'); - expect(pkg.private).toBe(true); - expect((pkg.bin as { [key: string]: string } | undefined)?.['workspacejson-datahub-adapter']).toBe('./dist/cli.js'); + it('no longer defines the DataHub adapter, which was extracted', () => { + // META-248 moved the dbt/DataHub adapter to workspacejson/datahub-agent, + // which owns DataHub consumption. It was staged here only while its + // permanent owner was undecided. This asserts the extraction stayed done; + // scripts/check-architecture.mjs enforces the same boundary, red-tested. + expect(existsSync(resolve(repoRoot, 'packages/datahub-adapter'))).toBe(false); }); }); diff --git a/packages/datahub-adapter/README.md b/packages/datahub-adapter/README.md deleted file mode 100644 index 7fa2902..0000000 --- a/packages/datahub-adapter/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# @workspacejson/cli - -Joins dbt models to [workspace.json](https://www.workspacejson.dev) behavioral -intelligence (fragility, co-change, modification history) by -**repository-root-relative POSIX path**. - -## The problem it solves - -dbt's `manifest.json` reports `original_file_path` relative to the **dbt project -root**. A workspace.json `fileIndex` is keyed relative to the **git repository -root** (see `@workspacejson/spec`, VR-640). When the dbt project is nested in a -subdirectory — `dbt/` under the repo root, the common real-world layout — the two -path representations differ by exactly that prefix, and a naive join silently -returns **zero rows** (no error). This was reproduced empirically in the HAC-75 -probe: 5/5 match at the repo root, 5/5 miss when nested. - -## The fix (the normalization shim) - -``` -projectPrefix = relative(gitRoot, dbtProjectDir) // "dbt" when nested, "" at root -joinKey = projectPrefix ? `${projectPrefix}/${original_file_path}` : original_file_path -``` - -`dbtProjectDir` is wherever `dbt_project.yml` lives. Real repos hold more than one -dbt project, so `findDbtProjects()` enumerates **all** of them rather than -assuming a single knowable path. - -## Usage - -```bash -workspacejson --git-root . --manifest dbt/target/manifest.json --workspace-json .agents/workspace.json -``` - -Exits non-zero if any dbt project produces zero joined rows. - -## Status - -MVP. The path-normalization shim and join are implemented and tested (including -the HAC-75 nested-repo case, red-first). Consumes `@workspacejson/spec` as a -workspace sibling for the `fileIndex` key contract. diff --git a/packages/datahub-adapter/package.json b/packages/datahub-adapter/package.json deleted file mode 100644 index 1129cc7..0000000 --- a/packages/datahub-adapter/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "@workspacejson/datahub-adapter", - "version": "0.0.1", - "private": true, - "description": "Private adapter that joins dbt models to workspace.json behavioral intelligence by repository-root-relative path (the DataHub / HAC-75 normalization shim). Not the workspace.json producer.", - "license": "Apache-2.0", - "author": "workspace-json contributors", - "homepage": "https://www.workspacejson.dev", - "repository": { - "type": "git", - "url": "git+https://github.com/workspacejson/cli.git", - "directory": "packages/datahub-adapter" - }, - "engines": { - "node": ">=20.0.0" - }, - "type": "module", - "main": "./dist/index.js", - "module": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "import": "./dist/index.js", - "types": "./dist/index.d.ts" - } - }, - "bin": { - "workspacejson-datahub-adapter": "./dist/cli.js" - }, - "files": [ - "dist", - "README.md" - ], - "scripts": { - "build": "tsup src/index.ts src/cli.ts --format esm --dts", - "test": "vitest run", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@workspacejson/spec": "0.4.4" - }, - "devDependencies": { - "@types/node": "22.19.17", - "tsup": "^8.0.0", - "typescript": "^5.4.0", - "vitest": "^1.6.0" - } -} diff --git a/packages/datahub-adapter/src/cli.ts b/packages/datahub-adapter/src/cli.ts deleted file mode 100644 index 15eb267..0000000 --- a/packages/datahub-adapter/src/cli.ts +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env node -import { readFileSync } from "node:fs"; -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -import { extractModels, findDbtProjects, type DbtManifest } from "./dbt.js"; -import { computeProjectPrefix, canonical } from "./normalize.js"; -import { joinModels, type FileIndex } from "./join.js"; - -interface Args { - gitRoot: string; - manifest: string; - workspaceJson: string; -} - -function parseArgs(argv: string[]): Args { - const map = new Map(); - for (let i = 0; i < argv.length; i += 1) { - const a = argv[i]; - if (a?.startsWith("--")) map.set(a.slice(2), argv[++i] ?? ""); - } - const gitRoot = resolve(map.get("git-root") ?? process.cwd()); - const manifest = resolve(map.get("manifest") ?? "target/manifest.json"); - const workspaceJson = resolve(map.get("workspace-json") ?? ".agents/workspace.json"); - return { gitRoot, manifest, workspaceJson }; -} - -function readJson(path: string): T { - return JSON.parse(readFileSync(path, "utf8")) as T; -} - -/** - * Join dbt models (manifest.json) to workspace.json behavioral intelligence, - * normalizing dbt's project-relative paths to repo-root-relative keys (VR-640). - * Exits non-zero if any dbt project produces zero joined rows — the silent - * zero-row failure HAC-75 was built to surface. - */ -export function run(args: Args): number { - const manifest = readJson(args.manifest); - const models = extractModels(manifest); - const workspace = readJson<{ generated?: { fileIndex?: FileIndex }; fileIndex?: FileIndex }>( - args.workspaceJson, - ); - const fileIndex: FileIndex = workspace.generated?.fileIndex ?? workspace.fileIndex ?? {}; - - // The dbt project these models belong to is the manifest's grandparent dir - // (/target/manifest.json -> ). `projects` is the full multi-project - // enumeration (VR-640 guard), reported so a repo with several dbt projects is - // visibly not being treated as one. - const projects = findDbtProjects(args.gitRoot); - const dbtProjectDir = dirname(dirname(args.manifest)); - const prefix = computeProjectPrefix(args.gitRoot, dbtProjectDir); - - if (prefix === null) { - console.error(`dbt project ${dbtProjectDir} is not inside git root ${args.gitRoot}`); - return 2; - } - - const result = joinModels(models, prefix, fileIndex); - console.log(`git root: ${args.gitRoot}`); - console.log(`dbt project: ${canonical(dbtProjectDir)} (prefix: "${prefix}")`); - console.log(`projects found: ${projects.length}`); - console.log(`join: ${result.matched}/${result.total} models matched fileIndex`); - for (const row of result.rows) { - console.log(` [${row.matched ? "hit " : "MISS"}] ${row.originalFilePath} -> ${row.normalizedKey}`); - } - return result.total > 0 && result.matched === 0 ? 1 : 0; -} - -if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { - process.exit(run(parseArgs(process.argv.slice(2)))); -} diff --git a/packages/datahub-adapter/src/dbt.ts b/packages/datahub-adapter/src/dbt.ts deleted file mode 100644 index 581c907..0000000 --- a/packages/datahub-adapter/src/dbt.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { readdirSync } from "node:fs"; -import { join } from "node:path"; - -import type { DbtModel } from "./join.js"; - -interface ManifestNode { - resource_type?: string; - unique_id?: string; - original_file_path?: string; -} - -export interface DbtManifest { - nodes?: Record; -} - -/** Extract model nodes (resource_type === "model") from a parsed manifest.json. */ -export function extractModels(manifest: DbtManifest): DbtModel[] { - const models: DbtModel[] = []; - for (const node of Object.values(manifest.nodes ?? {})) { - if (node.resource_type === "model" && node.original_file_path) { - models.push({ - uniqueId: node.unique_id ?? node.original_file_path, - originalFilePath: node.original_file_path, - }); - } - } - return models; -} - -const DEFAULT_IGNORE = new Set(["node_modules", ".git", "target", "dbt_packages", "dist"]); - -/** - * Enumerate every dbt project under `root` — a directory containing a - * `dbt_project.yml`. VR-640's multi-project guard: real repos hold more than one - * dbt project, so the shim must never assume a single knowable project dir. - */ -export function findDbtProjects(root: string, ignore: Set = DEFAULT_IGNORE): string[] { - const found: string[] = []; - const walk = (dir: string): void => { - let entries: ReturnType; - try { - entries = readdirSync(dir, { withFileTypes: true }); - } catch { - return; // unreadable dir — skip, don't crash the sweep - } - for (const entry of entries) { - if (entry.isFile() && entry.name === "dbt_project.yml") { - found.push(dir); - } else if (entry.isDirectory() && !ignore.has(entry.name)) { - walk(join(dir, entry.name)); - } - } - }; - walk(root); - return found.sort(); -} diff --git a/packages/datahub-adapter/src/index.ts b/packages/datahub-adapter/src/index.ts deleted file mode 100644 index 0afc89a..0000000 --- a/packages/datahub-adapter/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { toPosix, canonical, computeProjectPrefix, normalizeModelPath } from "./normalize.js"; -export { joinModels } from "./join.js"; -export type { DbtModel, FileIndex, JoinRow, JoinResult } from "./join.js"; -export { extractModels, findDbtProjects } from "./dbt.js"; -export type { DbtManifest } from "./dbt.js"; diff --git a/packages/datahub-adapter/src/join.ts b/packages/datahub-adapter/src/join.ts deleted file mode 100644 index 412692b..0000000 --- a/packages/datahub-adapter/src/join.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { FileIndexEntry } from "@workspacejson/spec"; - -import { normalizeModelPath } from "./normalize.js"; - -/** A dbt model as read from manifest.json. */ -export interface DbtModel { - uniqueId: string; - /** `original_file_path` — relative to the dbt project root. */ - originalFilePath: string; -} - -/** - * workspace.json behavioral intelligence, keyed by repository-root-relative - * POSIX path (per @workspacejson/spec fileIndex, VR-640). Values follow the - * spec's own FileIndexEntry contract — the join only needs key presence. - */ -export type FileIndex = Record; - -export interface JoinRow { - uniqueId: string; - originalFilePath: string; - /** The normalized repo-root-relative key used for lookup. */ - normalizedKey: string; - matched: boolean; -} - -export interface JoinResult { - rows: JoinRow[]; - matched: number; - total: number; -} - -/** - * Join dbt models to a workspace.json fileIndex. Each model's project-relative - * `original_file_path` is normalized to a repo-root-relative key via - * `projectPrefix`, then looked up by membership. Pass an empty `projectPrefix` - * to join without normalization (the naive path — correct only when the dbt - * project sits at the git root). - */ -export function joinModels( - models: DbtModel[], - projectPrefix: string, - fileIndex: FileIndex, -): JoinResult { - const rows = models.map((m): JoinRow => { - const normalizedKey = normalizeModelPath(projectPrefix, m.originalFilePath); - return { - uniqueId: m.uniqueId, - originalFilePath: m.originalFilePath, - normalizedKey, - matched: Object.prototype.hasOwnProperty.call(fileIndex, normalizedKey), - }; - }); - return { rows, matched: rows.filter((r) => r.matched).length, total: rows.length }; -} diff --git a/packages/datahub-adapter/src/normalize.ts b/packages/datahub-adapter/src/normalize.ts deleted file mode 100644 index 1efc061..0000000 --- a/packages/datahub-adapter/src/normalize.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { relative, sep } from "node:path"; - -/** Convert an OS-native path to canonical POSIX (forward slashes). No-op on POSIX. */ -export function toPosix(p: string): string { - return sep === "\\" ? p.replace(/\\/g, "/") : p; -} - -/** Canonical form: POSIX separators, no leading "./", no trailing slash. */ -export function canonical(p: string): string { - return toPosix(p) - .replace(/^\.\//, "") - .replace(/\/+$/, ""); -} - -/** - * The repository-root-relative POSIX prefix from the git root to a dbt project - * directory. Empty string when the dbt project IS the git root (the control - * layout where dbt's `original_file_path` already matches the workspace.json key). - * - * Returns null when `dbtProjectDir` is not inside `gitRoot` (relative path - * escapes upward) — a caller that gets null must not attempt the join, since no - * repo-root-relative key can be derived. - */ -export function computeProjectPrefix(gitRoot: string, dbtProjectDir: string): string | null { - const rel = canonical(relative(gitRoot, dbtProjectDir)); - if (rel === ".." || rel.startsWith("../")) return null; - return rel; -} - -/** - * Normalize a dbt `original_file_path` (relative to the dbt project root) into - * the canonical workspace.json fileIndex key: repository-root-relative POSIX - * (VR-640). This is the entire DataHub join fix — prepend the project prefix so - * a nested dbt project's model paths line up with git-root-relative keys. - */ -export function normalizeModelPath(projectPrefix: string, originalFilePath: string): string { - const rel = canonical(originalFilePath); - return projectPrefix ? `${projectPrefix}/${rel}` : rel; -} diff --git a/packages/datahub-adapter/test/find-projects.test.ts b/packages/datahub-adapter/test/find-projects.test.ts deleted file mode 100644 index 588ec74..0000000 --- a/packages/datahub-adapter/test/find-projects.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { fileURLToPath } from "node:url"; -import { dirname, resolve } from "node:path"; - -import { describe, expect, it } from "vitest"; - -import { findDbtProjects } from "../src/index.js"; - -const here = dirname(fileURLToPath(import.meta.url)); -const MULTI = resolve(here, "fixtures/multi"); - -describe("VR-640 multi-project guard: findDbtProjects", () => { - it("enumerates every dbt_project.yml, not just the first", () => { - const projects = findDbtProjects(MULTI).map((p) => p.slice(MULTI.length + 1)); - expect(projects).toEqual(["analytics", "sub/warehouse"]); - }); - - it("returns an empty list for a tree with no dbt projects", () => { - expect(findDbtProjects(resolve(here, "fixtures/multi/analytics/models"))).toEqual([]); - }); -}); diff --git a/packages/datahub-adapter/test/fixtures/multi/analytics/dbt_project.yml b/packages/datahub-adapter/test/fixtures/multi/analytics/dbt_project.yml deleted file mode 100644 index 4aa2ffc..0000000 --- a/packages/datahub-adapter/test/fixtures/multi/analytics/dbt_project.yml +++ /dev/null @@ -1,3 +0,0 @@ -name: analytics -version: "1.0.0" -profile: analytics diff --git a/packages/datahub-adapter/test/fixtures/multi/analytics/models/customers.sql b/packages/datahub-adapter/test/fixtures/multi/analytics/models/customers.sql deleted file mode 100644 index 43258a7..0000000 --- a/packages/datahub-adapter/test/fixtures/multi/analytics/models/customers.sql +++ /dev/null @@ -1 +0,0 @@ -select 1 as id diff --git a/packages/datahub-adapter/test/fixtures/multi/sub/warehouse/dbt_project.yml b/packages/datahub-adapter/test/fixtures/multi/sub/warehouse/dbt_project.yml deleted file mode 100644 index 82b16d8..0000000 --- a/packages/datahub-adapter/test/fixtures/multi/sub/warehouse/dbt_project.yml +++ /dev/null @@ -1,3 +0,0 @@ -name: warehouse -version: "1.0.0" -profile: warehouse diff --git a/packages/datahub-adapter/test/join.nested.test.ts b/packages/datahub-adapter/test/join.nested.test.ts deleted file mode 100644 index b9c16d6..0000000 --- a/packages/datahub-adapter/test/join.nested.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { computeProjectPrefix, joinModels, type DbtModel, type FileIndex } from "../src/index.js"; - -// ─── Fixtures from the HAC-75 join probe ───────────────────────────────────── -// jaffle_shop_duckdb's five models. `originalFilePath` is exactly what dbt's -// manifest.json reports (relative to the dbt project root) — identical whether -// the project sits at the repo root or nested under dbt/. -const MODELS: DbtModel[] = [ - { uniqueId: "model.jaffle_shop.customers", originalFilePath: "models/customers.sql" }, - { uniqueId: "model.jaffle_shop.orders", originalFilePath: "models/orders.sql" }, - { uniqueId: "model.jaffle_shop.stg_customers", originalFilePath: "models/staging/stg_customers.sql" }, - { uniqueId: "model.jaffle_shop.stg_payments", originalFilePath: "models/staging/stg_payments.sql" }, - { uniqueId: "model.jaffle_shop.stg_orders", originalFilePath: "models/staging/stg_orders.sql" }, -]; - -// workspace.json fileIndex as the REAL Vreko emitter produced it on the nested -// repo (git-root-relative keys, observed in the HAC-75 probe): dbt/ prefix. -const NESTED_FILE_INDEX: FileIndex = { - "dbt/models/customers.sql": {}, - "dbt/models/orders.sql": {}, - "dbt/models/staging/stg_customers.sql": {}, - "dbt/models/staging/stg_payments.sql": {}, - "dbt/models/staging/stg_orders.sql": {}, -}; - -// Control layout: dbt project AT the git root, so keys carry no prefix. -const ROOT_FILE_INDEX: FileIndex = { - "models/customers.sql": {}, - "models/orders.sql": {}, - "models/staging/stg_customers.sql": {}, - "models/staging/stg_payments.sql": {}, - "models/staging/stg_orders.sql": {}, -}; - -describe("DataHub join — nested dbt project (HAC-75 treatment)", () => { - it("RED: naive join (original_file_path used directly) matches 0/5", () => { - // No normalization — the failure the probe reproduced. This is the guard: - // if the shim were a no-op, the join silently returns zero rows. - const naive = joinModels(MODELS, "", NESTED_FILE_INDEX); - expect(naive.matched).toBe(0); - expect(naive.rows.every((r) => !r.matched)).toBe(true); - }); - - it("GREEN: shim join (repo-root-relative normalization) matches 5/5", () => { - const prefix = computeProjectPrefix("/repo", "/repo/dbt"); - expect(prefix).toBe("dbt"); - const joined = joinModels(MODELS, prefix as string, NESTED_FILE_INDEX); - expect(joined.matched).toBe(5); - expect(joined.rows.every((r) => r.matched)).toBe(true); - expect(joined.rows[0]?.normalizedKey).toBe("dbt/models/customers.sql"); - }); - - it("control: dbt project at git root joins 5/5 with an empty prefix", () => { - const prefix = computeProjectPrefix("/repo", "/repo"); - expect(prefix).toBe(""); - const joined = joinModels(MODELS, prefix as string, ROOT_FILE_INDEX); - expect(joined.matched).toBe(5); - }); - - it("rejects a dbt project outside the git root (no derivable key)", () => { - expect(computeProjectPrefix("/repo/dbt", "/repo")).toBeNull(); - }); -}); diff --git a/packages/datahub-adapter/tsconfig.json b/packages/datahub-adapter/tsconfig.json deleted file mode 100644 index 149104e..0000000 --- a/packages/datahub-adapter/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "dist", - "rootDir": "src", - "declaration": true, - "declarationMap": false, - "emitDeclarationOnly": false - }, - "include": [ - "src/**/*.ts", - "../../types/**/*.d.ts" - ], - "exclude": [ - "src/**/*.test.ts", - "test/**", - "src/**/__tests__/**" - ] -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 627b3a5..24ff6ea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,25 +99,6 @@ importers: specifier: ^1.6.0 version: 1.6.1(@types/node@22.19.17) - packages/datahub-adapter: - dependencies: - '@workspacejson/spec': - specifier: 0.4.4 - version: 0.4.4 - devDependencies: - '@types/node': - specifier: 22.19.17 - version: 22.19.17 - tsup: - specifier: ^8.0.0 - version: 8.5.1(postcss@8.5.23)(typescript@5.9.3) - typescript: - specifier: ^5.4.0 - version: 5.9.3 - vitest: - specifier: ^1.6.0 - version: 1.6.1(@types/node@22.19.17) - packages: '@babel/runtime@7.29.7': diff --git a/scripts/check-architecture.mjs b/scripts/check-architecture.mjs index 1e40984..5acfb03 100644 --- a/scripts/check-architecture.mjs +++ b/scripts/check-architecture.mjs @@ -131,6 +131,11 @@ const FOREIGN_DIRECTORIES = [ { path: "vsix", owner: "workspacejson/integrations" }, { path: "packages/spec", owner: "workspacejson/standard" }, { path: "packages/rules", owner: "workspacejson/standard" }, + // Extracted under META-248. It was DataHub consumer logic staged here while + // its permanent owner was decided, never durable CLI architecture. Listing + // it keeps it from drifting back: `neutral-producer-purity` only scans + // packages/cli, so a re-added sibling package would otherwise pass. + { path: "packages/datahub-adapter", owner: "workspacejson/datahub-agent" }, { path: "src/pages", owner: "workspacejson/site" }, { path: "astro.config.mjs", owner: "workspacejson/site" }, ]; @@ -223,8 +228,8 @@ for (const manifestPath of manifests) { } } - if (manifest.name === "@workspacejson/datahub-adapter" && manifest.private !== true) { - report("private-package-publication", manifestPath, "@workspacejson/datahub-adapter must remain private:true; it is staged here pending extraction to workspacejson/datahub-agent and must never be published from this repository"); + if (manifest.name === "@workspacejson/datahub-adapter") { + report("repository-boundary", manifestPath, "@workspacejson/datahub-adapter was extracted to workspacejson/datahub-agent under META-248 and must not be redefined here"); } if (STANDARD_OWNED.has(manifest.name)) { @@ -249,7 +254,7 @@ if (existsSync(workflowsDirectory)) { } } if (content.includes("@workspacejson/datahub-adapter")) { - report("private-package-publication", file, "publishing workflow references the private @workspacejson/datahub-adapter"); + report("repository-boundary", file, "publishing workflow references @workspacejson/datahub-adapter, which was extracted to workspacejson/datahub-agent under META-248 and is not publishable from here"); } } } diff --git a/scripts/check-architecture.test.mjs b/scripts/check-architecture.test.mjs index d113f36..b82aaff 100644 --- a/scripts/check-architecture.test.mjs +++ b/scripts/check-architecture.test.mjs @@ -119,11 +119,21 @@ const cases = [ }), }, { - name: "private-package-publication: @workspacejson/datahub-adapter loses private:true", - expect: "private-package-publication", - mutate: (root) => patchJson(join(root, "packages/datahub-adapter/package.json"), (m) => { - delete m.private; - }), + // META-248: the adapter was extracted to workspacejson/datahub-agent. The + // old red test mutated its manifest, which no longer exists. What must stay + // red is its RETURN — re-creating the package here in any form. + name: "repository-boundary: the extracted DataHub adapter package is re-added", + expect: "repository-boundary", + mutate: (root) => write(join(root, "packages/datahub-adapter/package.json"), + `${JSON.stringify({ name: "@workspacejson/datahub-adapter", version: "0.0.1", private: true }, null, 2)}\n`), + }, + { + // Private:true is not a defence once the package is gone — the boundary is. + // A "helpfully" public re-add must be just as red as a private one. + name: "repository-boundary: the extracted DataHub adapter returns as a public package", + expect: "repository-boundary", + mutate: (root) => write(join(root, "packages/datahub-adapter/package.json"), + `${JSON.stringify({ name: "@workspacejson/datahub-adapter", version: "0.0.1" }, null, 2)}\n`), }, { name: "foreign-publish: release workflow publishing a standard-owned package", @@ -132,8 +142,8 @@ const cases = [ `name: Rogue\non: workflow_dispatch\njobs:\n publish:\n runs-on: ubuntu-latest\n steps:\n - run: npm publish --workspace @workspacejson/spec\n`), }, { - name: "private-package-publication: workflow publishing the private DataHub adapter", - expect: "private-package-publication", + name: "repository-boundary: workflow publishing the extracted DataHub adapter", + expect: "repository-boundary", mutate: (root) => write(join(root, ".github/workflows/rogue.yml"), `name: Rogue\non: workflow_dispatch\njobs:\n publish:\n runs-on: ubuntu-latest\n steps:\n - run: npm publish --workspace @workspacejson/datahub-adapter\n`), },