canned-prompts/service/tests
tegwick d1631e4eb4 Publisher identity: app-local tokens, and enforceable namespace ownership
Closes the gap that made section 20.1 ownership advisory. The service could
refuse anonymous callers but could not tell two publishers apart, so a closed
namespace could be protected and never attributed.

Follows DR-3, resolved 2026-07-10: app-local accounts, with platform OIDC
demand-gated on client SSO requests, instance consolidation, or local-account
toil across more than two apps. None of those triggers has fired here, so this
is deliberately not OIDC. Tokens rather than accounts because a registry is
consumed by CLIs and agents — no browser, no session, no UI to log into, and a
login surface nothing uses is a liability.

The whole authentication boundary stays in auth.py, so contract section 2.3 is
met and a later OIDC switch is bounded rather than a search.

The properties that matter are the ones about what a credential cannot do:

- tokens are stored hashed, because a registry that can print its own
  credentials back is one database read away from impersonating every publisher
  it knows, and are shown once at creation;
- an unknown token and a wrong token get the same answer, so a caller cannot
  enumerate which tokens exist;
- a publisher cannot mint publishers — that would be an administrator with
  extra steps, and revoking one would no longer revoke what it could do;
- the operator token publishes but owns nothing, so it is a bootstrap path
  rather than an identity that can hold a namespace;
- a closed namespace with no owner recorded admits nobody, including the
  operator: reading a missing owner as "anyone" would invert the point of
  closing it;
- revocation is a timestamp, not a delete, so what someone published stays
  attributed to them after their credential is withdrawn.

Migration 0003 adds publishers and index_entries.published_by. The attribution
is a name rather than a foreign key, so deleting a publisher cannot erase the
history of what they published.

Service tests 49 -> 61.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 388925@bnt-lap001
Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-08 10:35:51 +02:00
..
conftest.py CANP-WP-0006 T03: read API 2026-09-06 20:23:25 +02:00
test_health.py Survive credential rotation: re-read the lease for every connection 2026-09-08 10:11:35 +02:00
test_publish_api.py CANP-WP-0006 T04: publish API 2026-09-06 20:30:00 +02:00
test_publisher_identity.py Publisher identity: app-local tokens, and enforceable namespace ownership 2026-09-08 10:35:51 +02:00
test_read_api.py CANP-WP-0006 T03: read API 2026-09-06 20:23:25 +02:00
test_schema.py Service fixes found by the first real deployment 2026-09-08 08:56:59 +02:00