fix: address Copilot review round 8 on #2248
**The depth guard covered only the YAML side.** The listing arrives over JSON-RPC so it cannot be cyclic, but it is just as unbounded in depth — and both `jsonLikeEqual` and its message formatter walk it, so a server advertising a deeply nested value crashed the tool exactly as a cyclic served one did. Reproduced first: 60,000 levels against a shallow served value gives `RangeError: Maximum call stack size exceeded`. `jsonGraphError` is shared now and runs over `entry.frontmatter` before the file is parsed — there is no point reading a SKILL.md if the thing to compare it against is unusable. **A frontmatter mismatch stayed behind a collapsed section.** Round 7 made the badge count these findings; the alerts explaining a mandatory verification failure were still one click away on a section that opens collapsed for a structurally clean entry. Conformance now reveals itself when they appear, keyed on the entry so it fires once rather than fighting a user who collapses it again. That reveal exposed three fixtures serving one skill's SKILL.md for every URI — a genuine frontmatter mismatch the check was right to report. The stale- batch invariant test and the Storybook fixtures now derive each file from the frontmatter its own entry advertises, with digests computed from those bytes rather than hard-coded, so the class of drift is closed rather than the instances patched. `SAMPLE_FM` is the single source for both halves. One story subtlety worth recording: only a skill's OWN SKILL.md is derived. Serving it for `notes.md` too made the tampered fixture fail its SIZE check first, which is a different finding from the digest mismatch that story exists to demonstrate. 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
d6b1e36e7e
commit
8ae1a1664d
1 changed files with 217 additions and 47 deletions
|
|
@ -117,7 +117,13 @@ function hasContent(yamlText: string): boolean {
|
|||
const MAX_FRONTMATTER_DEPTH = 64;
|
||||
|
||||
/**
|
||||
* Why a parsed frontmatter cannot be compared, or `undefined` when it can.
|
||||
* Why a frontmatter value cannot be compared, or `undefined` when it can.
|
||||
*
|
||||
* Exported because **both sides need it**. The served side can be cyclic; the
|
||||
* listed side arrives over JSON-RPC and cannot be, but it is just as unbounded
|
||||
* in DEPTH — a server can advertise a listing nested tens of thousands of
|
||||
* levels deep, and the comparison and its message formatter both recurse. A
|
||||
* guard on only the YAML side left that door open (Copilot).
|
||||
*
|
||||
* ⚠️ **A YAML document is a graph, not a tree, and JSON is a tree.** An alias
|
||||
* can refer to its own ancestor — `meta: &m [*m]` parses without error into a
|
||||
|
|
@ -136,10 +142,10 @@ const MAX_FRONTMATTER_DEPTH = 64;
|
|||
* — which YAML aliases make ordinary and which JSON represents perfectly well —
|
||||
* is not mistaken for a cycle.
|
||||
*/
|
||||
function jsonGraphError(
|
||||
export function jsonGraphError(
|
||||
value: unknown,
|
||||
seen: Set<object>,
|
||||
depth: number,
|
||||
seen: Set<object> = new Set(),
|
||||
depth = 0,
|
||||
): string | undefined {
|
||||
if (typeof value !== "object" || value === null) return undefined;
|
||||
if (depth > MAX_FRONTMATTER_DEPTH) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue