⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.
Context
src/review/content-lane/source-evidence.ts's parseSimpleFrontmatter collapses a block sequence to
a comma-joined string (:180):
fields[key] = items.join(", ");
For a scalar source field, that value then goes to scalarSourceUrlValues
(src/review/content-lane/source-evidence.ts:194-200), which only knows how to split the flow
form:
function scalarSourceUrlValues(value: string): string[] {
const trimmed = value.trim();
if (!trimmed) return [];
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
return trimmed.slice(1, -1).split(",").map(unquoteYamlValue).filter(Boolean);
}
return [unquoteYamlValue(trimmed)].filter(Boolean);
}
So a documentationUrl field authored as a two-item block sequence yields the single string
"https://a.example/1, https://b.example/2". new URL() on that throws, so
validateFetchableSourceUrl returns outcome: "invalid_url" which becomes status: "hard_failure",
blocking: true, the whole source-evidence report is "failed", and sourceEvidenceCloseDecision
emits a "dead or invalid source URL" close/manual on a submission whose sources are both live.
The sibling path already does this correctly: listSourceUrlValues
(src/review/content-lane/source-evidence.ts:218-228) splits every - item into its own
SubmittedSourceUrl for list-typed fields. Only the scalar path collapses. #8016's tests
(test/unit/content-lane-source-evidence.test.ts:92, :326) cover only the single-item sequence,
which is why this is undetected: a one-item join produces a valid URL.
Requirements
- A scalar source field authored as a multi-item YAML block sequence must yield one
SubmittedSourceUrl per item, not one joined string — matching how listSourceUrlValues already
treats list fields.
- The single-item sequence case must keep producing exactly one
SubmittedSourceUrl with the same
value it produces today (no comma-space suffix, no behaviour change).
- The inline-scalar and flow-sequence (
[a, b]) forms must be unchanged.
- A genuinely invalid URL must still produce
outcome: "invalid_url" / status: "hard_failure" — this
issue removes a false positive, it must not weaken the real check.
⚠️ Required pattern: mirror listSourceUrlValues in the same file, which already splits a sequence
into one value per item. What does NOT satisfy this issue: changing the joiner character (any joiner
still produces one string); special-casing the comma-space separator inside scalarSourceUrlValues
by splitting on it (a comma is legal inside a URL query string, so that reintroduces a different
corruption); or making the multi-item scalar case route to manual instead of parsing it.
Deliverables
All Deliverables above are required in a single PR. A PR that satisfies only some of them — for
example splitting the values (Deliverable 1) without the end-to-end
checkSubmittedSourceEvidence regression test (Deliverable 2) — does not resolve this issue.
Test Coverage Requirements
This repo enforces 99%+ Codecov patch coverage, branch-counted, and vitest.config.ts's
coverage.include covers src/**/*.ts — this file is measured. Both arms of every changed conditional
need coverage: single-item vs multi-item sequence, flow form vs block form, empty vs non-empty. The
regression test in Deliverable 2 is required by name.
Expected Outcome
A content submission whose scalar source field lists two live URLs as a YAML sequence is verified
against both of them and passes, instead of being closed for a "dead or invalid source URL" that only
existed because the parser joined them.
Links & Resources
Context
src/review/content-lane/source-evidence.ts'sparseSimpleFrontmattercollapses a block sequence toa comma-joined string (
:180):For a scalar source field, that value then goes to
scalarSourceUrlValues(
src/review/content-lane/source-evidence.ts:194-200), which only knows how to split the flowform:
So a
documentationUrlfield authored as a two-item block sequence yields the single string"https://a.example/1, https://b.example/2".new URL()on that throws, sovalidateFetchableSourceUrlreturnsoutcome: "invalid_url"which becomesstatus: "hard_failure",blocking: true, the whole source-evidence report is"failed", andsourceEvidenceCloseDecisionemits a "dead or invalid source URL" close/manual on a submission whose sources are both live.
The sibling path already does this correctly:
listSourceUrlValues(
src/review/content-lane/source-evidence.ts:218-228) splits every- iteminto its ownSubmittedSourceUrlfor list-typed fields. Only the scalar path collapses. #8016's tests(
test/unit/content-lane-source-evidence.test.ts:92,:326) cover only the single-item sequence,which is why this is undetected: a one-item join produces a valid URL.
Requirements
SubmittedSourceUrlper item, not one joined string — matching howlistSourceUrlValuesalreadytreats list fields.
SubmittedSourceUrlwith the samevalue it produces today (no comma-space suffix, no behaviour change).
[a, b]) forms must be unchanged.outcome: "invalid_url"/status: "hard_failure"— thisissue removes a false positive, it must not weaken the real check.
Deliverables
SubmittedSourceUrlentries with the two distinct URLs, asserted by a new named case in
test/unit/content-lane-source-evidence.test.ts.checkSubmittedSourceEvidenceover that two-item field with astub
fetchImplreturning 200 for both URLs reportsstatus: "passed"(today:"failed"withoutcome: "invalid_url").notaurl) still yieldsoutcome: "invalid_url"/status: "hard_failure".All Deliverables above are required in a single PR. A PR that satisfies only some of them — for
example splitting the values (Deliverable 1) without the end-to-end
checkSubmittedSourceEvidenceregression test (Deliverable 2) — does not resolve this issue.Test Coverage Requirements
This repo enforces 99%+ Codecov patch coverage, branch-counted, and
vitest.config.ts'scoverage.includecoverssrc/**/*.ts— this file is measured. Both arms of every changed conditionalneed coverage: single-item vs multi-item sequence, flow form vs block form, empty vs non-empty. The
regression test in Deliverable 2 is required by name.
Expected Outcome
A content submission whose scalar source field lists two live URLs as a YAML sequence is verified
against both of them and passes, instead of being closed for a "dead or invalid source URL" that only
existed because the parser joined them.
Links & Resources
src/review/content-lane/source-evidence.ts:173-200(the defect),:218-228(listSourceUrlValues, the pattern to mirror),:340-360(fetchSourceUrl)test/unit/content-lane-source-evidence.test.ts:92,:326(the single-item fixtures)