Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,22 @@ describe('FR-002 — no dynamic loader anywhere in the adapter source', () => {
expect(files.length).toBeGreaterThan(0);
});

test('the excluded-from-scan set is exactly the three self-referential guard files', () => {
test('the excluded-from-scan set is exactly the five self-referential guard files', () => {
// These files contain the rule literals themselves. The exclusion is pinned
// so it cannot grow into a way of hiding a violation.
// so it cannot grow into a way of hiding a violation: adding an entry fails
// this test until someone updates it deliberately, which is the point.
//
// The two consumer entries were added on 2026-08-05. Each must name the very
// thing it forbids — the schema file it pins by hash, and the adapter package
// it proves is never imported — so both were unscannable without an entry
// here. The alternative two sessions reached for first was renaming around
// the scanner, which leaves the trap armed for the next writer.
expect([...EXCLUDED_FROM_SCAN]).toEqual([
'packages/adapters/catalog-backstage/test/envelope-shape-locality.test.ts',
'packages/adapters/catalog-backstage/test/no-dynamic-loader.test.ts',
'packages/adapters/catalog-backstage/test/source-scan.ts',
'packages/catalog-envelope/test/no-core-schema-change.test.ts',
'packages/catalog-envelope/test/no-adapter-import.test.ts',
]);
});

Expand Down
24 changes: 24 additions & 0 deletions packages/adapters/catalog-backstage/test/source-scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,35 @@ export const CONSUMER_PACKAGE_NAME = '@adrkit/catalog-envelope';
*
* This is asserted to be exactly this set. An exclusion list that can grow
* without anyone noticing is the same defect the scans are guarding against.
*
* **A guard that must name what it forbids belongs here.** The scans are
* deliberately literal — `importSpecifiers` matches `from '…'` and
* `import '…'` textually rather than resolving them — so a file that *states*
* a rule is indistinguishable to it from a file that *breaks* one. Feature 010
* hit this three times: the consumer's two boundary guards (which must name
* `schema/adr.schema.json` to hash it, and `@adrkit/catalog-backstage` to
* forbid importing it), and, more surprisingly, ordinary prose and string data
* — the literal `'bulk-import'`, which is ADR-0015's own plugin name, scanned
* as a side-effecting `import '…'`.
*
* Adding an entry is the correct fix and is preferred over renaming around
* the scanner, which is what two sessions did before this list was extended.
* Renaming leaves the trap armed for the next writer; listing the file is
* visible, reviewable, and asserted. Do **not** loosen the patterns to make a
* false positive go away — a scan that misses a real edge is worse than one
* that occasionally over-matches, and this list is the intended relief valve.
*
* @see specs/010-catalog-backstage/contracts/package-boundary.md §4
*/
export const EXCLUDED_FROM_SCAN: readonly string[] = [
'packages/adapters/catalog-backstage/test/envelope-shape-locality.test.ts',
'packages/adapters/catalog-backstage/test/no-dynamic-loader.test.ts',
'packages/adapters/catalog-backstage/test/source-scan.ts',
// Consumer-side boundary guards. Each must name the very thing it forbids:
// the schema file it pins by hash, and the adapter package it proves is
// never imported. See package-boundary.md §4.
'packages/catalog-envelope/test/no-core-schema-change.test.ts',
'packages/catalog-envelope/test/no-adapter-import.test.ts',
Comment on lines +67 to +68
];

export interface ScannedFile {
Expand Down
39 changes: 39 additions & 0 deletions specs/010-catalog-backstage/contracts/package-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,42 @@ verbatim by `scripts/check-deps.test.ts`.
A line number is a reference that nothing checks. A reason string is a reference the
test suite checks on every run, so a citation that goes stale fails the build rather
than quietly misleading a reader.

---

## §6. Reading an installed dependency's version — permitted, and why it is not loader behaviour

**Decided by the maintainer, 2026-08-05.**

FR-029 / T063 require the `picomatch` version be **read at runtime from the resolved
dependency, never transcribed**, so that the frozen glob engine recorded in a snapshot is
the engine that actually ran. ADR-0013 and FR-002 separately forbid any dynamic runtime
adapter/plugin loader, and the guard enforcing that
(`packages/adapters/catalog-backstage/test/no-dynamic-loader.test.ts`) bans
`import.meta.resolve` anywhere in the adapter.

Read naively, those two requirements collide.

**They do not.** The permitted implementation is a **filesystem read of the installed
dependency's manifest** — walking up from the module to `node_modules/picomatch/package.json`
and reading its `version` field. This satisfies FR-029 because the value comes from the
installed artifact rather than from a literal in our source, and it does not engage the
loader-guard's concern because it invokes no resolver, imports no module, and cannot load
code.

The distinction the guard protects is **dynamic module loading**, not **filesystem access**:

| | Loads code | Resolver invoked | Permitted |
|---|---|---|---|
| `import.meta.resolve(...)`, dynamic `import()`, `require.resolve` | yes | yes | **no** — ADR-0013, FR-002 |
| Reading `node_modules/<dep>/package.json` as a file | no | no | **yes** — this section |

Recorded here rather than left in a source comment because it looked, to the session that
implemented it, like a route *around* the guard rather than a path *through* it — and a
future reader deciding the same question deserves the reasoning, not an inference from
what happened to be committed.

**The observation still applies.** A test asserting the read value matches the lockfile is
required and must be observed failing (ADR-0016), because a read that silently returns
`undefined` and a read that returns the right version are indistinguishable from a green
suite alone.