From 435fc13b9b9fea18288e7011b2d886ce1141a6cb Mon Sep 17 00:00:00 2001 From: Kiril Kirov Date: Tue, 6 Oct 2026 07:18:18 +0000 Subject: [PATCH 1/2] fix(core): stop scoring generic XML feed media types A feed served as application/xml or text/xml now passes feed-entry-identity-and-canonical-integrity instead of returning warn. Every feed reader parses a generic XML type, no source names a consumer that treats it worse, and application/rss+xml was never registered with IANA. The type is still recorded under details.warnings; a non-XML type such as text/html still fails. Also redefine the changeset bump rule in AGENTS.md: major means the output contract changes, minor means it grows, and patch means a verdict is corrected to match its dossier with id, tier, grade, weight and schema unchanged. --- .changeset/feed-generic-xml-type.md | 5 ++++ AGENTS.md | 21 ++++++++++++--- ...-entry-identity-and-canonical-integrity.md | 13 +++++++--- ...y-identity-and-canonical-integrity.test.ts | 26 +++++++++++-------- ...-entry-identity-and-canonical-integrity.ts | 23 +++++----------- 5 files changed, 54 insertions(+), 34 deletions(-) create mode 100644 .changeset/feed-generic-xml-type.md diff --git a/.changeset/feed-generic-xml-type.md b/.changeset/feed-generic-xml-type.md new file mode 100644 index 00000000..8037068c --- /dev/null +++ b/.changeset/feed-generic-xml-type.md @@ -0,0 +1,5 @@ +--- +"@forkpoint/agent-lighthouse-core": patch +--- + +`machine-discovery/feed-entry-identity-and-canonical-integrity` no longer warns when a feed is served as `application/xml` or `text/xml`. Every feed reader parses a generic XML type, and no source names a consumer that treats it worse; `application/rss+xml` was never registered with IANA. The type is still recorded under `details.warnings`, and a non-XML type such as `text/html` still fails. diff --git a/AGENTS.md b/AGENTS.md index 1d67f321..8ad8dc8d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -183,6 +183,21 @@ pnpm build && pnpm test && pnpm typecheck && pnpm lint && pnpm check:dossiers && `pnpm build` has to run first. Watch for stale build artifacts: an untracked `.js` or `.d.ts` under `packages/*/src/` shadows its source in vitest and turns a red suite green. -Add a changeset for anything a user would notice. Removing an audit, changing -what one reports, or moving a tier is a `major` — a scan's output changes even -when the audit carried weight 0. +Add a changeset for anything a user would notice. The bump follows what a +consumer of the output can rely on, not whether a verdict moved: + +- **`major`** — the contract changes. An audit id is removed or renamed, a + tier or grade moves (and with it the weight), the result or report schema + changes, or a CLI flag, MCP tool or public export is removed or changes + meaning. Scripts, baselines and dashboards that read the output can break. + This holds even when the audit carried weight 0. +- **`minor`** — the contract grows. A new audit, a new informative or + experimental check, a new `details` key, a new flag or output format. +- **`patch`** — the contract holds and the code is corrected. A verdict that + disagreed with its own dossier is brought into line, a false finding is + removed, a message is reworded. The id, tier, grade, weight and schema stay + the same; some sites' scores move because they were wrong before. + +A verdict correction is a `patch` only when the dossier already supports the +new behaviour. If the fix needs a new source or a changed grade, the dossier +changes first and the bump follows the rule above. diff --git a/docs/evidence/audits/machine-discovery/feed-entry-identity-and-canonical-integrity.md b/docs/evidence/audits/machine-discovery/feed-entry-identity-and-canonical-integrity.md index b113ba9d..ff6fe1c4 100644 --- a/docs/evidence/audits/machine-discovery/feed-entry-identity-and-canonical-integrity.md +++ b/docs/evidence/audits/machine-discovery/feed-entry-identity-and-canonical-integrity.md @@ -51,6 +51,7 @@ Tier per evidence policy: **scored** — grade B meets the A/B bar required for ## Review history - 2026-08-20 — proposed by the novel-checks research pass (10-agent evidence workflow); sources URL-verified at research time. +- 2026-10-06 — a generic XML media type stopped returning `warn`; no source documents a consumer penalty, and `application/rss+xml` is unregistered. ## Implementation deviations @@ -71,10 +72,14 @@ link already explains the tracking parameters, so the finding names both and the tracking arm fires on its own only when the page declares no canonical at all to disagree with. -**A generic `application/xml` or `text/xml` warns rather than fails.** The -registered type is `application/atom+xml` or `application/rss+xml`, but a -generic XML type still parses everywhere; a type that is not XML at all — most -often `text/html` from a misconfigured catch-all — fails. +**A generic `application/xml` or `text/xml` is noted, not scored.** Atom's +`application/atom+xml` is registered by RFC 4287. `application/rss+xml` was +never registered with IANA; it is a convention. A generic XML type parses in +every feed reader, and no source names a consumer that treats it worse. The +audit records it under `details.warnings` and still passes. A type that is not +XML at all — most often `text/html` from a misconfigured catch-all — fails. +Until 2026-10-06 the generic type returned `warn`, which cost half the audit's +weight with no consumer source behind it. **The newest-five selection uses only timezone-carrying dates.** `parseFeedDate` returns `undefined` for a timestamp with no offset rather than diff --git a/packages/core/src/audits/machine-discovery/feed-entry-identity-and-canonical-integrity.test.ts b/packages/core/src/audits/machine-discovery/feed-entry-identity-and-canonical-integrity.test.ts index 54bfcf19..b313119d 100644 --- a/packages/core/src/audits/machine-discovery/feed-entry-identity-and-canonical-integrity.test.ts +++ b/packages/core/src/audits/machine-discovery/feed-entry-identity-and-canonical-integrity.test.ts @@ -261,17 +261,21 @@ describe("FeedEntryIdentityAndCanonicalIntegrityAudit", () => { expect(strings(r, "failures").join(" ")).toContain("not absolute HTTPS"); }); - // A generic XML type parses, so it is a delivery defect rather than a broken feed. - it("warns when the feed is served as text/xml but passes its identity checks", async () => { - const { result } = run({ - feed: CLEAN_ATOM, - feedType: "text/xml", - items: CLEAN_ITEMS, - }); - const r = await result; - expect(r.status).toBe("warn"); - expect(strings(r, "warnings").join(" ")).toContain("text/xml"); - }); + // A generic XML type parses everywhere and no source names a consumer that + // treats it worse, so it is noted in details but never costs score. + it.each(["text/xml", "application/xml"])( + "passes a feed served as %s and notes the generic type", + async (feedType) => { + const { result } = run({ + feed: CLEAN_ATOM, + feedType, + items: CLEAN_ITEMS, + }); + const r = await result; + expect(r.status).toBe("pass"); + expect(strings(r, "warnings").join(" ")).toContain(feedType); + }, + ); it("fails a feed served as text/html", async () => { const { result } = run({ diff --git a/packages/core/src/audits/machine-discovery/feed-entry-identity-and-canonical-integrity.ts b/packages/core/src/audits/machine-discovery/feed-entry-identity-and-canonical-integrity.ts index fc9e9cf7..792a1f09 100644 --- a/packages/core/src/audits/machine-discovery/feed-entry-identity-and-canonical-integrity.ts +++ b/packages/core/src/audits/machine-discovery/feed-entry-identity-and-canonical-integrity.ts @@ -30,7 +30,11 @@ const EXPECTED_TYPE: Record = { json: /^application\/(feed\+json|json)/i, }; -/** A generic XML type: not wrong enough to fail, not the registered type either. */ +/** + * A generic XML type. Every feed reader parses it, and no source names a + * consumer that treats it worse, so it is recorded but never costs score. + * `application/rss+xml` was never registered with IANA; only Atom's type was. + */ const GENERIC_XML = /^(application|text)\/xml/i; /** Does `text` carry a `` that points elsewhere or holds non-text data? */ @@ -95,7 +99,7 @@ export class FeedEntryIdentityAndCanonicalIntegrityAudit extends Audit { if (expected && !expected.test(type)) { if (GENERIC_XML.test(type)) { warnings.push( - `${where}: served as "${type}" rather than the registered ${feed.declaredType} media type`, + `${where}: served as "${type}", a generic XML type; the ${feed.declaredType}-specific type is optional`, ); } else { failures.push( @@ -234,7 +238,7 @@ export class FeedEntryIdentityAndCanonicalIntegrityAudit extends Audit { const displayValue = `${entriesChecked} entries, ${failures.length} problem(s)`; const expected = "Every entry carries the id and timestamp its format requires, no id repeats, and item links are absolute HTTPS URLs equal to the page’s canonical"; - const found = `${feeds.length} feed(s), ${entriesChecked} entries checked, ${canonicalChecks.length} item URL(s) compared against their canonical; ${failures.length} failure(s), ${warnings.length} warning(s).`; + const found = `${feeds.length} feed(s), ${entriesChecked} entries checked, ${canonicalChecks.length} item URL(s) compared against their canonical; ${failures.length} failure(s), ${warnings.length} media-type note(s).`; const details = { feeds: feeds.map((feed) => feed.url), entriesChecked, @@ -256,19 +260,6 @@ export class FeedEntryIdentityAndCanonicalIntegrityAudit extends Audit { }; } - if (warnings.length > 0) { - return { - ...this.warn( - `Entry identity is sound, but ${warnings.length} delivery problem(s) remain.`, - expected, - found, - "Serve the feed as its registered media type.", - ), - displayValue, - details, - }; - } - return { ...this.pass( `${entriesChecked} entries across ${feeds.length} feed(s) carry stable identities and canonical links.`, From 10eb2c2559eab3c3f10f68cd97d49a4f92c209c4 Mon Sep 17 00:00:00 2001 From: Kiril Kirov Date: Tue, 6 Oct 2026 07:49:42 +0000 Subject: [PATCH 2/2] chore(changeset): mark feed media type fix as minor for 5.2.0 --- .changeset/feed-generic-xml-type.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/feed-generic-xml-type.md b/.changeset/feed-generic-xml-type.md index 8037068c..d42c40f0 100644 --- a/.changeset/feed-generic-xml-type.md +++ b/.changeset/feed-generic-xml-type.md @@ -1,5 +1,5 @@ --- -"@forkpoint/agent-lighthouse-core": patch +"@forkpoint/agent-lighthouse-core": minor --- `machine-discovery/feed-entry-identity-and-canonical-integrity` no longer warns when a feed is served as `application/xml` or `text/xml`. Every feed reader parses a generic XML type, and no source names a consumer that treats it worse; `application/rss+xml` was never registered with IANA. The type is still recorded under `details.warnings`, and a non-XML type such as `text/html` still fails.