Extend WP-0006 auth for per-human sub-credentials (WP-0009-T02)
migrations/0005_licensor_credentials.sql: licensors can now hold multiple rows per licensor_id (credential_label, rights tier, issued_by, revoked_at). Real structural finding: licensor_id couldn't simply become non-unique, since phase_manifests, extensions, and breach_records all FK to licensors(licensor_id), which only worked because that column used to be unique. Introduced licensor_identities (one row per tenant) as the new FK target for all four tables, with an ensure_licensor_identity trigger auto-creating the identity on first credential insert - so existing code (including every earlier test fixture) needed no changes. registry.py: Licensor gains credential_label/rights; RIGHTS_TIERS + has_right() ordinal helper (enforcement is Control Plane's job, T03/ T04, not this task's); issue_sub_credential/revoke_sub_credential (revocation via a SECURITY DEFINER function, matching set_extension_status's existing pattern - trf_app has no UPDATE grant on licensors); authenticate() rejects revoked credentials identically to unrecognized ones. Attribution scoped honestly: ledger_entry.schema.json stays unmodified (frozen Stage 0 surface, additionalProperties:false) - per-entry human attribution is a hosting-layer-only column (ledger_entries.submitted_by_token, ledger.get_ledger_attribution()), recorded alongside but never inside the signed entry payload. Narrower than "the signature names the human," but exactly the "(or an accompanying attributable field)" alternative this task's own description anticipated. All four Docker-gated test files that append Ledger entries needed migration 0005 added (append_entry's INSERT now references the new column). New tests/test_licensor_credentials.py (8 tests): multi- credential resolution, duplicate-label rejection, revocation and its idempotence, invalid-rights rejection, the has_right helper, per-entry attribution recorded and not leaking into exported ledger JSON, and DB-level UPDATE rejection. Full suite: 84 offline, 41 with Docker (up from 30); no stray containers left running.
This commit is contained in:
parent
04c604745b
commit
7986e62f31
10 changed files with 581 additions and 10 deletions
|
|
@ -72,7 +72,7 @@ hypothetical branch — see T02 below, added specifically for this reason.
|
|||
|
||||
```task
|
||||
id: TREV-WP-0009-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "52ae3a7a-6b55-4694-8bc1-cb0e32a4fe31"
|
||||
```
|
||||
|
|
@ -91,6 +91,54 @@ plus new tests for the sub-credential path. Does not change the Ledger's
|
|||
append-only guarantees or the hash-chain/signature scheme itself, only
|
||||
who may authenticate as `binky` and how that's distinguished.
|
||||
|
||||
**Result:** `migrations/0005_licensor_credentials.sql` — `licensors` can
|
||||
now hold multiple rows per `licensor_id` (`credential_label`, `rights`
|
||||
tier, `issued_by`, `revoked_at`). Real structural finding along the way:
|
||||
`licensor_id` could not simply become non-unique, because
|
||||
`phase_manifests`, `extensions`, and `breach_records` all carry a foreign
|
||||
key to `licensors(licensor_id)`, which only worked because that column
|
||||
used to be unique — a FK target must be unique. Introduced a new
|
||||
`licensor_identities` table (one row per tenant) as the FK target for all
|
||||
four tables instead, with an `ensure_licensor_identity` trigger that
|
||||
auto-creates the identity row on first credential insert (so existing
|
||||
code that inserts directly into `licensors` — including every earlier
|
||||
test fixture — needed no changes), plus
|
||||
`registry.create_licensor_identity` for callers that want tenant
|
||||
onboarding as its own explicit step. `registry.py` gained
|
||||
`Licensor.credential_label`/`.rights`, `RIGHTS_TIERS`/`has_right()` (an
|
||||
ordinal helper — enforcing what each tier may do is Control Plane's own
|
||||
job, T03/T04, not this task's), `issue_sub_credential`/
|
||||
`revoke_sub_credential` (revocation via a `revoke_credential()` SECURITY
|
||||
DEFINER function, matching `set_extension_status`'s existing pattern —
|
||||
`trf_app` has no UPDATE grant on `licensors`). `authenticate()` now
|
||||
rejects a revoked credential identically to an unrecognized one.
|
||||
|
||||
**Attribution, scoped honestly:** `ledger_entry.schema.json` was
|
||||
deliberately left unmodified (frozen Stage 0 normative surface,
|
||||
`additionalProperties: false`) — per-entry human attribution is instead a
|
||||
hosting-layer-only column, `ledger_entries.submitted_by_token`, recorded
|
||||
alongside but never inside the signed entry payload
|
||||
(`ledger.get_ledger_attribution`). This means the claim is narrower than
|
||||
"the signature itself names the human": the cryptographic signature is
|
||||
unchanged and still only attests to the entry content and chain; the
|
||||
*database* additionally knows which credential submitted each entry,
|
||||
queryable but not portable/exported with the entry itself. Exactly the
|
||||
"(or an accompanying attributable field)" alternative this task's own
|
||||
description anticipated.
|
||||
|
||||
All four Docker-gated test files that append Ledger entries needed
|
||||
migration `0005` added to their setup (`ledger.append_entry`'s INSERT now
|
||||
references the new column) — done for
|
||||
`test_registry_hosting.py`/`test_ledger_hosting.py`/
|
||||
`test_hosted_conformance.py`/`test_onboarding_hosted.py`. New
|
||||
`tests/test_licensor_credentials.py` (8 tests): multi-credential
|
||||
resolution to the same `licensor_id`, duplicate-active-label rejection,
|
||||
revocation and its idempotence, invalid-rights rejection, the `has_right`
|
||||
ordinal helper, per-entry attribution recorded and *not* leaking into the
|
||||
exported ledger JSON, and DB-level UPDATE rejection on `licensors`. Full
|
||||
suite: 84 passing offline (unchanged), 41 passing with Docker (up from
|
||||
30); no stray containers left running.
|
||||
|
||||
## Control Plane backend: auth layer and audit log
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue