chore(#2801): remove the hostBehaviors.reviewerCli deprecated alias (#3272)

* test(#2801): failing-first suite for the hostBehaviors.reviewerCli alias removal

Inverts the Phase 5a rows that assert the derived legacy alias still
contributes a reviewer slug, and adds the removal-warning coverage the
alias's exit needs (ADR-2782 D9).

RED against unmodified production code, by design: the six shipped
manifests still declare the key and collectReviewerWarnings emits nothing
for hostBehaviors.

Refs #2801

* chore(#2801): remove the hostBehaviors.reviewerCli deprecated alias

ADR-2782 D9, Phase 7 — the final phase of epic #2782.

The derived legacy alias survived one release (Phase 5a shipped in 1.9.0;
1.9.1 and 1.10.0 have since gone out), so it goes. A declared reviewer
body is now the only route onto the reviewer roster.

- deriveReviewerSlugs no longer reads runtime.hostBehaviors.reviewerCli
- the key is stripped from the six manifests that carried it; each already
  declares a reviewer body whose slug equals its capability id, so the
  derived roster is unchanged at the same twelve slugs
- collectReviewerWarnings emits a presence-based, non-fatal removal notice
  for any manifest still declaring the key, reaching both the build-time
  registry generation and the third-party overlay load path. The check runs
  before the reviewer-body early-return, because the manifest it exists for
  is the alias-only one that has no body.
- hostBehaviors stays an open, unvalidated bag for its other 59 keys; this
  adds one keyed removal notice, not general validation

Refs #2801

* refactor(#2801): give the reviewer-warning channel a typed IR

Review finding: the new tests asserted with String#includes() on the
warning prose, which CONTRIBUTING.md's 'Prohibited: Raw Text Matching on
Test Outputs' bans in favor of a typed intermediate representation.

Adds the IR beside the renderer rather than replacing it, which is the
shape that section prescribes and bin/verify-reapply-patches.cjs already
models:

- REVIEWER_WARNING, a frozen code enum
- REMOVED_REVIEWER_CLI_FIELD, so the emitting site and its test share one
  symbol instead of duplicating a literal
- collectReviewerWarningRecords(cap), returning typed records

collectReviewerWarnings(cap) keeps its exact string[] contract as a thin
map over the records, so both production consumers are untouched. Every
section-K row now asserts on record.code/field/capId and none on the
rendered message. Locks the code surface, asserts the renderer stays
one-to-one with the records, and migrates the pre-existing Phase 2 test
on the same channel off prose matching.

Refs #2801

* test(#2801): invert the section F alias fall-through regression row

Caught by the remote runner: 2 unique failures on both Node lanes out of
31,692. tests/reviewer-lane-declarations.test.cjs section F — Phase 5a's
isolated-security-review regressions — asserted that a blank reviewer.slug
falls through to the hostBehaviors.reviewerCli alias rather than dropping
the lane. That is the direct inverse of this phase's contract.

The original rationale held only while the alias existed. With it gone
there is nothing to fall through to: a blank body is not a declaration,
and a declaration is the only route onto the roster.

Inverted rather than deleted — the row carries the adversarial-review
provenance for the slug trim, and removing a security regression guard to
make a change pass is backwards. The duplicate row added earlier in
section C is dropped instead; section F is its canonical home.

Also corrects two count strings Phase 5b left at eleven while asserting
twelve, which would misreport on failure.

Refs #2801

* docs(#2801): give the removed reviewerCli flag a migration path

The Reference edit alone satisfied CI — a file under docs/ moved, so
lint-docs-required.cjs was green — while the task-oriented quadrant said
nothing about the removal. A maintainer whose lane had just gone silent
would have found the field documented as removed and no page telling them
what to do about it.

Adds a migration section to the how-to: the symptom, the verbatim warning
they will see, the before/after manifest, and the note to keep the
reviewer slug equal to the capability id so existing
review.default_reviewers entries and --<slug> flags survive.

Refs #2801

* chore(#2801): backfill changeset pr number to 3272

* feat(#2801): close the runtime.hostBehaviors vocabulary

ADR-1016 closes twelve descriptor axes and rejects an open escape hatch
in the descriptor. It never mentioned runtime.hostBehaviors, and that
silence was read as permission: 59 keys across 18 manifests, 39 of them
set by a single capability, validated by nothing. The reference docs went
further and attributed the open seam to ADR-1016, which does not mention
the field at all.

KNOWN_HOST_BEHAVIORS enumerates the vocabulary. An undeclared key yields a
non-fatal UNKNOWN_HOST_BEHAVIOR record on the same D4.3 channel as the
alias removal notice, reaching both build-time generation and overlay
install.

Warning, never error, for the reason this phase exists: an error would
hard-break an out-of-tree descriptor carrying a bespoke key with no
deprecation window, which is what reviewerCli was given a release to
avoid. Escalation is a separate decision.

reviewerCli is excluded from the unknown-key sweep so it keeps its own
notice with the migration pointer rather than drawing two records.

A parity test binds the vocabulary to the shipped manifests in both
directions, and a second asserts no shipped capability draws a notice, so
the closure is provably inert in-tree.

Records the decision and the miscitation as an ADR-1016 amendment.

Refs #2801

* fix(#2801): bound and sanitize the unknown-key diagnostics

Two findings from an isolated adversarial review of the closure commit,
both proven by execution rather than asserted.

MAJOR, introduced by the closure: the new Object.keys(hostBehaviors) sweep
had no ceiling. An installed third-party manifest is bounded only by
MANIFEST_MAX_BYTES, and an 8.69MB manifest with 800,000 keys produced
800,000 records and ~139MB of message text, retained for the registry's
lifetime in OverlayMeta.diagnostics. Now capped at ten records plus a
summary carrying omittedCount, mirroring capability-loader's existing
slice(0,3) idiom. The same manifest now yields 11 records and 1748 chars.

MINOR, newly reachable: manifest-supplied key names were interpolated raw.
Unlike cap.id, which validateCapability gates on KEBAB_RE before these
diagnostics run, hostBehaviors keys have no grammar check anywhere, so
ANSI escapes and CRLF reached stderr and OverlayMeta.warnings intact. New
describeKey replaces C0/C1 controls and clips at 80 chars. The file
already had describeValue for this and applied it only to values.

Both fixes land on the pre-existing reviewer.* sweep too — it carried the
identical pair, and fixing only the new copy would leave the same defect
one screen from its own fix.

Refs #2801

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-08-09 19:18:56 -04:00
committed by GitHub
parent 58d73dd220
commit 653f95e39f
18 changed files with 1007 additions and 117 deletions

View File

@@ -187,7 +187,6 @@ const capabilities = {
"effortSurface": "undocumented"
},
"hostBehaviors": {
"reviewerCli": true,
"projectInstructionFile": "GEMINI.md",
"noPathRewrite": true,
"hookPathStyle": "raw",
@@ -577,8 +576,7 @@ const capabilities = {
"skillsGlobalOnboarding": true,
"legacyCommandsGsdInstallMigration": true,
"legacyCommandsGsdUninstall": "global",
"hyphenNameAgentBody": true,
"reviewerCli": true
"hyphenNameAgentBody": true
}
},
"reviewer": {
@@ -1085,8 +1083,7 @@ const capabilities = {
"tomlConfigInstall": true,
"cleanupSkillSidecars": true,
"agentTomlFiles": true,
"frontmatterDialect": "codex",
"reviewerCli": true
"frontmatterDialect": "codex"
}
},
"reviewer": {
@@ -1337,8 +1334,7 @@ const capabilities = {
"stop",
"subagentStart",
"subagentStop"
],
"reviewerCli": true
]
}
},
"reviewer": {
@@ -2661,8 +2657,7 @@ const capabilities = {
"skipHomePrefixSubstitution": true,
"skipSettingsUi": true,
"skipUpdateBannerCommand": true,
"skipCodexSkillsManifest": true,
"reviewerCli": true
"skipCodexSkillsManifest": true
}
},
"reviewer": {
@@ -3005,8 +3000,7 @@ const capabilities = {
"legacyCommandsGsdCleanup": true,
"legacyCommandsGsdInstallMigration": true,
"legacyCommandsGsdUninstall": true,
"hyphenNameAgentBody": true,
"reviewerCli": true
"hyphenNameAgentBody": true
}
},
"reviewer": {
@@ -4853,7 +4847,6 @@ const runtimes = {
"effortSurface": "undocumented"
},
"hostBehaviors": {
"reviewerCli": true,
"projectInstructionFile": "GEMINI.md",
"noPathRewrite": true,
"hookPathStyle": "raw",
@@ -5114,8 +5107,7 @@ const runtimes = {
"skillsGlobalOnboarding": true,
"legacyCommandsGsdInstallMigration": true,
"legacyCommandsGsdUninstall": "global",
"hyphenNameAgentBody": true,
"reviewerCli": true
"hyphenNameAgentBody": true
}
},
"reviewer": {
@@ -5431,8 +5423,7 @@ const runtimes = {
"tomlConfigInstall": true,
"cleanupSkillSidecars": true,
"agentTomlFiles": true,
"frontmatterDialect": "codex",
"reviewerCli": true
"frontmatterDialect": "codex"
}
},
"reviewer": {
@@ -5683,8 +5674,7 @@ const runtimes = {
"stop",
"subagentStart",
"subagentStop"
],
"reviewerCli": true
]
}
},
"reviewer": {
@@ -6264,8 +6254,7 @@ const runtimes = {
"skipHomePrefixSubstitution": true,
"skipSettingsUi": true,
"skipUpdateBannerCommand": true,
"skipCodexSkillsManifest": true,
"reviewerCli": true
"skipCodexSkillsManifest": true
}
},
"reviewer": {
@@ -6477,8 +6466,7 @@ const runtimes = {
"legacyCommandsGsdCleanup": true,
"legacyCommandsGsdInstallMigration": true,
"legacyCommandsGsdUninstall": true,
"hyphenNameAgentBody": true,
"reviewerCli": true
"hyphenNameAgentBody": true
}
},
"reviewer": {

View File

@@ -1578,6 +1578,122 @@ const KNOWN_REVIEWER_FIELDS = new Set([
'handler',
]);
/**
* The closed `runtime.hostBehaviors` vocabulary (ADR-1016, closed via #2801).
*
* ADR-1016's core principle is that every per-runtime difference is a value over
* a closed primitive vocabulary, and that a host needing a new shape gets a
* reviewed first-party primitive rather than "an open escape hatch in the
* descriptor" (§Alternatives #2 rejects exactly that). `hostBehaviors` was the
* one hole left in that closure: 59 keys across 18 manifests, 39 of them set by
* a single capability, validated by nothing. It was described in the reference
* docs as a deliberate open seam sanctioned by ADR-1016 — ADR-1016 does not
* mention `hostBehaviors` at all, and its stated principle is the opposite.
*
* Adding a key here is deliberate and reviewed. That friction IS the decision
* (ADR-1016 §Consequences: "the closed vocabulary must grow (reviewed) when a
* genuinely new host shape appears — intentional friction, the trust boundary").
*
* WARNING, never error. Two reasons:
* 1. It matches the ADR-2782 D4.3 treatment of an unknown `reviewer` field, so
* a manifest built against a newer GSD degrades visibly instead of failing
* the build of a repo that merely reads it.
* 2. An error would hard-break an out-of-tree descriptor carrying a bespoke
* key, with no deprecation window — the exact mistake #2801's own alias
* removal spent a full release avoiding. Escalating to an error is a later
* step and needs its own window.
*
* Kept in sorted order, and `tests/reviewer-manifest-body.test.cjs` asserts this
* set equals the keys the shipped manifests actually declare, so the list cannot
* silently rot away from reality (DEFECT.GENERATIVE-FIX).
*/
const KNOWN_HOST_BEHAVIORS = new Set([
'agentFileExtension',
'agentFrontmatterExtensions',
'agentManifestStyle',
'agentTomlFiles',
'attributionConfigResolver',
'attributionSource',
'authorsCanonicalWorkflow',
'brandingRewrites',
'cleanupSkillSidecars',
'clineRulesSurface',
'combinedFamilyInstall',
'commandBodyConverter',
'doneBannerStyle',
'flatCommandDir',
'frontmatterDialect',
'globalDirResolver',
'hookPathStyle',
'hooksJsonSurface',
'hyphenNameAgentBody',
'installsCommandBodiesForWorkflowDelegation',
'legacyCommandsGsdCleanup',
'legacyCommandsGsdInstallMigration',
'legacyCommandsGsdUninstall',
'legacyDevinSkillsCleanup',
'localCommandsViaRules',
'localInstallDeferred',
'localInstallStyle',
'localTargetIsProjectRoot',
'managedHookEvents',
'mcpCompanion',
'namedSubagentsSupported',
'nativeModelAliases',
'nativePlugin',
'noPathRewrite',
'ownsClaudePaths',
'permissionsSchema',
'pluginOnlyInstall',
'projectInstructionFile',
'reapplyCommand',
'reportCommandsDir',
'reportSkillsCount',
'retiredArtifacts',
'settingsFileByScope',
'sharedHooksDirName',
'skillFrontmatterVersion',
'skillPriorityFrontmatter',
'skillsGlobalOnboarding',
'skillsManifestPrefix',
'skipCodexSkillsManifest',
'skipHomePrefixSubstitution',
'skipSettingsUi',
'skipSharedHooksInstall',
'skipUpdateBannerCommand',
'soloStageMetadata',
'sourceMarkerFile',
'tomlConfigInstall',
'trackCategoryDescription',
'verificationStyle',
'writeCategoryDescription',
]);
/**
* Frozen reason codes for the non-fatal reviewer diagnostics (ADR-2782 D4.3).
*
* The IR behind `collectReviewerWarnings`' rendered strings. Tests assert on
* these codes; the rendered `message` is operator console output and tests must
* not depend on it (CONTRIBUTING.md, "Prohibited: Raw Text Matching on Test
* Outputs"), which is the same split `bin/verify-reapply-patches.cjs` uses.
*
* Adding a code is THREE coordinated changes: this enum, the emitting site in
* `collectReviewerWarningRecordFields`, and the test that locks
* `Object.keys(REVIEWER_WARNING).sort()`. That coupling is the point — it stops
* the code surface drifting from the test surface.
*/
const REVIEWER_WARNING = Object.freeze({
/** A key inside a `reviewer` body that this GSD version does not know. */
UNKNOWN_REVIEWER_FIELD: 'unknown_reviewer_field',
/** A `runtime.hostBehaviors` key that was removed from the vocabulary. */
REMOVED_HOST_BEHAVIOR: 'removed_host_behavior',
/** A `runtime.hostBehaviors` key outside the closed vocabulary. */
UNKNOWN_HOST_BEHAVIOR: 'unknown_host_behavior',
});
/** Dotted path of the field removed by ADR-2782 D9 / #2801. */
const REMOVED_REVIEWER_CLI_FIELD = 'runtime.hostBehaviors.reviewerCli';
const KNOWN_PROBE_FIELDS = new Set(['kind', 'binary', 'needle', 'timeoutMs', 'hostConfigKey', 'path']);
/** A bounded probe timeout must be a finite, positive INTEGER of milliseconds. */
@@ -1615,6 +1731,41 @@ function describeValue(v) {
}
}
/**
* Ceiling on how many undeclared-key diagnostics one capability may produce.
*
* The loops below iterate MANIFEST-SUPPLIED keys, and an installed third-party
* manifest is bounded only by MANIFEST_MAX_BYTES (8MB). Unbounded, one manifest
* of 800k keys yields 800k records and ~139MB of message text, retained for the
* registry's lifetime in OverlayMeta.diagnostics. Ten is enough to act on; the
* rest are summarized. Mirrors the existing truncation idiom in
* `capability-loader.cts` (`crossErrs.slice(0, 3)`).
*/
const MAX_REPORTED_UNKNOWN_KEYS = 10;
/** Ceiling on how much of one manifest-supplied key name a diagnostic repeats. */
const MAX_REPORTED_KEY_CHARS = 80;
/**
* Render a manifest-supplied KEY for a diagnostic: control-safe and bounded.
*
* Key names carry no grammar anywhere — unlike `cap.id`, which `validateCapability`
* gates on KEBAB_RE before these diagnostics run — so a key is fully
* attacker-controlled text heading for stderr and OverlayMeta.warnings. C0/C1
* controls (ESC, CR, LF) become U+FFFD so a key cannot emit terminal escapes or
* forge a log line, and the result is clipped so one key cannot carry megabytes
* into a retained diagnostic.
*
* @param {*} key
* @returns {string}
*/
function describeKey(key) {
const raw = typeof key === 'string' ? key : String(key);
// eslint-disable-next-line no-control-regex
const safe = raw.replace(/[\x00-\x1f\x7f-\x9f]/g, '<27>');
return safe.length > MAX_REPORTED_KEY_CHARS ? safe.slice(0, MAX_REPORTED_KEY_CHARS) + '…' : safe;
}
/** Extract a message from an unknown thrown value without throwing again. */
function safeErrorMessage(err) {
try {
@@ -1661,7 +1812,39 @@ function validateEnumField(ctx, label, value, validSet) {
}
/**
* Collect NON-FATAL diagnostics for a reviewer body (ADR-2782 D4.3).
* Collect NON-FATAL reviewer diagnostics for a capability manifest as TYPED
* RECORDS (ADR-2782 D4.3, plus the D9 `hostBehaviors.reviewerCli` removal
* notice, #2801).
*
* This is the IR. `collectReviewerWarnings` below renders it to strings for the
* two production consumers; tests assert on these records instead of matching
* the rendered prose (CONTRIBUTING.md, "Prohibited: Raw Text Matching on Test
* Outputs").
*
* Record shape — `code` and `capId` are always present; the rest is per-code:
* { code: REVIEWER_WARNING.UNKNOWN_REVIEWER_FIELD,
* capId, field: 'reviewer.<key>', knownFields: string[], message }
* { code: REVIEWER_WARNING.REMOVED_HOST_BEHAVIOR,
* capId, field: 'runtime.hostBehaviors.reviewerCli',
* replacement: 'reviewer', docs: '<how-to path>', message }
*
* TOTAL: returns an array for ANY input and never throws. It runs on
* loadRegistry's ACCEPT path, so a diagnostic that throws would cost the user a
* lane that is otherwise perfectly valid (#1461 OVL-1).
*
* @param {object} cap A capability manifest.
* @returns {Array<object>} Warning records; empty when there is nothing to say.
*/
function collectReviewerWarningRecords(cap) {
try {
return collectReviewerWarningRecordFields(cap);
} catch {
return [];
}
}
/**
* Render the IR for operator console output.
*
* Kept separate from validateReviewerBody so validateCapability's contract
* (`=> string[]` of ERRORS) is unchanged for its two existing callers. The
@@ -1669,35 +1852,135 @@ function validateEnumField(ctx, label, value, validSet) {
* through OverlayMeta.warnings. A warning written only to a build log nobody
* reads is not a warning (ADR-2782 D4, "Where warnings surface").
*
* The `string[]` shape is load-bearing for both consumers and does not change.
*
* @param {object} cap A capability manifest.
* @returns {string[]} Warning strings; empty when there is nothing to say.
*/
function collectReviewerWarnings(cap) {
// Same totality contract as validateReviewerBody, for the same reason: this is
// called from loadRegistry's accept path, and a diagnostic that throws must
// never cost the user a working lane.
try {
return collectReviewerWarningFields(cap);
} catch {
return [];
}
return collectReviewerWarningRecords(cap).map((record) => record.message);
}
function collectReviewerWarningFields(cap) {
const warnings = [];
if (typeof cap !== 'object' || cap === null || Array.isArray(cap)) return warnings;
const r = cap.reviewer;
if (typeof r !== 'object' || r === null || Array.isArray(r)) return warnings;
function collectReviewerWarningRecordFields(cap) {
const records = [];
if (typeof cap !== 'object' || cap === null || Array.isArray(cap)) return records;
const capId = typeof cap.id === 'string' ? cap.id : '(unknown)';
// ADR-2782 D9 / #2801 — the REMOVED `runtime.hostBehaviors.reviewerCli` alias.
//
// Emitted BEFORE the `reviewer`-body early-return below, deliberately. The
// manifest this notice exists for is the ALIAS-ONLY one, which by definition
// carries no body; after that guard the check would fire only for
// capabilities that already declare a lane — exactly the set that does not
// need telling.
//
// Presence-based, not `=== true`: the key is unknown at ANY value now, which
// is the same rule the unknown-`reviewer.*`-field loop below applies. Own-key
// read, so a polluted prototype cannot manufacture this warning on every
// otherwise-innocent manifest. This is ONE keyed removal notice, not general
// `hostBehaviors` validation — the closed vocabulary below handles that (#2801).
const runtimeBody = cap.runtime;
if (typeof runtimeBody === 'object' && runtimeBody !== null && !Array.isArray(runtimeBody)) {
const hostBehaviors = runtimeBody.hostBehaviors;
if (
typeof hostBehaviors === 'object'
&& hostBehaviors !== null
&& !Array.isArray(hostBehaviors)
&& Object.prototype.hasOwnProperty.call(hostBehaviors, 'reviewerCli')
) {
records.push({
code: REVIEWER_WARNING.REMOVED_HOST_BEHAVIOR,
capId,
field: REMOVED_REVIEWER_CLI_FIELD,
replacement: 'reviewer',
docs: 'docs/how-to/ship-a-reviewer-lane.md',
message:
'⚠ capability "' + capId + '" ' + REMOVED_REVIEWER_CLI_FIELD + ' was removed (ADR-2782 D9) ' +
'— ignored, and it contributes no reviewer lane. Declare a `reviewer` body instead; see ' +
'docs/how-to/ship-a-reviewer-lane.md',
});
}
// #2801 — the closed `hostBehaviors` vocabulary (ADR-1016). `reviewerCli` is
// excluded here: it already drew its own removal notice above, and a second,
// generic "unknown key" record for the same key would be noise, not signal.
if (typeof hostBehaviors === 'object' && hostBehaviors !== null && !Array.isArray(hostBehaviors)) {
let reported = 0;
let omitted = 0;
for (const key of Object.keys(hostBehaviors)) {
if (isReservedName(key) || key === 'reviewerCli' || KNOWN_HOST_BEHAVIORS.has(key)) continue;
if (reported >= MAX_REPORTED_UNKNOWN_KEYS) {
omitted += 1;
continue;
}
reported += 1;
const safeKey = describeKey(key);
records.push({
code: REVIEWER_WARNING.UNKNOWN_HOST_BEHAVIOR,
capId,
field: 'runtime.hostBehaviors.' + safeKey,
message:
'⚠ capability "' + capId + '" runtime.hostBehaviors.' + safeKey + ' is not a known host behavior ' +
'in this GSD version — ignored. Adding one is a reviewed first-party change (ADR-1016).',
});
}
if (omitted > 0) {
records.push({
code: REVIEWER_WARNING.UNKNOWN_HOST_BEHAVIOR,
capId,
field: 'runtime.hostBehaviors',
truncated: true,
omittedCount: omitted,
message:
'⚠ capability "' + capId + '" declares ' + omitted + ' further unknown runtime.hostBehaviors ' +
'key(s), not listed. A manifest this far outside the vocabulary is likely built for a ' +
'different GSD version.',
});
}
}
}
const r = cap.reviewer;
if (typeof r !== 'object' || r === null || Array.isArray(r)) return records;
// Same ceiling and the same key sanitization as the hostBehaviors sweep above.
// This loop predates #2801 and carried both defects; fixing only the new copy
// would leave the identical defect one screen away from its own fix.
let reportedFields = 0;
let omittedFields = 0;
for (const key of Object.keys(r)) {
if (isReservedName(key) || KNOWN_REVIEWER_FIELDS.has(key)) continue;
warnings.push(
'⚠ capability "' + capId + '" reviewer.' + key + ' is not a known reviewer field ' +
'in this GSD version — ignored. Known fields: ' + [...KNOWN_REVIEWER_FIELDS].join(', '),
);
if (reportedFields >= MAX_REPORTED_UNKNOWN_KEYS) {
omittedFields += 1;
continue;
}
reportedFields += 1;
const safeKey = describeKey(key);
records.push({
code: REVIEWER_WARNING.UNKNOWN_REVIEWER_FIELD,
capId,
field: 'reviewer.' + safeKey,
knownFields: [...KNOWN_REVIEWER_FIELDS],
message:
'⚠ capability "' + capId + '" reviewer.' + safeKey + ' is not a known reviewer field ' +
'in this GSD version — ignored. Known fields: ' + [...KNOWN_REVIEWER_FIELDS].join(', '),
});
}
return warnings;
if (omittedFields > 0) {
records.push({
code: REVIEWER_WARNING.UNKNOWN_REVIEWER_FIELD,
capId,
field: 'reviewer',
knownFields: [...KNOWN_REVIEWER_FIELDS],
truncated: true,
omittedCount: omittedFields,
message:
'⚠ capability "' + capId + '" declares ' + omittedFields + ' further unknown reviewer field(s), ' +
'not listed.',
});
}
return records;
}
/**
@@ -3170,8 +3453,14 @@ module.exports = {
VALID_EVIDENCE_CLASSES,
VALID_LANE_HANDLERS,
KNOWN_REVIEWER_FIELDS,
KNOWN_HOST_BEHAVIORS,
MAX_REPORTED_UNKNOWN_KEYS,
MAX_REPORTED_KEY_CHARS,
validateReviewerBody,
collectReviewerWarnings,
collectReviewerWarningRecords,
REVIEWER_WARNING,
REMOVED_REVIEWER_CLI_FIELD,
VALID_INSTALL_SURFACES,
VALID_PERMISSION_WRITERS,
VALID_EXTENDED_HOOK_EVENTS,