Skip to content

The reader only accepts specVersion 0.3.0, so it cannot open half the registered feeds #11

Closed

Description

@hhkaos

The bug

app.js decides what an OTE document is with an exact string comparison:

function isOteFeed(doc) {
  return doc && doc.specVersion === '0.3.0' && Array.isArray(doc.events);
}

function isOteEvent(doc) {
  return doc && doc.specVersion === '0.3.0' && doc.id && doc.name && doc.startDate;
}

So the reader accepts only 0.3.0. The spec is at 0.6.0, and of the four registered adopter feeds, two already declare 0.4.0 — eventos.wiki and saraoia.com. The reader cannot open them today, and it will fail to open every feed published from now on.

This is not a 0.6 chore; it is a live failure against the real registry, and it is silent: the document loads fine, it is simply not recognised as an OTE document.

The fix

The version a document declares should decide which rules apply, never whether it is OTE. A feed is an OTE feed because it declares a specVersion and carries events[].

const SUPPORTED = /^0\.[1-6]\.\d+$/;  // or: any 0.x, and say so when it is newer than known

Being a reader rather than a validator, the tolerant reading is the right one: show the events, and if the version is newer than anything known, say so in the UI rather than refusing. A consumer that ignores fields it does not understand is explicitly correct in OTE — unknown fields have always been legal.

The inheritance list at line 352 is also short by two: contributing (new in 0.5) is inherited feed→event exactly like license, and the list should not need updating by hand each time — the schemas carry x-inheritsFrom on precisely the fields that inherit.

Worth having once it opens them

Nothing below changes whether a feed loads; they are what a reader can now show.

  • speakers with affiliation — "Ada Lovelace, Analytical Engines" is two fields, never one string.
  • recordings — a past event with a video is the case a reader is most useful for.
  • feed.covers — the window a feed claims completeness for. Directly relevant here: it tells the reader whether an event disappearing means "cancelled" or "aged out of the published window", which is the difference between dropping a saved event and keeping it.
  • partOf.offers + offers[].scope — for a multi-part conference the admission lives on the parent, so a talk showing "free" because it has no offers of its own would be wrong.

@opentechevents/schema@0.6.0 ships every version's schemas, so the reader can validate against whatever a document declares rather than hardcoding anything. ajvOptions is exported too — pass it whole; the bare strict: true the package used to document never compiled any OTE schema.

Activity

  1. hhkaos commented on Nov 4, 2026

    @hhkaos
    ContributorAuthor

    Fixed in the working tree (0.10.0). Notes on what was found while doing it.

    Confirmed against the live registry, 2026-11-04

    Feed specVersion Before After
    Community Builders 0.3.0 opens opens, flagged out-of-window
    eventos.wiki 0.4.0 silently not OTE opens (485 events)
    Sarao 0.4.0 silently not OTE opens (180 events)
    CoruñaJUG 0.5.0 silently not OTE opens (11 events)

    Driven in a browser against the four real feeds: 25 events before, 701 after.
    The one feed that did open was the one outside the support window, which is a
    fair summary of the bug.

    hydrateFeedCache() ran the same check on the IndexedDB cache, so a 0.4 feed
    was also evicted from the offline cache on every start.

    Decision: no dependency on @opentechevents/validate

    Reasoned before writing anything, since it conditions the rest.

    The decisive argument is not size, it is semantics. The reader must be more
    tolerant than a validator.
    validateDocument reports an out-of-window or
    unpublished version as an error; gating the reader on report.valid would
    make Community Builders' 0.3.0 feed unopenable — it works today — and would
    break every feed again at 0.7.0. That is this issue's bug, reintroduced with a
    better pedigree. A schema failure in one event would also invalidate a whole
    feed, when the right behaviour is to show the other 484.

    Size confirms it rather than decides it: ESM-only with per-version dynamic
    import() aimed at a bundler, ~3.7 MB of compiled validators, against a
    no-build PWA whose entire app.js is 86 kB. Vendoring means megabytes in the
    repo and the service-worker precache; a CDN means an offline-first reader
    depending on a third party to decide whether it can open a file.

    So what is mirrored is the policy, not the schemas: window computed as the
    last three minors, same vocabulary (current / outdated / out-of-window),
    being behind is a note and never a defect. The cost of mirroring is drift, so
    npm run check now reads the npm registry and the published event schema and
    fails when the version list or the inheritance list disagrees with them. The
    package stays the right tool where the job is judging a document.

    One addition to its vocabulary: future, for a version newer than this build
    knows. A validator has nothing to check such a document against; a reader can
    show it and say so, which is what this issue asks for.

    Classification is by minor, not by exact string, so 0.4.1 is as supported
    as 0.4.0 before this build has heard of it.

    Two corrections to the issue

    • The inheritance list was short by one, not two. x-inheritsFrom in the
      v0.6 event schema marks exactly five fields: specVersion, organizers,
      textLanguage, license, contributing. Only contributing (new in 0.5)
      was missing. Same five in v0.5; v0.4 has four. The list still can't be read
      at runtime without a build step, so it is checked at npm run check time
      against the published schema instead.
    • covers has nothing to attach to yet. It matters where a reader drops
      an event that disappeared from a feed — this one never drops anything
      (state.savedEvents is vestigial, nothing prunes). So there is no wrong
      inference to correct today. Worth revisiting when pruning exists; building
      it now would be building the consumer of a rule nobody applies.

    Not done here, and why

    speakers/affiliation, recordings and partOf.offers + offers[].scope
    (D032) are all rendering, and this app renders nothing — it sets
    widget.events on <ote-events>, already pinned at v0.6.0. inherit()
    copies whole event objects, so partOf and the rest reach the widget intact;
    fixing the reader's own inheritance list is the only part of D032 that was
    ever on this side. If an add-on price renders wrong on a 0.5 feed, that is an
    embed issue.

    UI

    A feed on an older supported release says so when you open that feed, and
    nothing on the aggregate screens — a legitimate choice shouldn't sit in the
    way of every session. Out-of-window, newer-than-known and unpublished get
    flagged in the sidebar too. Nothing refuses a feed, and no message asks the
    publisher for anything.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions