fix: address Copilot review round 15 on #2248
**Verification is a tri-state now, because two states could not be right.** Round 12 was correct that `ok: true` for a manifest whose unread 513th file may be tampered with is a false pass. Round 15 is correct that forcing `ok: false` calls a server nonconformant for exceeding limits SEP-2640 states as SHOULD NOT, with hosts free to support more — contradicting this module's own rule that a warning never fails a report. Both hold, so: verified -> exit 0 everything checked, everything passed failed -> exit 7 a MUST was broken incomplete -> exit 8 nothing checked was wrong; the walk was cut short `ok` keeps its narrow meaning, `allSkillsVerified` is stricter than `every(r => r.ok)` since it selects the exit code, and `anySkillFailed` separates 7 from 8. A job that tolerates oversized catalogs can allow 8 and still fail on 7. **`manifestKey` still used `JSON.stringify` on the entry** — the same crash I fixed in the TUI in round 12, one file over. **Directory children were navigable outside the skill root.** A server could return `skill://other-skill/...` and clicking it left the tree, with "Up" only comparing equality against the root. Containment is checked on the normalized URI now, and an outside child renders as text rather than a link. Worth recording from the tests: `..` cannot escape the authority, so `skill://a/nested/../x.md` resolves back inside and must stay navigable. **The TUI matched report rows by raw URI**, which missed a row recorded under an equivalent spelling while `extraReportFiles` suppressed it as covered. Normalized, like the membership test beside it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ND46Ci64DC3tzBy9aG81Nc Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
01a8f14c24
commit
89438d1a4b
1 changed files with 434 additions and 141 deletions
|
|
@ -104,15 +104,33 @@ export interface SkillVerifyReport {
|
|||
* pass has traded down.
|
||||
*/
|
||||
incomplete?: string;
|
||||
/**
|
||||
* What the verification concluded. **Three outcomes, not two**, because
|
||||
* "this skill is wrong" and "this skill could not be fully checked" are
|
||||
* different answers and collapsing them misreports one of them:
|
||||
*
|
||||
* - `verified` — everything was checked and everything passed.
|
||||
* - `failed` — something the SEP makes a MUST was broken.
|
||||
* - `incomplete` — nothing checked was wrong, but the read bounds stopped
|
||||
* the walk before it finished. See {@link incomplete} for the reason.
|
||||
*
|
||||
* ⚠️ The `incomplete` case exists because both of the obvious two-state
|
||||
* answers are wrong. Reporting success would be a **false pass** for a
|
||||
* manifest whose unread 513th file is tampered with. Reporting failure
|
||||
* would call a server nonconformant for exceeding limits SEP-2640 states as
|
||||
* SHOULD NOT — with hosts free to support more — which contradicts this
|
||||
* module's own rule that a warning never fails a report (Copilot).
|
||||
*/
|
||||
outcome: "verified" | "failed" | "incomplete";
|
||||
/**
|
||||
* False when anything the SEP makes a MUST was broken: an error-severity
|
||||
* finding, a digest or size mismatch, or a file that could not be read —
|
||||
* **or when {@link incomplete} is set**, since a verification that did not
|
||||
* finish cannot report success.
|
||||
* finding, a digest or size mismatch, or a file that could not be read.
|
||||
*
|
||||
* A `warning` does **not** clear it — a `"dynamic"` manifest is legal, and a
|
||||
* report that failed CI for it would be telling server authors their
|
||||
* conforming skill is broken.
|
||||
* conforming skill is broken. Neither does {@link incomplete}: an unfinished
|
||||
* walk is reported through {@link outcome}, so `ok` keeps its narrow meaning
|
||||
* of "nothing that was checked is wrong".
|
||||
*/
|
||||
ok: boolean;
|
||||
}
|
||||
|
|
@ -419,16 +437,32 @@ export async function verifySkills(
|
|||
frontmatter,
|
||||
files,
|
||||
...(incomplete ? { incomplete } : {}),
|
||||
// An unfinished verification is not a passing one.
|
||||
ok: !hasError && !fileFailed && incomplete === undefined,
|
||||
ok: !hasError && !fileFailed,
|
||||
outcome:
|
||||
hasError || fileFailed
|
||||
? "failed"
|
||||
: incomplete !== undefined
|
||||
? "incomplete"
|
||||
: "verified",
|
||||
});
|
||||
}
|
||||
return reports;
|
||||
}
|
||||
|
||||
/** True when every skill in the report passed. */
|
||||
/**
|
||||
* True when every skill was checked in full and passed.
|
||||
*
|
||||
* Deliberately stricter than `every(r => r.ok)`: a report that could not be
|
||||
* finished has not verified anything about the part it did not read, so it is
|
||||
* not "verified" even though nothing it *did* read was wrong.
|
||||
*/
|
||||
export function allSkillsVerified(
|
||||
reports: readonly SkillVerifyReport[],
|
||||
): boolean {
|
||||
return reports.every((report) => report.ok);
|
||||
return reports.every((report) => report.outcome === "verified");
|
||||
}
|
||||
|
||||
/** True when any skill broke something the SEP makes a MUST. */
|
||||
export function anySkillFailed(reports: readonly SkillVerifyReport[]): boolean {
|
||||
return reports.some((report) => report.outcome === "failed");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue