Skip to content

orb(content-lane): a multi-item source-URL sequence is joined into one unparseable URL and #9668

Description

@JSONbored

⚠️ 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

  • A scalar source field carrying a two-item block sequence produces two SubmittedSourceUrl
    entries with the two distinct URLs, asserted by a new named case in
    test/unit/content-lane-source-evidence.test.ts.
  • A named regression test asserts checkSubmittedSourceEvidence over that two-item field with a
    stub fetchImpl returning 200 for both URLs reports status: "passed" (today: "failed" with
    outcome: "invalid_url").
  • A test asserts the single-item sequence case is byte-identical to today's behaviour.
  • A test asserts a genuinely malformed value (e.g. notaurl) still yields
    outcome: "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
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

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions