Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/review/content-lane/source-evidence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,23 @@ function parseSimpleFrontmatter(source: string): Record<string, string> {
i += 1;
}
fields[key] = block.join(inline.startsWith(">") ? " " : "\n").trim();
} else if (inline !== "") {
} else if (inline === "") {
// Block/flow sequence (or nested map) on the following indented lines: gather each `- item` so a
// scalar-only field authored as a YAML sequence is still captured, matching duplicates.ts's parser
// (#8016; this branch existed only there, so such a field was invisible to the source-evidence gate).
const items: string[] = [];
// `lines[i]` is bounded by the `i < lines.length` loop guard; the `?? ""` is an unreachable
// noUncheckedIndexedAccess fallback (same guard as the block-scalar loop above).
/* v8 ignore next */
while (i < lines.length && /^\s/.test(lines[i] ?? "") && (lines[i] ?? "").trim() !== "") {
// `lines[i]` reuses the same in-bounds index already validated by `/^\s/.test` above; `?? ""`
// cannot fire (unreachable noUncheckedIndexedAccess fallback).
/* v8 ignore next */
items.push((lines[i] ?? "").replace(/^\s*-\s*/, "").trim());
i += 1;
}
fields[key] = items.join(", ");
} else {
fields[key] = unquoteYamlScalar(inline);
}
}
Expand Down
23 changes: 23 additions & 0 deletions test/unit/content-lane-source-evidence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ describe("checkSubmittedSourceEvidence", () => {
expect(report.status).toBe("failed");
});

it("fetch-verifies a scalar-only source authored as a YAML sequence, not just a scalar (#8016)", async () => {
// documentationUrl is a scalar-only field (NOT in the list-field set that listSourceUrlValues rescues),
// so before the ported sequence branch this URL was dropped by the parser and the gate could never
// verify it. It must now be captured and fetch-checked exactly like a scalar value.
const url = "https://docs.acme.example/seq-guide";
const src = ["---", "documentationUrl:", ` - ${url}`, "---", "", "body"].join("\n");
const report = await checkSubmittedSourceEvidence(src, fakeFetch({ [url]: 200 }));
expect(report.urls.map((u) => u.url)).toContain(url);
expect(report.status).toBe("passed");
});

it("is retryable (not hard) on a 403/429/5xx canonical source", async () => {
const src = mdx({ githubUrl: "https://github.com/acme/x" });
const report = await checkSubmittedSourceEvidence(src, fakeFetch({ "https://github.com/acme/x": 403 }));
Expand Down Expand Up @@ -312,6 +323,18 @@ describe("extractSubmittedSourceUrls — frontmatter parsing edge cases", () =>
);
});

it("captures a scalar-only source field authored as a YAML block sequence (#8016)", () => {
// A scalar field (documentationUrl) written as a sequence was silently dropped here — the sequence
// branch existed only in duplicates.ts — so the URL never reached the fetch-reachability gate.
// documentationUrl is NOT a list field, so listSourceUrlValues does not rescue it; only the ported
// parser branch captures it.
const src = ["---", "documentationUrl:", " - https://docs.acme.example/guide", "---", "", "body"].join("\n");
const urls = extractSubmittedSourceUrls(src);
expect(urls.map((u) => `${u.field}:${u.url}`)).toContain(
"documentationUrl:https://docs.acme.example/guide",
);
});

it("does not surface any block-scalar header on a list field as a bogus URL (both indicator orders)", () => {
// `retrievalSources` is a list field; a block-scalar header is not a URL. The old guard only skipped the bare
// `|`/`>`, so `|-` leaked as the literal url "|-". YAML allows chomping and the indentation digit in EITHER
Expand Down