From c810fe6dfe26d97c5d044a24b76d339e4af30386 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 6 Oct 2026 00:31:51 +0200 Subject: [PATCH] Add AI topics, a sources discovery command, and intelligence-radar's pointer sources. Topics ai.benchmarks, ai.pricing and ai.models; 'sources --repo' lists visible sources matching a repo's topics. Registers the five ir-* URL sources contributed by intelligence-radar (IR-WP-0010-T04). Co-Authored-By: Claude Opus 5.5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 7780@bnt-lap001 Assistant-Session: 622cd705-cb31-4322-ba08-dd42e44fa583 --- AGENTS.md | 1 + docs/data-contracts.md | 4 ++++ inbound_library/__main__.py | 6 ++++++ inbound_library/registry.py | 17 +++++++++++++++++ registry/sources/ir-anthropic-pricing.json | 13 +++++++++++++ .../sources/ir-anthropic-sonnet-launch.json | 13 +++++++++++++ registry/sources/ir-anthropic-sonnet.json | 13 +++++++++++++ registry/sources/ir-openrouter-models.json | 14 ++++++++++++++ registry/sources/ir-swe-bench.json | 13 +++++++++++++ registry/topics.json | 7 +++++-- tests/test_library.py | 18 ++++++++++++++++++ 11 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 registry/sources/ir-anthropic-pricing.json create mode 100644 registry/sources/ir-anthropic-sonnet-launch.json create mode 100644 registry/sources/ir-anthropic-sonnet.json create mode 100644 registry/sources/ir-openrouter-models.json create mode 100644 registry/sources/ir-swe-bench.json diff --git a/AGENTS.md b/AGENTS.md index bd88279..e60dbfe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -106,6 +106,7 @@ Python 3.11+ standard library only; no install step. | `python3 -m inbound_library run-due` | one timer wake; safe to repeat | | `python3 -m inbound_library new --repo [--topic t] [--since iso]` | pull notices | | `python3 -m inbound_library show ` / `get ` | derived text / original bytes | +| `python3 -m inbound_library sources --repo [--topic t]` | registered sources a repo may see that match its topics (URL sources are pointers, never fetched) | | `python3 -m inbound_library health` | last scan, silence, quarantine, pending notices | Retained mail lives in the gitignored `data/` (see `docs/data-contracts.md`). diff --git a/docs/data-contracts.md b/docs/data-contracts.md index 852ed8a..ec8035c 100644 --- a/docs/data-contracts.md +++ b/docs/data-contracts.md @@ -76,6 +76,10 @@ subscriptions may only use registered topics. | `cadence` | `daily`, `weekly` or `irregular`; daily and weekly email sources are watched for silence (36 h or 9 days) | | `visibility` | `shared` (any matching repo) or `restricted` (owner only) | +A `url` source is a pointer. The library does not fetch it, so it produces no +items or notices. Repos find these sources with `sources --repo `. Extra +descriptive fields such as `publisher` and `notes` are allowed. + ## Subscription | Field | Meaning | diff --git a/inbound_library/__main__.py b/inbound_library/__main__.py index 77c4fd9..360cb48 100644 --- a/inbound_library/__main__.py +++ b/inbound_library/__main__.py @@ -81,6 +81,9 @@ def main(argv=None): get = sub.add_parser('get', help='write the original bytes of an item to stdout') get.add_argument('item') sub.add_parser('health', help='last scan, per-source counts, silence, quarantine') + sources = sub.add_parser('sources', help='list registered sources a repo may see, by topic') + sources.add_argument('--repo', required=True) + sources.add_argument('--topic', help='limit to one topic (default: the repo subscription topics)') args = parser.parse_args(argv) root = Path(args.root) config = _config(root, args.config) @@ -121,6 +124,9 @@ def main(argv=None): if args.command == 'get': sys.stdout.buffer.write(store.get_item(root, args.item)) return 0 + if args.command == 'sources': + _print(reg.sources_for(reg.load(root), args.repo, args.topic)) + return 0 if args.command == 'health': report = notify.health(root) _print(report) diff --git a/inbound_library/registry.py b/inbound_library/registry.py index a57e522..957b592 100644 --- a/inbound_library/registry.py +++ b/inbound_library/registry.py @@ -120,3 +120,20 @@ def subscribers_for(registry, source_id): set(source.get('topics', [])) & set(subscription.get('topics', [])): matched.append(repo) return matched + + +def sources_for(registry, repo, topic=None): + """Sources visible to a repo whose topics match its subscription (or one topic). + + URL sources are pointers: the library lists them and does not fetch them. + """ + if topic: + wanted = {topic} + else: + subscription = registry['subscriptions'].get(repo, {}) + wanted = set(subscription.get('topics', [])) + listed = [] + for ident, source in sorted(visible_sources(registry, repo).items()): + if wanted & set(source.get('topics', [])): + listed.append({key: value for key, value in source.items() if key != '_file'}) + return listed diff --git a/registry/sources/ir-anthropic-pricing.json b/registry/sources/ir-anthropic-pricing.json new file mode 100644 index 0000000..4ed266e --- /dev/null +++ b/registry/sources/ir-anthropic-pricing.json @@ -0,0 +1,13 @@ +{ + "id": "ir-anthropic-pricing", + "owner_repo": "intelligence-radar", + "channel": "url", + "url": "https://claude.com/pricing", + "topics": [ + "ai.pricing" + ], + "cadence": "irregular", + "visibility": "shared", + "publisher": "Anthropic", + "notes": "Primary source captured by intelligence-radar (catalog/sources.json id anthropic-pricing). Pointer only; inbound-library does not fetch it." +} diff --git a/registry/sources/ir-anthropic-sonnet-launch.json b/registry/sources/ir-anthropic-sonnet-launch.json new file mode 100644 index 0000000..c59bfc2 --- /dev/null +++ b/registry/sources/ir-anthropic-sonnet-launch.json @@ -0,0 +1,13 @@ +{ + "id": "ir-anthropic-sonnet-launch", + "owner_repo": "intelligence-radar", + "channel": "url", + "url": "https://www.anthropic.com/news/claude-sonnet-5", + "topics": [ + "ai.models" + ], + "cadence": "irregular", + "visibility": "shared", + "publisher": "Anthropic", + "notes": "Primary source captured by intelligence-radar (catalog/sources.json id anthropic-sonnet-launch). Pointer only; inbound-library does not fetch it." +} diff --git a/registry/sources/ir-anthropic-sonnet.json b/registry/sources/ir-anthropic-sonnet.json new file mode 100644 index 0000000..2bf309e --- /dev/null +++ b/registry/sources/ir-anthropic-sonnet.json @@ -0,0 +1,13 @@ +{ + "id": "ir-anthropic-sonnet", + "owner_repo": "intelligence-radar", + "channel": "url", + "url": "https://www.anthropic.com/claude/sonnet", + "topics": [ + "ai.models" + ], + "cadence": "irregular", + "visibility": "shared", + "publisher": "Anthropic", + "notes": "Primary source captured by intelligence-radar (catalog/sources.json id anthropic-sonnet). Pointer only; inbound-library does not fetch it." +} diff --git a/registry/sources/ir-openrouter-models.json b/registry/sources/ir-openrouter-models.json new file mode 100644 index 0000000..e9d9523 --- /dev/null +++ b/registry/sources/ir-openrouter-models.json @@ -0,0 +1,14 @@ +{ + "id": "ir-openrouter-models", + "owner_repo": "intelligence-radar", + "channel": "url", + "url": "https://openrouter.ai/api/v1/models", + "topics": [ + "ai.models", + "ai.pricing" + ], + "cadence": "irregular", + "visibility": "shared", + "publisher": "OpenRouter", + "notes": "Primary source captured by intelligence-radar (catalog/sources.json id openrouter-models). Pointer only; inbound-library does not fetch it." +} diff --git a/registry/sources/ir-swe-bench.json b/registry/sources/ir-swe-bench.json new file mode 100644 index 0000000..7cb3f8f --- /dev/null +++ b/registry/sources/ir-swe-bench.json @@ -0,0 +1,13 @@ +{ + "id": "ir-swe-bench", + "owner_repo": "intelligence-radar", + "channel": "url", + "url": "https://www.swebench.com/", + "topics": [ + "ai.benchmarks" + ], + "cadence": "irregular", + "visibility": "shared", + "publisher": "SWE-bench maintainers", + "notes": "Primary source captured by intelligence-radar (catalog/sources.json id swe-bench). Pointer only; inbound-library does not fetch it." +} diff --git a/registry/topics.json b/registry/topics.json index 4b25614..08f4aa6 100644 --- a/registry/topics.json +++ b/registry/topics.json @@ -1,7 +1,10 @@ { - "version": 2, + "version": 3, "topics": { "intelligence.daily-brief": "Daily editorial brief on intelligence and AI (automation-generated, unreviewed)", - "intelligence.weekly-brief": "Weekly editorial brief on intelligence and AI (automation-generated, unreviewed)" + "intelligence.weekly-brief": "Weekly editorial brief on intelligence and AI (automation-generated, unreviewed)", + "ai.benchmarks": "Capability benchmarks and leaderboards for AI systems (primary maintainer pages)", + "ai.pricing": "Published prices and availability of AI models and APIs", + "ai.models": "Model releases, model cards and provider model catalogs" } } diff --git a/tests/test_library.py b/tests/test_library.py index e0f160a..b71e682 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -98,6 +98,24 @@ class Library(unittest.TestCase): self.assertEqual(reg.validate(registry), []) self.assertEqual(reg.subscribers_for(registry, 'arxiv-agents'), ['markitect']) + def test_sources_lists_visible_matches_only(self): + write(self.root / 'registry' / 'sources' / 'arxiv-agents.json', { + 'id': 'arxiv-agents', 'owner_repo': 'intelligence-radar', 'channel': 'url', + 'url': 'https://arxiv.org/list/cs.MA/recent', 'topics': ['research.papers'], + 'cadence': 'irregular', 'visibility': 'shared'}) + write(self.root / 'registry' / 'sources' / 'private.json', { + 'id': 'private', 'owner_repo': 'intelligence-radar', 'channel': 'url', + 'url': 'https://example.org', 'topics': ['research.papers'], + 'cadence': 'irregular', 'visibility': 'restricted'}) + write(self.root / 'registry' / 'subscriptions' / 'markitect.json', { + 'repo': 'markitect', 'agent': 'markitect', 'topics': ['research.papers'], + 'channels': ['pull']}) + registry = reg.load(self.root) + self.assertEqual([s['id'] for s in reg.sources_for(registry, 'markitect')], ['arxiv-agents']) + self.assertEqual([s['id'] for s in reg.sources_for(registry, 'intelligence-radar', + 'research.papers')], + ['arxiv-agents', 'private']) + # Intake def test_scan_retains_message_attachment_and_provenance(self): self.drop('1.eml', mail('Grok ', 'Daily brief', 'Body text', b'attached'))