* feat(#2904): add a `reviewer` entry type so third-party reviewer lanes are discoverable
ADR-2782 made a reviewer lane installable by a third party, but neither
discoverability catalog could hold one. The Community Capability Registry
requires a non-empty `loopExtensionPoints` and forbids a lane from declaring
any hook kind, so a `role: "reviewer"` entry is unsatisfiable by construction;
the EoS Registry is for ADR-1239 host integrations, which a lane is not.
Adds a third catalog — `docs/registries/reviewers.json` →
`docs/registries/reviewer-registry.md` — whose `interactions` describes the
lane: slug, flags, transport, evidenceClass, reviewsSection, requiresBinaries,
configKeys, runtimeCompat.
The lane vocabulary is a hand-written mirror of `capability-validator.cjs`
(the same pattern as `AXES` mirroring `HOST_INTEGRATION_AXES`), with parity
enforced by tests/registry-reviewer-parity.test.cjs. `slug` deliberately uses
the runtime `LANE_SLUG_RE` grammar rather than the registry's kebab-only `id`
rule, so real lanes (`lm_studio`, `4o-mini`) are not rejected.
Two binary type branches became three-way Map dispatch. Both now fail loudly
on an unrecognized type instead of silently treating it as a capability —
`renderMarkdown` in particular writes a committed catalog file, so a silent
wrong-title render was the worst failure mode available.
Also fixed while here: `gen-registry.cjs` parsed source JSON with no error
handling, so a malformed or non-array `capabilities.json` surfaced as a raw
SyntaxError/TypeError instead of an actionable CLI error.
Closes #2904
* fix(#2904): bound and sanitize untrusted registry `interactions` strings
Review findings from the pre-PR passes.
Security (isolated pass): `interactions` string fields reached the generated,
committed Markdown catalog with no control-character check and no length
bound. A `reviewsSection` carrying ESC and a `requiresBinaries` element
carrying NUL plus 5000 characters validated clean and landed verbatim in the
rendered page — `mdInline` escapes Markdown metacharacters and collapses CRLF,
but nothing else. The identical gap already existed on the capability type's
`configKeys`/`requires`/`runtimeCompat`/`produces`/`consumes`, so it is fixed
there too rather than inherited into a third type.
`hasDisallowedControlChar` is lifted to module scope so exactly one
implementation exists, and a shared `validateStringArrayField` enforces
control-character rejection, a 200-character element cap and a 50-element
array cap for both types.
Correctness (standards pass): `renderMarkdown`'s per-entry summary builder was
still an if/else-if chain whose final `else` was the capability branch — the
one per-type dispatch point this change had not converted, and the same silent
fallthrough it removes elsewhere. It now lives in `RENDER_META` alongside the
title, so a fourth type cannot silently inherit capability's rendering. All
three types' rendered output is byte-identical to before the refactor.
Also corrects a test comment that still claimed the reviewer suites were
failing-first against an unmodified module.
* chore(#2904): backfill changeset PR number (#2912)
(cherry picked from commit 90771ddf02)
This commit is contained in:
@@ -3,11 +3,13 @@
|
||||
|
||||
/**
|
||||
* scripts/validate-registry.cjs — CLI validator for the third-party
|
||||
* discoverability catalogs (issue #2182):
|
||||
* discoverability catalogs (issue #2182, plus #2904):
|
||||
*
|
||||
* - docs/registries/capabilities.json ("GSD Community Capability Registry")
|
||||
* - docs/registries/eos.json ("GSD EoS Registry", PR2 — optional
|
||||
* until that JSON file ships)
|
||||
* - docs/registries/reviewers.json ("GSD Reviewer Lane Registry",
|
||||
* issue #2904 — optional until that JSON file ships)
|
||||
*
|
||||
* Validates each source's JSON array against the closed schema in
|
||||
* scripts/registry-schema.cjs (validateEntries). Human-readable errors go to
|
||||
@@ -33,14 +35,15 @@ const { validateEntries } = require('./registry-schema.cjs');
|
||||
// a subprocess against isolated temp-fixture directories via `cwd`.
|
||||
const SOURCES = [
|
||||
{ file: 'capabilities.json', type: 'capability' },
|
||||
{ file: 'eos.json', type: 'eos' },
|
||||
{ file: 'eos.json', type: 'eos', optional: true },
|
||||
{ file: 'reviewers.json', type: 'reviewer', optional: true },
|
||||
];
|
||||
|
||||
/**
|
||||
* Load + validate a single registry JSON file.
|
||||
*
|
||||
* @param {string} jsonPath absolute path to the registry JSON file
|
||||
* @param {'capability'|'eos'} type
|
||||
* @param {'capability'|'eos'|'reviewer'} type
|
||||
* @returns {{ok: boolean, errors: Array<{index: number, id?: string, field: string, reason: string}>}}
|
||||
*/
|
||||
function validateFile(jsonPath, type) {
|
||||
@@ -81,10 +84,11 @@ function main() {
|
||||
const results = [];
|
||||
let anyFailed = false;
|
||||
|
||||
for (const { file, type } of SOURCES) {
|
||||
for (const { file, type, optional } of SOURCES) {
|
||||
const jsonPath = path.join(registriesDir, file);
|
||||
// eos.json is optional until PR2 ships it — skip silently when absent.
|
||||
if (type === 'eos' && !fs.existsSync(jsonPath)) continue;
|
||||
// eos.json (pre-PR2) and reviewers.json (issue #2904) are optional until
|
||||
// their source JSON ships — skip silently when absent.
|
||||
if (optional && !fs.existsSync(jsonPath)) continue;
|
||||
|
||||
const verdict = validateFile(jsonPath, type);
|
||||
results.push({ file, type, ok: verdict.ok, errors: verdict.errors });
|
||||
|
||||
Reference in New Issue
Block a user