Skip to content

feat: license facts drive the gate, providers emit / core completes, accuracy defaults - #27

Merged
MyPrototypeWhat merged 24 commits into
mainfrom
claude/architecture-design-review-581001
Sep 17, 2026
Merged

MyPrototypeWhat merged 24 commits into
mainfrom
claude/architecture-design-review-581001

Conversation

@MyPrototypeWhat

Copy link
Copy Markdown
Collaborator

Summary

Redesign of the core contract and the default search path. The library has no external users, so removed surfaces are removed, not aliased.

  • Rights decisions run on license facts, not id strings. LicenseId is an open string; an id without a LICENSE_FACTS row resolves to unknown, which grants nothing. RightsRecord.facts? lets a source declare terms narrower than its label. The gate, merge conflict resolution, attribution and the reranker read factsOf(rights); the id only appears in reason text.
  • Cross-source conflicts are keyed on a five-axis facts fingerprint. CC0-1.0 vs PD no longer reports a phantom conflict; the same id with narrower facts now does. stricterLicense and the hand-written LICENSE_PERMISSIVENESS table are replaced by compareRestrictiveness(factsA, factsB) and a derived permissivenessScore(facts) (obligation credit scaled by the grant fraction, so proprietary/unknown never outrank a real CC grant).
  • Providers emit, core completes. ReferenceProvider.search returns EmittedReference[]; core stamps id, source, canonicalUrl, verifiedAt, relevance and applies the per-provider limit. okJson(res, label) replaces the per-provider status check. A default User-Agent is injected (Art Institute of Chicago rejects Node's default).
  • Accuracy on by default, no model, no network. Source-confidence weighting of each source's RRF contribution (floor 0.1), a default lexicalReranker() over title + description + tags + excerpt (CJK bigrams) with the fused relevance blended back at fusionWeight 0.5, a same-source near-duplicate-title penalty, and an opt-in minRelevance threshold.
  • Richer text where it was missing. Met, Art Institute of Chicago, Wikimedia Commons and Rijksmuseum populate description / tags (Art Institute also sourceScore).
  • Narrow sources can decline. ReferenceProvider.accepts?({ text, modalities }) skips a provider with reason: 'declined'; nailbook declines non-nail queries; an explicit sources list bypasses acceptance; an all-declined search returns empty instead of throwing. polyhaven matches per token.
  • Removed: SearchFilters, SearchInput.filters, NormalizedQuery.filters, SearchMeta.appliedFilters, QueryFeature, ReferenceProvider.queryFeatures, the legacy feature→control routing, the MCP filters parameter, stricterLicense, LICENSE_PERMISSIVENESS.
  • Collapsed mirrors: one CONTROL_PATHS registry from which the key union, accessors and zod schema derive; searchMetaSchema / buildSearchControlsSchema exported from core and consumed by @refkit/mcp.
  • Orchestrator split into stages: selectProviders, runPass with named stages; SearchInput.deadlineMs and SearchInput.gateContext.userJurisdiction; cursor passes accumulate meta.passes, summed latency, per-pass failure warnings and URL-deduped rights conflicts. Cache namespace bumped to refkit:v3.
  • MCP: minRelevance, deadlineMs, gateContext search parameters; rerank defaults to true.

Design spec: docs/superpowers/specs/2026-09-14-accuracy-and-contract-redesign-design.md.

Migration notes for provider authors

Return EmittedReference[]: drop id, source, verifiedAt, relevance and any .slice(0, limit); canonicalUrl defaults to sourceUrl. Use okJson. Replace queryFeatures with capabilities.controls. Populate description / tags where the source has them. licenseVersion is only valid on the six versioned CC families.

Opting out of the accuracy defaults

createRefkit({ sourceConfidence: false }), rerank: false (client or per call), lexicalReranker({ fusionWeight: 0 }) for pure lexical order. minRelevance is off by default and is calibrated against the blended score; it does not transfer to rerank: false.

Verification

  • pnpm typecheck, pnpm lint, pnpm test:run: 509 passed, 23 skipped (live smokes).
  • Each of the nine tasks was reviewed independently; a whole-branch review confirmed D1–D8 conformance.
  • Not verified offline: the four providers' new field mappings run against fixtures (Rijksmuseum's description shape from one live probe). Worth one pnpm test:live run with keys.

Follow-ups (out of scope)

MCP agentRef still omits description / tags; Wikimedia housekeeping categories add reranker noise; no test combines deadlineMs with a signal-ignoring provider.

…c, Wikimedia, Rijksmuseum

Met emits description from objectName/medium/culture/period and tags from
classification + tags[].term; Artic requests short_description, medium_display
and the three title vocabularies, emitting description, deduped tags and
sourceScore from _score; Wikimedia Commons emits description from
ImageDescription and tags from pipe-separated Categories; Rijksmuseum emits the
EDM record's localized description.

Adds a shared `plainText` helper to core's provider-helpers (strip markup,
collapse whitespace, cap at 500 chars) so the HTML-stripping logic has one
definition — wikimedia-commons' local stripTags is replaced by it.

Also pins the previously unasserted tag mappings (freesound, nailbook, pixabay
image + video, polyhaven) and the second-factory ids (pexels-video,
pixabay-video, openverse-audio) with regression assertions.
…ver richer text, minRelevance

