diff --git a/docs/research/ecosystem-intelligence-sources-and-signals-2026-08-26.md b/docs/research/ecosystem-intelligence-sources-and-signals-2026-08-26.md new file mode 100644 index 000000000..a56ca9f73 --- /dev/null +++ b/docs/research/ecosystem-intelligence-sources-and-signals-2026-08-26.md @@ -0,0 +1,353 @@ +# Ecosystem intelligence sources and opportunity-ranking signals + +Date: 2026-08-26 + +## Decision + +Build ecosystem intelligence from a small, allowlisted source stack rather than from +general web search or a single third-party risk score: + +1. **Project-owned evidence** is authoritative for identity, supported releases, + configuration, deployment, security policy, advisories, and fixed-version claims. +2. **Ecosystem authorities** supply package identities, release artifacts, dependency + relationships, and the strongest available adoption proxies. +3. **CVE List, OSV/GHSA, CISA KEV, and EPSS** supply disclosed-vulnerability history and + exploitation context. They identify architectural priors and historical controls; + they do not establish that a current deployment is vulnerable. +4. **OpenSSF Scorecard and Criticality Score inputs** may contribute individually + attributable heuristics. Do not ingest their overall scores as verdicts. + +Use the seven dimensions already chosen in the [OSS target portfolio +scorecard](./oss-offensive-research-targets-2026-08-26.md#selection-scorecard): +exposure/adoption, researchable surface, reproducibility, parallel density, disclosure +maturity, portfolio diversity, and operational safety. Preserve the complete signal +vector, source evidence, confidence, freshness, and missingness beside any weighted +queue position. Hard admission gates remain gates; a high score cannot override an +unclear license, absent disclosure path, irreproducible target, unsafe egress, or weak +authorization. + +The resulting object is an **opportunity hypothesis**, not a Finding. Reusable facts +may enter the global Security Knowledge Graph. Only a separately authorized project +campaign may create project-owned Targets, Research Observations, Investigation +Assertions, Research Priorities, Artifacts, and Findings. + +## What the intelligence layer is deciding + +The layer has two related outputs that should not be collapsed: + +- **Ecosystem opportunity** asks whether a project or configuration family combines + meaningful reach, security-relevant boundaries, active change, and useful historical + priors. +- **Campaign readiness** asks whether one exact current or historical revision and + configuration can be licensed, pinned, provisioned, isolated, reset, observed, and + disclosed safely. + +The first output can nominate a seam such as “multi-tenant gateway callback and URL +handling.” It cannot say “release X is vulnerable to SSRF.” The second output selects a +specific Target Recipe only after the [program's hard admission +gates](./open-source-offensive-research-program-plan-2026-08-26.md#hard-admission-gates) +pass. + +## Chosen source stack + +### Tier 1: project-owned sources + +| Source | Use | Update and provenance | Constraints, noise, and gaming | +| --- | --- | --- | --- | +| Upstream repository at an immutable commit | Canonical source identity, license files, dependency manifests, routes, default configuration, release automation, tests, and architecture evidence | Record host, owner/repo, commit SHA, tree or file path, retrieval time, response validator, and content hash. GitHub's repository response exposes fields such as default branch, archive status, timestamps, license metadata, stars, and forks ([repository API](https://docs.github.com/en/rest/repos/repos)). | Metadata can be stale or publisher-supplied; a detected license is not legal advice. Stars, forks, topics, issue counts, and repository size are easy to misread and must be weak, capped signals. | +| Project release artifacts, tags, changelog, and release notes | Supported-version candidates, cadence, security fixes, artifact provenance, and current-versus-replay cutoffs | Save tag and target commit, publication time, asset digest when supplied, asset size, and release body. GitHub's release API exposes tag, draft/prerelease state, timestamps, assets, download counts, and SHA-256 asset digests when available ([release API](https://docs.github.com/en/rest/releases/releases)). | Tags can move; release assets and notes may be replaced or corrected. Pin the resolved commit and downloaded bytes, not the tag name alone. Release download counts exclude other distribution channels and are weak adoption evidence. | +| Project security policy, advisory page/feed, and disclosure contact | Reporting route, scope, acknowledgement/fix expectations, affected/fixed versions, and first-party vulnerability themes | Preserve the exact page/file revision and advisory JSON. Repository advisories expose GHSA/CVE identifiers, state, affected packages, version ranges, CVSS, CWE, and publication/withdrawal timestamps ([repository-advisory API](https://docs.github.com/en/rest/security-advisories/repository-advisories)). | Disclosure practice differs sharply between projects. No advisory feed does not mean no vulnerabilities; high counts may indicate a mature CNA or recent bulk publication rather than poor software. | +| Official install/deployment/configuration documentation | Supported topology, network listeners, roles, plugins, uploads/imports, callbacks, databases, caches, proxies, and third-party dependencies | Prefer versioned documentation or a documentation commit. Store the cited section, product version, URL, retrieval time, and content hash. Confirm important defaults against the pinned source or built target. | Documentation can lag code and often omits negative or edge-case behavior. It supports a surface hypothesis, not reachability or impact. | + +GitHub integrations should use stable, specific requests and save `ETag` or +`Last-Modified`; correctly authorized conditional requests can return `304` without +using the primary rate budget ([GitHub REST best +practices](https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api)). +GitHub documents 60 unauthenticated requests per hour and 5,000 for ordinary +authenticated users, plus separate secondary limits, so polling must be cached, +conditional, and bounded ([rate limits](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api)). + +### Tier 1: ecosystem authorities + +| Source | Use | Update and provenance | Constraints, noise, and gaming | +| --- | --- | --- | --- | +| Package registry metadata and immutable artifacts | Canonical ecosystem/name/version identity, publication time, dist-tags/default version, deprecation, artifact hashes, declared license, and upstream links | Store a Package URL (PURL), registry URL, version, release serial or validator, artifact digest, publisher-provided fields, and retrieval time. PyPI's JSON API, for example, returns project metadata and release-file SHA-256/BLAKE2 digests and warns that uploaded metadata may not match file contents ([PyPI JSON API](https://docs.pypi.org/api/json/)). | Registry metadata and project URLs are publisher assertions. Verify source mapping and license from the artifact/repository. Deletions, yanks, dist-tag movement, mirrors, and name transfers need explicit state. | +| Registry-owned download/install data, when the ecosystem publishes it | Relative reach inside one ecosystem and one fixed time window | Save exact query, UTC window, filters, result, dataset revision/partition, and query hash. PyPI publishes raw download events in BigQuery with package/version/installer fields ([PyPI download dataset guide](https://packaging.python.org/en/latest/guides/analyzing-pypi-package-downloads/)). WordPress.org's plugin directory provides rounded active-installation bands used by the [portfolio snapshot](./oss-offensive-research-targets-2026-08-26.md#wordpress-with-third-party-plugins). | Never compare raw counts across ecosystems. PyPI explicitly documents cache, mirror, hosting, inflation, and historical-quality effects and says downloads do not establish project quality. CI and automated updates can dominate package downloads; active-install bands are rounded. | +| deps.dev v3 | Cross-ecosystem package versions, direct dependency graphs, license expressions, package-to-repository links, attestations, and advisory keys | Use the stable v3 API and persist the full request/response plus the relation provenance. The API covers Cargo, Go, Maven, npm, NuGet, PyPI, and RubyGems; its version response distinguishes verified attestations from unverified metadata links ([deps.dev API](https://docs.deps.dev/api/v3/)). | It is a derived index, not the owner of package or repository facts. Coverage differs by ecosystem; project links may be unverified and advisory keys cover that package version directly, not all dependencies. Confirm important relationships at the registry or repository. | +| GitHub dependency graph/SBOM, when enabled and accessible | Dependency inventory for one repository and commit; useful for candidate reachability and recipe provenance | Capture repository, commit, generated time, tool version, and full SBOM artifact. GitHub exposes separate dependency-graph and SBOM endpoints ([dependency-graph API](https://docs.github.com/en/rest/dependency-graph)). | Availability depends on repository configuration and permissions. This is not a complete, public reverse-dependent census and must not be presented as one. | + +For npm, automated collection should use documented public APIs, not crawl the website. +npm's terms permit public-API use but forbid automated website access and identify five +million requests in a month as unreasonably high; they also restrict redistribution of +npm security data ([npm Open-Source Terms](https://docs.npmjs.com/policies/open-source-terms/)). +Store only the fields needed for internal research ranking and retain the governing +terms/version with the source configuration. + +### Tier 1/2: disclosed-vulnerability and exploitation context + +| Source | Authority and use | Update mechanics | Constraints and interpretation | +| --- | --- | --- | --- | +| CVE List V5 | Official CVE state and CNA-authored containers; canonical CVE identifiers, references, affected claims, and record state | Mirror the official `CVEProject/cvelistV5` repository at a commit or consume release snapshots/delta log. Records can use different schema versions, so select the schema from each record rather than its date ([official CVE List cache](https://github.com/CVEProject/cvelistV5), [CVE Services/resources](https://www.cve.org/ResourcesSupport/AllResources/CveServices)). | CNA completeness and affected-version precision vary. Preserve CNA and program containers separately, including rejected or disputed states; do not overwrite source assertions with later enrichment. | +| Project GHSA feed plus GitHub Advisory Database/OSV | Package-aware affected ranges, aliases, severity/CWE, fixed versions, and ecosystem records | Prefer project-owned GHSA records for publisher claims. For scalable joins, OSV offers package/version and commit queries, batch queries, full/per-ecosystem dumps, and `modified_id.csv` incremental feeds ([OSV API](https://google.github.io/osv.dev/api/), [OSV data sources and dumps](https://google.github.io/osv.dev/data/)). | OSV is an aggregator with source-specific licenses, including CC-BY, CC0, MIT, Apache, BSD, and CC-BY-SA. Retain each record's source and license; do not flatten contradictory records. Absence is not proof of safety. | +| CISA Known Exploited Vulnerabilities | Government-curated evidence that a disclosed CVE has been exploited in the wild; use as a separate historical overlay and a source of architecture themes | Snapshot the JSON/CSV plus catalog version/update time and join by CVE alias. CISA says KEV should be an input to vulnerability prioritization and publishes the catalog and JSON schema ([KEV catalog](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)). | KEV is intentionally selective and CVE-centric. Membership does not prove the selected release/configuration is affected; non-membership does not mean no exploitation. | +| FIRST EPSS | Time-indexed probability and percentile for exploitation of a disclosed CVE; useful for historical-control and vulnerability-theme prioritization | Bulk ingest the daily CSV, record model version and publish date, and retain the dated file hash. FIRST provides free daily current and historical data and warns that model-version boundaries shift scores ([EPSS data](https://www.first.org/epss/data.html)). | EPSS ranks CVEs, not projects, code paths, or undisclosed bugs. Never sum EPSS into a project “vulnerability probability.” Keep probability, percentile, date, and model version together. | +| NVD enrichment | Optional CVSS/CPE enrichment when it adds information absent from the CNA record | Increment by modification window, persist NVD's last-modified timestamp and response, and respect API limits. NVD recommends modified-date synchronization and API keys for higher limits ([NVD API guidance](https://nvd.nist.gov/general/news/API-Key-Announcement)). | NVD is enrichment, not the source of a vendor acknowledgement. CPE matching can be broad or wrong; retain it as a distinct assertion with its own provenance. | + +Aliases must be resolved as a graph, not by discarding identifiers. One underlying +advisory may have CVE, GHSA, OSV, vendor, and distribution IDs. Keep every source record, +then create an explicit dedupe cluster with the matching evidence. The [OSV quality +guide](https://google.github.io/osv.dev/data_quality.html) likewise treats aliases, +related IDs, upstream IDs, canonical package names, and valid ranges as material record +quality. + +### Tier 2: reusable posture and criticality heuristics + +Use these as explainable component observations, never as a one-number answer: + +- **OpenSSF Scorecard:** ingest the pinned tool version, repository commit, individual + check, score, reason/details, and scan time. Useful checks include Maintained, + Security-Policy, Signed-Releases, Packaging, Pinned-Dependencies, Code-Review, + Dangerous-Workflow, and Fuzzing. The project documents that the weekly public API + omits several checks because of API cost and licenses API results under CDLA + Permissive 2.0 ([Scorecard README](https://github.com/ossf/scorecard)). Its own check + documentation says automated detection can have false positives/negatives and allows + maintainer annotations ([checks](https://github.com/ossf/scorecard/blob/main/docs/checks.md), + [annotations](https://github.com/ossf/scorecard/blob/main/config/README.md)). +- **OpenSSF Criticality Score:** borrow raw inputs such as age, recent releases, + contributor breadth, and dependency evidence only when their collection method is + reproducible. Do not ingest `default_score`. The project describes the score as beta, + GitHub-only, and configurable, and its working group notes that activity bias can miss + stable critical projects ([Criticality Score](https://github.com/ossf/criticality_score), + [Securing Critical Projects WG](https://github.com/ossf/wg-securing-critical-projects)). + +An OpenSSF check measures an observable practice, not exploitable impact. A low score can +prioritize a manual question; it cannot create a vulnerability assertion. + +## Sources not admitted to automatic ranking + +The following may lead a human to a primary source, but they do not become scored facts: + +- search-engine result rank, generated summaries, scraped “top project” lists, social + media attention, exploit rumors, and anonymous forum claims; +- unaudited vulnerability aggregators that discard source IDs, affected ranges, record + state, or licensing; +- GitHub issue/PR volume or sentiment as a quality or vulnerability metric; +- search-scraped reverse-dependent counts. OpenSSF's own Criticality Score discussion + documents false matches and missing indirect dependencies in commit-mention-based + dependent counts ([design issue](https://github.com/ossf/criticality_score/issues/102)); +- public scan/search services that would submit target URLs or disclose research + interest; and +- raw stars, forks, pulls, downloads, advisories, CVEs, KEV entries, EPSS values, or + Scorecard totals without window, denominator, source, and caveat. + +These exclusions keep the system local-first and avoid turning public-internet scanning +into “discovery.” + +## Normalized evidence model + +Every ingested value should be backed by one immutable **Source Observation**. This is a +proposed contract for the next lifecycle/graph decision, not an implementation schema: + +```text +SourceObservation + observation_id + subject + canonical repo URL + package URLs and ecosystem coordinates + aliases and mapping provenance + source + source_id, source_tier, publisher, source_record_id + request URL/method/query or repository path + governing terms/license and access class + capture + retrieved_at, effective_at, window_start, window_end + ETag/Last-Modified/source commit/dataset partition/model version + HTTP status, pagination boundary, raw artifact id, sha256 + extraction + extractor name/version, field path, raw value and unit + normalized signal name/value/unit + transformation and cohort + quality + authority, identity confidence, completeness, freshness + missing reason, conflicts, caveats, reviewer state +``` + +The raw response or repository blob enters the ordinary Artifact path after redaction. +The Security Knowledge Graph stores the reusable assertion and its provenance pointer, +not an untraceable copy of the number. A changed source produces a new observation; it +does not mutate the historical observation used by an earlier ranking. + +### Identity rules + +1. Canonicalize a repository to forge/owner/repository, following verified moves while + retaining old names as aliases. +2. Canonicalize packages with PURL and ecosystem-native name/version rules. Do not merge + packages merely because their display names resemble one another. +3. Require provenance for package-to-repository mappings. Prefer signed publish + attestations or registry-owned links; mark publisher metadata as unverified when the + source does. +4. Treat a product family, repository, package, deployable application, plugin, and + configuration profile as different subjects connected by typed relations. +5. Resolve vulnerability aliases into a cluster while retaining every source assertion, + state, range, and timestamp. + +## Signal definitions + +All counts use fixed UTC windows and retain numerator and denominator. Continuous values +are log-transformed when appropriate and converted to empirical percentiles only inside +a comparable cohort (for example, npm application packages or self-hosted Go services), +never across unrelated ecosystems. + +| Dimension | Preferred source-backed signals | Guardrails | +| --- | --- | --- | +| **Exposure/adoption — 20%** | Registry downloads/active installs in 30/90/365-day windows; direct and reverse dependency evidence; official container pulls or release-asset downloads when their scope is documented; stars/forks as weak secondary context | Require at least one ecosystem-owned measure for a high-confidence score. Cap any one count channel, use cohort percentiles, show disagreement, and never call it market share. | +| **Researchable surface — 20%** | Evidence-backed trust-boundary inventory: anonymous and authenticated network routes; role/tenant/account boundaries; file/archive/media parsers; URL fetches/callbacks/webhooks; imports/exports; templates; plugins/extensions; tool/code execution; secret-bearing provider integrations; multiple persistence services | Derive from cited source/configuration and then confirm in the built Target Recipe. Count distinct boundary families, not endpoints or lines of code. Surface is opportunity, not vulnerability. | +| **Reproducibility — 15%** | Immutable source and artifact references; lockfile/SBOM; official local deployment path; deterministic seed/reset/readiness; supported offline or locally faked integrations; affected/fixed historical refs | This becomes a hard gate before campaign admission. Publisher documentation earns a hypothesis; a successful two-instance lifecycle smoke test earns readiness. | +| **Parallel density — 15%** | Measured cold/warm start, reset and teardown time, idle/seeded/active peak RAM and CPU, image and writable-state size, ports, service count | Planning estimates are explicitly lower confidence. Score only measured envelopes for campaign admission and retain the host/runtime profile. | +| **Disclosure maturity — 10%** | Security policy and private contact; supported-version clarity; first-party advisories; median acknowledgement/fix interval when both dates exist; withdrawal/correction handling | Do not reward advisory volume. Separate missing data from slow response, and publisher claims from observed dates. A usable disclosure path is a hard gate. | +| **Portfolio diversity — 10%** | New language/runtime, protocol, parser family, identity model, trust boundary, extension model, or deployment archetype relative to admitted recipes | Compare against the current corpus snapshot. Similarity is not a security weakness; it only reduces marginal portfolio value. | +| **Operational safety — 10%** | Loopback/internal binding, non-root support, no privileged/host-Docker requirements, egress containment, synthetic credentials/data, observable side effects, reliable cleanup, non-destructive validation path | Safety requirements remain hard gates. A lower-risk target may rank ahead on tie-breaks, but no score compensates for unsafe or unauthorized execution. | + +Three unweighted overlays stay visible: + +- **Change pressure:** release cadence, default/config/dependency change, security-sensitive + subsystem churn, and recent ownership/maintainer transitions. This can break ties inside + researchable-surface dimensions, but automated monorepo commits or release trains must + not dominate. +- **Disclosed-history priors:** deduped 12/24/36-month advisory counts, distinct affected + subsystems and CWE families, patch/disclosure cadence, KEV membership, and dated EPSS. + These suggest variant-review themes and historical controls; they are not current + findings and cannot dominate the score. +- **Evidence confidence:** authority, identity mapping, completeness, freshness, agreement, + and reproducibility of each contributing observation. + +This preserves the program's existing scorecard while supplying the missing source and +normalization rules. It also prevents the recent advisory bursts described in the +[portfolio snapshot](./oss-offensive-research-targets-2026-08-26.md#additional-lightweight-targets) +from becoming an automatic “most vulnerable” ranking. + +## Ranking procedure + +### 1. Apply gates before scores + +Reject or hold a candidate when license/redistribution status, exact revision, +reproducible local deployment, disclosure route, isolation, egress, safe reset/teardown, +or authorization cannot be established. Record the rejected gate and evidence. Do not +encode a failed gate as a low numeric score. + +### 2. Build comparable cohorts + +Compare like with like first: package libraries, self-hosted applications, CMS plugins, +identity providers, AI gateways, and device firmware are materially different +populations. A portfolio selection can then take the strongest evidence-backed candidate +from multiple cohorts rather than allowing the largest ecosystem to fill the queue. + +### 3. Normalize transparently + +- For heavy-tailed counts, retain the raw value, compute `log1p(value)`, and convert it + to a 0–1 empirical percentile within the dated cohort. +- For bounded observations, use an evidence rubric with named anchors (`absent`, + `partial`, `documented`, `verified`) mapped to 0, 0.33, 0.67, and 1 only for arithmetic. +- For recency, store the actual age and documented support state. Do not silently turn + old-but-stable into abandoned. +- Do not impute missing as zero or as the cohort median. Mark it missing, reduce the + dimension's evidence confidence, and require manual review when a required source is + absent. +- Cap correlated proxies inside their dimension. Stars, forks, downloads, dependents, + and pulls are not five independent votes for reach. + +### 4. Compute a queue position, not a verdict + +For candidates that pass gates, multiply each dimension's normalized value by the +existing 20/20/15/15/10/10/10 weights. Publish: + +```text +candidate + gate results + seven dimension values and weighted contributions + raw source observations behind every component + unweighted change-pressure and disclosed-history overlays + confidence and missingness by dimension + total used only for ordering this cohort/snapshot + portfolio novelty and tie-break reason +``` + +Never display the total without the vector. Do not assign universal “safe,” “vulnerable,” +or “high risk” bands. For close totals, prefer higher evidence confidence, then missing +portfolio coverage, then verified reproducibility, then lower measured operational cost. +Require a human decision when those still tie. + +### 5. Separate discovery from replay + +Freeze the source snapshot and advisory cutoff before a current-head campaign. Known +answers, vulnerable routes, and post-cutoff findings remain outside the agent-visible +project context. Historical affected/fixed pairs belong to the labeled replay lane. If a +current investigation collides with a known issue, record the collision and move that +branch to replay as required by the [program plan](./open-source-offensive-research-program-plan-2026-08-26.md#keep-three-activities-separate). + +## Refresh and reproducibility contract + +Recommended initial cadence: + +| Frequency | Sources | Behavior | +| --- | --- | --- | +| Daily | CVE deltas, OSV `modified_id.csv`, KEV catalog, EPSS daily file, project advisories for active candidates | Increment by source cursor/validator; store changed raw records and tombstone/withdrawal state. Re-rank only affected candidates. | +| Weekly | Project releases/tags, registry metadata, deps.dev relations, Scorecard component results, active-candidate deployment docs | Use conditional requests and stable pagination. Flag identity, supported-version, source-map, or license drift for review. | +| Monthly | Adoption windows, full candidate cohorts, portfolio diversity, disclosure response measures | Freeze a dated cohort and normalization parameters; produce a new immutable ranking snapshot rather than rewriting the old one. | +| Per Target Recipe | Exact source/artifact hashes, docs version, SBOM/lockfile, lifecycle and resource measurements | Campaign admission consumes the pinned recipe snapshot, not live mutable intelligence. | + +Every ranking snapshot must preserve: + +- candidate inclusion query and exclusion reasons; +- source configuration and terms/license versions; +- retrieval timestamps, cursors, validators, commits, model versions, raw artifact hashes, + pagination, and failures; +- extractor and normalization versions, cohort membership, raw values, transformations, + weights, missing-data decisions, and tie-breaks; +- alias clusters and package-to-repository mapping evidence; and +- the final component vector, confidence vector, queue order, and human override with + rationale. + +If a source is unavailable, rate-limited, malformed, or materially stale, preserve the +last good observation with its age and lower confidence. Do not silently substitute a +different provider or turn missing into a favorable value. + +## Initial application to the approved OSS portfolio + +The source stack supports the existing six-family plan without reopening its candidate +decision: + +- **LiteLLM, Mastra, Langflow, Keycloak, and Grafana:** repository/release/advisory APIs, + project security pages, official deployment docs, package/attestation relationships, + and pinned container/source artifacts establish the project and configuration record. +- **WordPress and plugins:** WordPress.org active-installation bands and plugin/core + release data are the ecosystem-owned reach and version sources; core and each plugin + stay separate subjects before an evidence-backed configuration relation joins them. +- **Gitea, Vaultwarden, Ghost, and Strapi:** the existing portfolio's GitHub/advisory and + official deployment evidence can be recaptured with immutable timestamps and hashes; + raw advisory bursts remain an overlay rather than the ranking. +- **Historical controls:** CVE/GHSA/OSV affected ranges and project release evidence pick + candidate affected/fixed pairs; KEV and EPSS prioritize which disclosed classes are + operationally useful controls, not which current project is likely vulnerable. + +The next Wayfinder decision can now define how `SourceObservation`, reusable ecosystem +assertions, ranking snapshots, and project-owned Research Priorities map into the +Security Knowledge Graph. No product ingestion or scoring code should be built until +that lifecycle and ownership decision is resolved. + +## Acceptance checks for later implementation + +A future implementation is faithful to this decision only if it can demonstrate all of +the following: + +1. Rebuild a prior ranking from pinned raw artifacts without querying live sources. +2. Show the exact source and transformation behind every displayed component. +3. Distinguish missing, zero, stale, withdrawn, disputed, rejected, and conflicting data. +4. Keep current-head discovery free of target-specific known-answer contamination. +5. Prevent a failed hard gate from being overridden by a numeric score. +6. Keep global opportunity hypotheses distinct from project observations and validated + Findings. +7. Show changes between ranking snapshots as source/value/normalization changes rather + than only a rank delta. +8. Respect source access terms, licenses, rate limits, and attribution through export and + artifact retention.