# Source Shelf: five rules and a five-check script (free sample)

Version 1.0, 2026-10-29. By PaperChaseWebb. For founders, consultants and small teams who keep citing the same few industry reports and want those citations current and traceable. One Markdown file and one Python 3 script, standard library only. The script was tested on its worked example (5 findings, then 0) and on a missing file (exit 2). It was not run inside any agent harness or on real reports.

Lists of "reports that will shape your next business idea" are everywhere. Most link to a post about the report instead of the report, give no edition, and say nothing about who paid for the research. A year later someone quotes last edition's figure, or a figure the report never printed. These five rules keep a small shelf honest, and the script checks them in one file. The reading method (what is changing, who is struggling, what could help them) comes from an IdeasLab Global post, credited in the full pack.

## Install in one line

Save this page as FREE-SAMPLE.md, then:

```
python3 -c "import re;t=open('FREE-SAMPLE.md').read();open('shelf_mini.py','w').write(re.search(r'~~~python\n(.*?)~~~',t,re.S).group(1))"
python3 shelf_mini.py shelf-lite.json
```

## Five rules

1. **One owner.** One person decides what goes on the shelf. Everyone else, agents included, suggests.
2. **Link to the publisher, not the post.** Every report links over https to the publisher's own site. A carousel, a thread or a summary blog is not the report.
3. **Dated, and checked.** Every report has its edition and publish date, and someone looked for a newer edition in the last 180 days.
4. **Three questions, every time.** Read each report for what is changing, who is struggling because of it, and what could help them. The third answer is yours, so say so.
5. **Every figure comes with its quote.** A number in an answer must appear, as written, in the sentence you copied from the report, with its page. If you cannot find the sentence, leave the number out.

Agents never buy, register, subscribe, give an email address or comment to unlock a report. If a report is gated, the owner decides whether to get it.

## The five checks

| Id | Check |
|---|---|
| M1 | the shelf names an owner |
| M2 | every link is https on the publisher's own domain or a subdomain of it |
| M3 | every report has an edition, a publish date and a check within 180 days |
| M4 | every reading answers changing, struggling and help |
| M5 | every number in an answer appears in that answer's quote |

## The script

~~~python
#!/usr/bin/env python3
"""shelf_mini.py: five checks on a small report shelf (shelf-lite.json). Free sample of the Source Shelf.
Usage: python3 shelf_mini.py shelf-lite.json [--today YYYY-MM-DD]
Exit 0 clean, 1 findings, 2 missing or unreadable file. Standard library only. Reads one file, writes nothing."""
import datetime as dt, json, re, sys
from urllib.parse import urlparse

NUM = re.compile(r"\d+(?:[.,]\d+)*")


def date(s):
    try:
        return dt.date.fromisoformat(str(s))
    except ValueError:
        return None


def check(j, today):
    F = []
    if not str(j.get("owner", "")).strip():
        F.append("M1 name the one person who decides what goes on the shelf")
    for r in j.get("reports") or []:
        rid = r.get("id") or "?"
        dom = str(r.get("publisher_domain", "")).lower().strip(".")
        u = urlparse(str(r.get("link", "")))
        host = (u.hostname or "").lower()
        if u.scheme != "https" or not dom or not (host == dom or host.endswith("." + dom)):
            F.append(f"M2 report {rid}: link to the report on the publisher's own site (https, {dom or 'no domain given'}), not a post or summary")
        checked = date(r.get("checked"))
        if not str(r.get("edition", "")).strip() or not date(r.get("published")) or not checked or (today - checked).days > 180:
            F.append(f"M3 report {rid}: needs the edition, its publish date, and a check for a newer edition in the last 180 days")
    for x in j.get("readings") or []:
        xid = f"{x.get('report', '?')} {x.get('edition', '')}".strip()
        miss = [q for q in ("changing", "struggling", "help") if not str((x.get(q) or {}).get("answer", "")).strip()]
        if miss:
            F.append(f"M4 reading of {xid}: no answer for {', '.join(miss)}")
        for q in ("changing", "struggling", "help"):
            a = x.get(q) or {}
            have = set(NUM.findall(str(a.get("quote", ""))))
            bad = list(dict.fromkeys(n for n in NUM.findall(str(a.get("answer", ""))) if n not in have))
            if bad:
                F.append(f"M5 reading of {xid}, {q}: {', '.join(bad)} is not in a quote from the report; copy it with its page, or take it out")
    return F


def main(a):
    if len(a) < 2:
        print(__doc__); return 2
    today = dt.date.today()
    if "--today" in a:
        today = date(a[a.index("--today") + 1] if a.index("--today") + 1 < len(a) else "")
        if not today:
            print("--today needs YYYY-MM-DD", file=sys.stderr); return 2
    try:
        j = json.load(open(a[1]))
    except (OSError, ValueError) as e:
        print(f"cannot read {a[1]}: {e}", file=sys.stderr); return 2
    F = check(j, today)
    for f in F:
        print(f)
    print(f"5 checks, {len(F)} findings")
    return 1 if F else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv))
~~~

## Worked example

Alder and Pike Advisory is made up, and so are its publishers, reports, quotes and figures. Its shelf-lite.json before:

```json
{
 "owner": "",
 "reports": [
  {"id": "r1", "publisher": "Northgate Institute (made up)", "series": "Small Business Automation Survey", "edition": "2026 edition",
   "published": "2026-06-01", "checked": "2026-10-09", "publisher_domain": "northgate.example.org",
   "link": "https://www.instagram.com/p/made-up-carousel/"},
  {"id": "r2", "publisher": "Kestrel Capital (made up)", "series": "Venture Pulse", "edition": "",
   "published": "", "checked": "2026-02-10", "publisher_domain": "kestrel.example.com",
   "link": "https://kestrel.example.com/pulse"}
 ],
 "readings": [
  {"report": "r1", "edition": "2026 edition",
   "changing": {"answer": "Most small firms now book work in software: 58 percent.", "quote": "58 percent of owners with fewer than 20 staff now book jobs in software.", "page": "p. 7"},
   "struggling": {"answer": "Firms with 1 to 4 staff, about 4 in 10 of them.", "quote": "Owners of the smallest firms said setup took longer than they planned.", "page": "p. 12", "who": "owners of firms with 1 to 4 staff"},
   "help": {"answer": ""}}
 ]
}
```

Run with `--today 2026-10-29`:

```
M1 name the one person who decides what goes on the shelf
M2 report r1: link to the report on the publisher's own site (https, northgate.example.org), not a post or summary
M3 report r2: needs the edition, its publish date, and a check for a newer edition in the last 180 days
M4 reading of r1 2026 edition: no answer for help
M5 reading of r1 2026 edition, struggling: 1, 4, 10 is not in a quote from the report; copy it with its page, or take it out
5 checks, 5 findings
```

The fixes: Mara Ellison is named as the owner. r1's link moves from a social post to the report on northgate.example.org. r2 gets its edition (Q3 2026), the date it came out and a fresh check. The struggling answer drops "about 4 in 10", which the report never said, and keeps what the quote supports. The help answer is written, and starts with "Our inference". After:

```
5 checks, 0 findings
```

## One prompt to try

Paste into your agent: "Open the publisher's own page for [report series]. Tell me the current edition and the date it came out, exactly as the page shows them. If the report is behind a form, payment or sign-up, stop and tell me. Then read it and answer three questions: what is changing, who is struggling because of it, and what could help them. For the first two, copy the sentence from the report word for word with its page. Mark the third as your inference, with no figures."

## The full pack

The paid Source Shelf pack (pp-source-shelf, $19) grows this into a shelf your team can run for years: reports with a cadence, a basis and a note on what the publisher sells, edition checks recorded in a ledger, a cap on shelf size and one series per slot, quotes with locators and a length limit, help that rests on named rows, proposals that hand a struggling buyer to the Problem Mining Loop, fifteen checks, a one-page shelf, and every add, drop and replace waiting for the owner's yes, tied to its sha256 and typed at their own terminal. If you would rather have it set up with you, book a call from the home page at paperchasewebb.ai.

Made-up example; the business, people, publishers, reports, quotes and links are invented.

PaperChaseWebb 💸
