Skip to content

fix(catalog): settle the two guard collisions before Phase E inherits them - #89

Merged
mbeacom merged 1 commit into
mainfrom
mbeacom-supreme-guacamole
Aug 5, 2026
Merged

fix(catalog): settle the two guard collisions before Phase E inherits them#89
mbeacom merged 1 commit into
mainfrom
mbeacom-supreme-guacamole

Conversation

@mbeacom

@mbeacom mbeacom commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Two guard collisions, both found by implementation sessions, both worked around rather than resolved, and both certain to recur in Phase G. Maintainer decisions 2026-08-05, recorded so the next reader gets the reasoning rather than an inference from what happened to be committed.

D9 — reading an installed dependency's version

FR-029 / T063 require the picomatch version be read at runtime, never transcribed, so the glob engine recorded in a snapshot is the engine that actually ran. ADR-0013 / FR-002 forbid a dynamic loader, and the guard bans import.meta.resolve anywhere in the adapter.

Read naively those collide. They don't — 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

Walking up to node_modules/picomatch/package.json and reading its version takes the value from the installed artifact rather than a literal in our source, while invoking no resolver and loading no code. Recorded as package-boundary.md §6.

The ADR-0016 obligation stays attached: a read that silently returns undefined and one that returns 4.0.5 are indistinguishable from a green suite alone, so the assertion against the lockfile must be observed failing.

D10 — source-scan.ts false positives

The scans match from '…' and import '…' textually rather than resolving them, so a file that states a rule is indistinguishable from one that breaks it. It fired on:

  • the string 'bulk-import' — ADR-0015's own plugin name — read as a side-effecting import '…'
  • prose ending in … from before a quote

Phase C and Phase D each renamed around it, which leaves the trap armed for the next writer.

EXCLUDED_FROM_SCAN now carries the consumer's two 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 — so neither was scannable without an entry.

The patterns are deliberately not loosened. A scan that misses a real edge is worse than one that occasionally over-matches, and the exclusion list is the intended relief valve.

The guard caught this change

The pinned-set assertion — "an exclusion list that can grow without anyone noticing is the same defect the scans are guarding against"failed on my own edit until updated deliberately. That is the design working on the person extending it, and it is why the list is pinned rather than merely commented.

Checks

bun test 1504 pass / 0 fail · typecheck clean · check:depscore-has-no-adapter-deps: ok · check:freeze-hashes → ok over both frozen trees · adr lint 20 records, 0 errors.

Standing

ADR-0014 rung 1 only. No rung-2 or rung-3 claim, no release scheduled or prepared. ADR-0012 gate 3 recorded as observed; gate 4 unmet and not yet testable.

With this, every decision Phase E inherits has been made.

… them

Both were found by implementation sessions, both were worked around rather
than resolved, and both would have recurred in Phase G. Maintainer decisions
2026-08-05.

D9 - reading an installed dependency's version. FR-029/T063 require the
picomatch version be read at runtime and never transcribed; ADR-0013/FR-002
forbid a dynamic loader and the guard bans import.meta.resolve anywhere in
the adapter. Read naively those collide. They do not: the distinction the
guard protects is dynamic MODULE LOADING, not filesystem access. Walking up
to node_modules/picomatch/package.json and reading its version field invokes
no resolver, imports no module, and cannot load code, while still taking the
value from the installed artifact rather than a literal in our source.

Recorded as package-boundary.md §6 with the distinction tabulated, because
to the session that implemented it this looked like a route around the guard
rather than a path through it, and the next reader deserves the reasoning
rather than an inference from what happened to be committed. The ADR-0016
obligation stays attached: a read that silently returns undefined and one
that returns 4.0.5 are indistinguishable from a green suite alone.

D10 - source-scan false positives. The scans match `from '...'` and
`import '...'` textually rather than resolving them, so a file that STATES a
rule is indistinguishable from one that BREAKS it. This fired on the string
'bulk-import' - ADR-0015's own plugin name - and on prose ending in "from "
before a quote. Two sessions renamed around it, which leaves the trap armed
for the next writer.

EXCLUDED_FROM_SCAN now carries the consumer's two boundary guards, each of
which must name the very thing it forbids: the schema file it pins by hash,
and the adapter package it proves is never imported. The patterns are
deliberately NOT loosened - a scan that misses a real edge is worse than one
that occasionally over-matches, and the exclusion list is the intended relief
valve.

The pinned-set assertion caught this change and failed until updated
deliberately, which is the guard working exactly as designed; it now reads
five files and records why the two were added.

Gates: bun test 1504 pass / 0 fail, typecheck clean, check:deps ok,
check:freeze-hashes ok over both frozen trees, adr lint 20 records 0/0.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings August 5, 2026 21:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Documents dependency-version discovery and expands source-scan exclusions for consumer boundary guards.

Changes:

  • Defines permitted runtime manifest reading for picomatch.
  • Adds and pins two consumer guard exclusions.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
specs/010-catalog-backstage/contracts/package-boundary.md Records dependency-version lookup decision.
packages/adapters/catalog-backstage/test/source-scan.ts Extends scan exclusions and rationale.
packages/adapters/catalog-backstage/test/no-dynamic-loader.test.ts Pins the expanded exclusion set.
Suppressed comments (1)

packages/adapters/catalog-backstage/test/source-scan.ts:66

  • package-boundary.md §4 is about dependency allowlists, not these source-scan exclusions, so this reference directs maintainers to unrelated rationale. Remove it or replace it with the actual exclusion-policy section.
  // never imported. See package-boundary.md §4.

Comment on lines +67 to +68
'packages/catalog-envelope/test/no-core-schema-change.test.ts',
'packages/catalog-envelope/test/no-adapter-import.test.ts',
* 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

---

## §6. Reading an installed dependency's version — permitted, and why it is not loader behaviour
@mbeacom
mbeacom merged commit 19f3164 into main Aug 5, 2026
10 checks passed
@mbeacom
mbeacom deleted the mbeacom-supreme-guacamole branch August 5, 2026 21:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants