From a51efcbf4e48da98ae157d2e68e3a9d0b3f60566 Mon Sep 17 00:00:00 2001 From: Raul Jimenez Ortega Date: Wed, 4 Nov 2026 17:14:48 +0100 Subject: [PATCH] feat(validator): say which profile produced the recommendations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D042's second half, and the last app item on #101. A 1.0 will freeze validity — the schemas and the normative prose — and explicitly not the recommended profiles. So the set of warnings is not a stable interface, and the page that hands people that list was presenting it as if it were: Unmet spec recommendations (SHOULD). The document is still valid; these make it easier to find, filter and subscribe to. Nothing there says which rules produced the list, or that a later release can change it. Anyone scoring, badging or tracking this over time had no way to know what to pin. Now: Unmet recommendations from OTE 0.6.0's recommended profile (SHOULD). The document is still valid; these make it easier to find, filter and subscribe to. This list is not frozen the way validity is — a later release can add or drop recommendations — so pin the version if you score or badge on it. It names the version actually *checked*, not the one declared, so the version selector's "see what moving to 0.4 would change" names 0.4's profile. Two findings worth recording, both of which made this smaller than #101 assumed: **No `packages/validate` change was needed.** The guess was that a profile version had to be exposed alongside `recommendedProfileChecked`. There is no such number: each release ships its own profile (`v0.6/feed.recommended.schema.json`), so the version checked *is* the profile version. Inventing a second one would have implied they can drift apart. **The badge does not score on warnings**, so D042 does not reach it — `workers/validator/src/badge.ts` is keyed to validity, discovery and reachability only. Checked rather than assumed, as the rest of #101's app audit was. The sentence lives in `src/lib/report.ts` rather than `main.ts` so it is testable: per CONTRIBUTING.md this app's tests cover `src/lib/` and the UI is checked by hand. Verified in a browser as well as in tests: a valid feed with two unmet recommendations shows the 0.6.0 sentence, and overriding the selector to 0.4.0 reruns it as 0.4.0. 83 validator tests (5 new); lint, typecheck and the full suite green. Co-Authored-By: Claude Opus 5 --- apps/validator/src/lib/report.ts | Bin 9689 -> 10985 bytes apps/validator/src/main.ts | 3 +- apps/validator/test/report.test.ts | 47 ++++++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/apps/validator/src/lib/report.ts b/apps/validator/src/lib/report.ts index 179331e87be40f6fd0844f2579efd330afcc190a..5e18f7bc0806938240fc7a88e8727d5589fea0a0 100644 GIT binary patch delta 1237 zcmZ8h!EO^V5Ea*mD*|yEMMX_Rv`rD}AytGxt%L+p4{hNZ$M$ATckN(%vrVfid_tx0 zA^c9S-1rOLIN26)i@Y;$-n{oF_n-b;{JMX&>g@OX9qQB9LPUy%Ca!?$lnBIwQ@gK=C^ zkk0GD25lVT+(rfIT@2C&ZG3Qsn%Kly`H60=PFCR+(718ufOW7*HV= zAVM>4Y`n9x)}-@-T}?89n{*i1^09R_Wf2^O!ZpN&8J!4OK(P`r(0Dg$}d&(cTCV@+{*`hu}5R|3GYv7osrN^2^rE@pw$OM(W>DcYW~m`TH)7XmWafLOVDA z)ZOle$C222yXoWzfL_PiTk0k3$;JBa$+J~T($3Ac?3Q|GA5SknAMa%ZCrLS3<63DL}YT6pb<$N zv%A(d$=e`_-4?n%Zl=H~Xd~NZ20m>3o!ib|DDkLF delta 7 OcmaDEdeeKuO;rF9v;+|V diff --git a/apps/validator/src/main.ts b/apps/validator/src/main.ts index 6b48202..dcfe4aa 100644 --- a/apps/validator/src/main.ts +++ b/apps/validator/src/main.ts @@ -26,6 +26,7 @@ import { excerptAt, formatPointer } from "./lib/locate.js"; import { buildReport, MAX_SOURCE_BYTES, + recommendationsBlurb, type DocumentKind, type Finding, type Report, @@ -515,7 +516,7 @@ function renderReport(report: Report, label: string): void { renderFindings( recommendationsBox, "Recommendations", - "Unmet spec recommendations (SHOULD). The document is still valid; these make it easier to find, filter and subscribe to.", + recommendationsBlurb(report.specVersion), report.recommendations, "warn", ); diff --git a/apps/validator/test/report.test.ts b/apps/validator/test/report.test.ts index 93be205..7b4b119 100644 --- a/apps/validator/test/report.test.ts +++ b/apps/validator/test/report.test.ts @@ -5,7 +5,13 @@ import { describe, expect, it } from "vitest"; import { LATEST_VERSION } from "@opentechevents/validate"; -import { buildReport, MAX_DEPTH, maxDepth, type Report } from "../src/lib/report.js"; +import { + buildReport, + MAX_DEPTH, + maxDepth, + recommendationsBlurb, + type Report, +} from "../src/lib/report.js"; /** Reuses packages/validate's fixtures: one corpus, one opinion of "valid". */ function fixture(name: string): string { @@ -215,3 +221,42 @@ describe("buildReport — spec versions", () => { expect(report.recommendations).toEqual([]); }); }); + +// D042: a 1.0 freezes validity, not the recommended profiles. Anything that +// scores or badges on this list has to know which version produced it. +describe("recommendationsBlurb", () => { + it("names the version whose profile produced the list", () => { + expect(recommendationsBlurb("0.6.0")).toContain("OTE 0.6.0's recommended profile"); + }); + + it("warns that the list is not frozen the way validity is", () => { + const blurb = recommendationsBlurb("0.6.0"); + expect(blurb).toContain("not frozen"); + expect(blurb).toMatch(/add or drop recommendations/); + }); + + // The recommendations are still SHOULD-level, and saying so is what keeps + // them from reading as errors. + it("keeps the SHOULD framing and the reassurance that the document is valid", () => { + const blurb = recommendationsBlurb("0.6.0"); + expect(blurb).toContain("(SHOULD)"); + expect(blurb).toContain("still valid"); + }); + + // Unreachable through the UI — no version checked means no recommendations, + // and the box stays hidden — but the sentence must not read "OTE null's". + it("does not invent a version when none was applied", () => { + const blurb = recommendationsBlurb(null); + expect(blurb).not.toContain("null"); + expect(blurb).toContain("the recommended profile"); + }); + + // The version shown is the one actually checked, which is not always the + // one the document declares — a hand-picked version has to win here too. + it("reports whichever version was checked", async () => { + const report = validated( + await buildReport(fixture("versioned/event-0.3.0.json"), { version: LATEST_VERSION }), + ); + expect(recommendationsBlurb(report.specVersion)).toContain(`OTE ${LATEST_VERSION}`); + }); +});