- mergeReferences takes `weights` parallel to `perSource` and multiplies each
  RRF contribution by it (missing/invalid → 1; an all-zero pool scores 0, not NaN)
- new confidence.ts: sourceConfidence = floor + (1 − floor)·hitRate over the
  batch's title/description/tags/excerpt, reported per provider as
  `meta.providers[].confidence`; `RefkitOptions.sourceConfidence` (default on)
- lexicalReranker scores all four text fields, penalises same-source
  near-duplicate titles (sticky flag, so the greedy pass stays O(n²)) and can
  weight the source's own score (per-source min-max, default off)
- `RefkitOptions.rerank` defaults to lexicalReranker(), `SearchInput.rerank`
  overrides per call, `false` on either level restores raw fusion
- `SearchInput.minRelevance` + `meta.threshold` as a pipeline stage between
  rerank and gate; non-finite bars are ignored
- runPass composed of uniform stages (confidence/merge/rerank/threshold);
  rights-conflict warnings share one prefix; the deadline handle is acquired
  inside the try that cancels it
- MCP `rerank` is a boolean defaulting to true (false → raw fusion)
… polyhaven token matching

- `ReferenceProvider.accepts?({ text, modalities })` lets a source decline a query
  it cannot answer; selectProviders consults it last (after modality/sources/kind)
  and only on an unscoped search — naming a source in `sources` overrides the
  source's own judgement
- `PROVIDER_SKIP_REASONS` gains 'declined' (MCP's output schema tracks it via
  core's export); a declined provider reports `status: 'skipped', reason: 'declined'`
  in meta.providers and raises no warning — routing, not failure
- the empty-selection throw now fires only when nothing declined: an all-declined
  search runs a no-provider pass and returns an empty result set, while the
  modality/sources/kind misses still fail loudly
- nailbook accepts only nail-vocabulary queries (/nail|manicure|ネイル|ジェル|美甲|指甲|甲油/i);
  motif-only searches stay reachable via `sources: ['nailbook']`
- polyhaven's client-side filter matches each whitespace token independently over
  id/name/categories/tags and ranks assets by matched-token count (stable sort),
  instead of substring-matching the whole phrase
…eal query

The nearDuplicateThreshold case used titles whose Jaccard was 0.6, so its
expected order was identical at the default 0.7 and at 1 — the assertion proved
nothing about the threshold. Replace the fixture with a 0.8 repeat plus a
weaker-matching sibling and assert both orders: penalised at the default, spared
at 1.

The "lexical-only" case ran with fusionWeight live at 0.5 and passed only
because the fixture's incoming relevance was 0; pass fusionWeight: 0 so the
title is honest.

Add a diversity case with the fusion term live (incoming relevance 1.0 for three
refs of source a, 0.9 for one of source b, all lexical 1): under the shipped
weights the 0.1 source penalty still outweighs the 0.05 fusion edge, so b
surfaces second.

Add a client case pinning that a provider's accepts() receives the caller's
query text verbatim and then runs.
…ds once per asset

The acceptance regex matched `nail` inside `snail`, so "snail shell macro" —
exactly the generic image query nailbook has nothing for — was accepted. Use a
lookbehind, and add the katakana spelling of manicure plus a word-bounded `gel`
to the term list (amending the design spec's D8 list).

polyhaven rebuilt the per-asset match fields once per query token inside the
filter; hoist it into the map callback so it is computed once per asset.
search_references now exposes the three SearchInput surfaces added by the
redesign: minRelevance (post-rerank relevance bar, reported as meta.threshold),
deadlineMs (whole-call deadline, cursor advances included) and
gateContext.userJurisdiction (forwarded to the gateFor gate). The search input
object is typed as core's SearchInput, so every parameter is checked against the
contract it is forwarded into and not just against zod.

Each parameter's description states what the value means in practice: the
threshold is graded against the reranker's blend (a result matching no query
term lands near 0.3) and does not transfer to rerank: false, where
max-normalised fusion puts the top result at exactly 1; the deadline is a hard
bound while per-source resilience is on.
…and the accuracy defaults

Every sample now matches the types on this branch. Root README: reranking and
source-confidence weighting are documented as defaults (with the full
LexicalRerankOptions table), minRelevance gets a calibration note against the
reranker's blend, rights conflicts are described as facts-keyed with the actual
warning wording, deadlineMs is bounded against per-source resilience, and a new
"Source routing & acceptance" section covers accepts / reason: 'declined' /
explicit sources bypassing it. Architecture and the invariants now state that
providers emit EmittedReference while core stamps id, provenance, relevance and
the limit, that facts drive every rights decision, and that rehostPolicy is
host-facing metadata core never acts on.

core README: the same, plus a worked provider-contract sample and a controls
table that matches what the 23 provider ids actually declare. mcp README: the
real zero-config source list and every search_references parameter. The
semantic-rerank cookbook imports refText from core and replaces the default
rather than adding an optional one. provider-roadmap: real package count.
nailbook README: its query-acceptance behaviour.

The changeset lists every published package with its removed and added
surfaces; the breaking changes ship as minor under semver's pre-1.0 rule.
…ports; self-contained README provider sample
@MyPrototypeWhat
MyPrototypeWhat merged commit dadf4eb into main Sep 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant