fix(report): accept min host version floors (#17)

* fix(report): accept min host version floors

* fix(inspector): treat bundled channel entries as channel coverage

* fix(inspector): classify bundled channel probes

* fix(policy): allow wildcard seam rules
This commit is contained in:
Vincent Koc 2026-05-02 18:20:13 -07:00 committed by GitHub
parent 2eda65a8a9
commit 06cc55ce51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 63 additions and 13 deletions

View File

@ -241,7 +241,9 @@ function executionChecks(executionResults, policy, options) {
}
function findPolicyMatch(rules, item) {
return rules.find((rule) => item.seam === rule.seam && item.reason?.includes(rule.reasonIncludes));
return rules.find(
(rule) => (rule.seam === "*" || item.seam === rule.seam) && item.reason?.includes(rule.reasonIncludes),
);
}
function failedExecutionEvidence(executionResults) {

View File

@ -290,7 +290,7 @@ export function classifyPackageContracts({ fixture, inspection, fixtureReport })
fixture: fixture.id,
code: "package-min-host-version-drift",
level: "warning",
message: "package openclaw.install.minHostVersion does not match the target OpenClaw build version",
message: "package openclaw.install.minHostVersion is not a semver floor for the target OpenClaw build version",
evidence: [
`minHostVersion:${packageSummary.openclaw.install.minHostVersion}`,
`buildOpenClawVersion:${packageSummary.openclaw.buildOpenClawVersion}`,
@ -300,7 +300,7 @@ export function classifyPackageContracts({ fixture, inspection, fixtureReport })
fixture: fixture.id,
decision: "plugin-upstream-fix",
seam: "package-metadata",
action: "Ask the plugin to keep install.minHostVersion aligned with the OpenClaw package surface it targets.",
action: "Ask the plugin to publish install.minHostVersion as a semver floor for the OpenClaw package surface it targets.",
evidence: packageSummary.path,
});
}
@ -1090,11 +1090,15 @@ function packageNpmPackMissingMetadata(packageSummary, fixtureReport) {
function packageMinHostVersionDrift(packageSummary) {
const openclaw = packageSummary.openclaw;
return (
nonEmptyString(openclaw?.install?.minHostVersion) &&
nonEmptyString(openclaw?.buildOpenClawVersion) &&
openclaw.install.minHostVersion !== openclaw.buildOpenClawVersion
);
if (!nonEmptyString(openclaw?.install?.minHostVersion) || !nonEmptyString(openclaw?.buildOpenClawVersion)) {
return false;
}
return parseMinHostVersionFloor(openclaw.install.minHostVersion) !== openclaw.buildOpenClawVersion;
}
function parseMinHostVersionFloor(value) {
const match = /^>=([0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)$/.exec(value);
return match?.[1] ?? null;
}
function repoPathIncludedInNpmPack(packageSummary, repoPath) {

View File

@ -12,7 +12,7 @@ import { buildCompatibilityReport, buildReport } from "./report.js";
const execFileAsync = promisify(execFile);
const registrationEquivalents = new Map([
["registerChannel", new Set(["createChatChannelPlugin", "defineChannelPluginEntry", "registerChannel"])],
["registerChannel", new Set(["createChatChannelPlugin", "defineBundledChannelEntry", "defineChannelPluginEntry", "registerChannel"])],
]);
export async function inspectFixtureSet(config, options = {}) {
@ -147,6 +147,7 @@ export function inspectSourceText(text, filePath = "source.js") {
const hooks = collectDetailedMatches(searchableText, /\bapi\.on\(\s*["'`]([^"'`]+)["'`]/g, filePath, "name");
const registrations = [
...collectDetailedMatches(searchableText, /\bapi\.(register[A-Za-z0-9]+)\s*\(/g, filePath, "name"),
...collectDetailedMatches(searchableText, /\b(defineBundledChannelEntry)\s*\(/g, filePath, "name"),
...collectDetailedMatches(searchableText, /\b(defineChannelPluginEntry)\s*\(/g, filePath, "name"),
...collectDetailedMatches(searchableText, /\b(createChatChannelPlugin)\s*\(/g, filePath, "name"),
...collectDetailedMatches(searchableText, /\b(definePluginEntry)\s*\(/g, filePath, "name"),

View File

@ -11,6 +11,11 @@ export const syntheticRegistrationExecutionProfiles = {
callableProperties: [],
reason: "entry wrapper metadata is captured before channel runtime execution",
},
defineBundledChannelEntry: {
mode: "metadata-only",
callableProperties: [],
reason: "bundled channel entry metadata is captured before channel runtime execution",
},
definePluginEntry: {
mode: "metadata-only",
callableProperties: [],

View File

@ -63,6 +63,41 @@ test("ci policy allows known blocked probes but fails unknown blockers", () => {
assert.match(renderCiPolicyMarkdown(report), /Plugin Inspector CI Policy/);
});
test("ci policy supports wildcard seam rules for generated surface blockers", () => {
const report = buildCiPolicyReport({
policy: {
...policy,
allowedBlocked: [
...policy.allowedBlocked,
{
id: "generated-surface-runtime-gap",
seam: "*",
reasonIncludes: "generated surface has no callable runtime",
decision: "allowed-blocked",
until: "generated surface runtime harness lands",
},
],
},
compatibilityReport: compatibilityReport(),
executionResults: executionResults([
{
seam: "before_tool_call",
reason: "generated surface has no callable runtime",
},
{
seam: "registerCommand",
reason: "generated surface has no callable runtime",
},
]),
});
assert.equal(report.status, "pass");
assert.deepEqual(
report.checks.filter((check) => check.id.startsWith("execution-results.blocked.")).map((check) => check.action),
["warn", "warn"],
);
});
test("ci policy fails ref diff hard regressions", () => {
const report = buildCiPolicyReport({
policy,

View File

@ -84,8 +84,10 @@ test("fixture set inspection treats channel factories as channel registration co
path.join(dir, "fixture", "index.js"),
[
'import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";',
'import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";',
"",
"export const channel = createChatChannelPlugin({ id: 'fixture-channel' });",
"export default defineBundledChannelEntry({ id: 'bundled-channel' });",
].join("\n"),
"utf8",
);
@ -110,7 +112,7 @@ test("fixture set inspection treats channel factories as channel registration co
assert.equal(report.status, "pass");
assert.deepEqual(report.breakages, []);
assert.deepEqual(report.fixtures[0].registrations, ["createChatChannelPlugin"]);
assert.deepEqual(report.fixtures[0].registrations, ["createChatChannelPlugin", "defineBundledChannelEntry"]);
});
test("capture entrypoint imports a local fixture and records registrations", async () => {

View File

@ -538,7 +538,7 @@ test("compatibility fixture summary reads manifests and OpenClaw package metadat
clawhubSpec: "clawhub:@openclaw/fixture-plugin",
npmSpec: "@openclaw/fixture-plugin",
defaultChoice: "clawhub",
minHostVersion: "2026.5.2",
minHostVersion: ">=2026.5.2",
},
release: {
publishToClawHub: true,
@ -601,7 +601,7 @@ test("compatibility fixture summary reads manifests and OpenClaw package metadat
clawhubSpec: "clawhub:@openclaw/fixture-plugin",
npmSpec: "@openclaw/fixture-plugin",
defaultChoice: "clawhub",
minHostVersion: "2026.5.2",
minHostVersion: ">=2026.5.2",
});
assert.deepEqual(report.package.openclaw.release, {
publishToClawHub: true,
@ -737,7 +737,7 @@ test("package contract classifier reports broken install and release metadata",
clawhubSpec: null,
npmSpec: "fixture-plugin",
defaultChoice: "clawhub",
minHostVersion: "2026.5.1",
minHostVersion: ">=2026.5.1",
},
release: {
publishToClawHub: true,

View File

@ -111,6 +111,7 @@ test("synthetic probe plan classifies generated kitchen-sink registrars", () =>
"registerAgentToolResultMiddleware",
"registerAutoEnableProbe",
"registerChannel",
"defineBundledChannelEntry",
"registerCli",
"registerCliBackend",
"registerCodexAppServerExtensionFactory",