feat(catalog): seed Mokosh product catalog + one-call admin create + token audit (BUNYIP-33) #34

Merged
nrupard merged 2 commits from feat/bunyip-33-catalog-entitlements into main 2026-06-02 17:46:07 +02:00
Owner

What

Seeds the Mokosh product distribution catalog and completes the catalog/entitlement surface (BUNYIP-33, subtask of the BUNYIP-28 story).

Changes

  • Seed migration (idempotent, ON CONFLICT DO NOTHING): the four real products published in psa-systems-private - mokosh-server, mokosh-api, mokosh-www (OCI images @ v0.2.0) and vervain-agent (generic-package binaries @ its current published version).
  • One-call admin create: CreateApplication + the repository INSERT now carry the full distribution config (Forgejo download coordinates, artifact source, OCI image coordinates). Previously creating a usable product required create-then-update.
  • Shared validation in the model: DistributionConfig::validate() holds the rules (artifact_source values, source-aware Forgejo all-or-nothing, OCI all-or-nothing); both the create and update handlers call it, so they cannot drift. Full unit-test coverage of every rule.
  • Token-issuance audit: oci_login_succeeded rows now carry the target application as a resource (resource_type + resource_id) alongside user and scope.
  • Entitlement: verified as already enforced by the M1 single-plan model on both paths (downloads via claims with 15-min staleness bound; OCI via per-request DB reload). Documented, no code change.

Live verification (dev stack, all acceptance criteria)

Criterion Result
Fresh DB seeds the catalog PASS - 4 products, migration applies cleanly
Admin adds a product with Forgejo coordinates without code changes PASS - one POST returns 201 with all coords persisted; partial config gets a clean 400
Non-entitled user denied PASS - 401 + audit reason no_active_membership
Token issuance audit-logged with user + application PASS - resource_type=application, resource_id matches the app (pre-change rows show NULL for comparison)
Downloads listing for entitled member PASS - GET /v1/downloads lists vervain-agent with its real binaries from the Forgejo generic package registry

Notes

  • "Downloads page lists the products" (the web UI half of criterion 1) is BUNYIP-34 scope; the API equivalent is verified here.
  • The vervain-agent pinned version (0.1.0-6-g7ecf9c3) is its only published version today; admins update the pin as releases land.
## What Seeds the Mokosh product distribution catalog and completes the catalog/entitlement surface (**BUNYIP-33**, subtask of the BUNYIP-28 story). ## Changes * **Seed migration** (idempotent, ON CONFLICT DO NOTHING): the four real products published in psa-systems-private - `mokosh-server`, `mokosh-api`, `mokosh-www` (OCI images @ v0.2.0) and `vervain-agent` (generic-package binaries @ its current published version). * **One-call admin create**: `CreateApplication` + the repository INSERT now carry the full distribution config (Forgejo download coordinates, artifact source, OCI image coordinates). Previously creating a usable product required create-then-update. * **Shared validation in the model**: `DistributionConfig::validate()` holds the rules (artifact_source values, source-aware Forgejo all-or-nothing, OCI all-or-nothing); both the create and update handlers call it, so they cannot drift. Full unit-test coverage of every rule. * **Token-issuance audit**: `oci_login_succeeded` rows now carry the target application as a resource (resource_type + resource_id) alongside user and scope. * **Entitlement**: verified as already enforced by the M1 single-plan model on both paths (downloads via claims with 15-min staleness bound; OCI via per-request DB reload). Documented, no code change. ## Live verification (dev stack, all acceptance criteria) | Criterion | Result | | --- | --- | | Fresh DB seeds the catalog | PASS - 4 products, migration applies cleanly | | Admin adds a product with Forgejo coordinates without code changes | PASS - one POST returns 201 with all coords persisted; partial config gets a clean 400 | | Non-entitled user denied | PASS - 401 + audit reason no_active_membership | | Token issuance audit-logged with user + application | PASS - resource_type=application, resource_id matches the app (pre-change rows show NULL for comparison) | | Downloads listing for entitled member | PASS - GET /v1/downloads lists vervain-agent with its real binaries from the Forgejo generic package registry | ## Notes * "Downloads page lists the products" (the web UI half of criterion 1) is BUNYIP-34 scope; the API equivalent is verified here. * The vervain-agent pinned version (0.1.0-6-g7ecf9c3) is its only published version today; admins update the pin as releases land.
feat(catalog): seed Mokosh product catalog + one-call admin create + token audit (BUNYIP-33)
Some checks failed
Check / fmt / clippy / build / test (pull_request) Failing after 18s
eb9653659f
Seeds the distribution catalog with the real Mokosh products published in psa-systems-private and completes the remaining catalog/entitlement surface.

- Migration 20260602000050 seeds four products idempotently (ON CONFLICT DO NOTHING so admin edits survive re-runs): mokosh-server, mokosh-api, mokosh-www as OCI images pinned to v0.2.0, and vervain-agent as generic-package binaries pinned to its current published version. Catalog-only rows use a 'catalog' container_name sentinel since they exist for distribution, not the hosted-app machinery.
- CreateApplication accepts the full distribution config (Forgejo download coordinates, artifact source, OCI image coordinates), and ApplicationRepository::create persists them, so an admin can add a fully usable product in one API call instead of create-then-update.
- The distribution validation rules move into the model as DistributionConfig::validate() (per the BUNYIP-30 review finding that validity belongs in the model): one set of rules shared by the create and update handlers, covering artifact_source values, source-aware Forgejo all-or-nothing, and OCI all-or-nothing. update_application's inline validation is replaced by the shared call; new model unit tests cover every rule.
- OCI registry token issuance now audits the target application as a resource (resource_type=application + resource_id) alongside the user and scope, so per-product pull activity is traceable from oci_login_succeeded rows without joining per-blob requests.
- Entitlement: verified as already enforced by the M1 single-plan model on both paths (downloads gate on MemberUser/has_member_access claims with a 15-minute staleness bound from the access-token TTL; OCI re-loads the user from the DB on every request). No code change needed; the model is documented in the issue.

Live verification on the dev stack (all four acceptance criteria): the migration applies and seeds the catalog; an admin one-call create with full coordinates returns 201 with everything persisted while a partial config gets a clean 400 from the shared validation; oci_login_succeeded audit rows now carry the application resource (verified against the seeded mokosh-server; pre-change rows show NULL for comparison); a non-member token request is denied 401 with audit reason no_active_membership; and GET /v1/downloads for an entitled member lists the seeded vervain-agent product with its real binaries from the Forgejo generic package registry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(catalog): address PR #34 review findings
Some checks failed
Check / fmt / clippy / build / test (pull_request) Failing after 18s
Create release / Create release from merged PR (pull_request) Has been skipped
336a4da4b4
- Add is_hosted discriminator: catalog-only products (mokosh-server/api/www, vervain-agent) no longer appear as dead launch tiles in the application hub. Migration adds the column + partial index, seeds set is_hosted = FALSE, /v1/applications now lists only active hosted apps via ApplicationRepository::list_active_hosted(); /v1/downloads and the OCI registry still serve catalog products.
- Harden DistributionConfig::validate(): reject empty/whitespace-only field values (they passed presence checks but broke at pull/download time) and reject forgejo_package on a release artifact_source (silently dead configuration). New unit tests cover both rules.
- Deduplicate handler construction: create/update admin handlers now build their DistributionConfig via CreateApplication::distribution() and UpdateApplication::distribution_merged(), so the field mapping lives in the model and the two handlers cannot drift. distribution_merged() handles the empty-string clear sentinel for forgejo_package as three-state (clear / set / keep).
- CreateApplication/UpdateApplication accept is_hosted; repository create() defaults it to TRUE (hosted) via COALESCE, update() COALESCEs it like other optional fields.
- Drop redundant #[serde(default)] attrs on Option fields of CreateApplication.
- Migration header documents the M1 entitlement model, the self-hoster deactivation path, and references BUNYIP-39 (post-M1 per-product entitlements backlog issue).

Verified: fmt clean, zero new clippy violations vs main, workspace tests pass (one pre-existing flaky env-var test, BUNYIP-36, passes in isolation), live dev stack: hub lists only hosted apps, /v1/downloads serves vervain-agent assets from Forgejo, just verify-oci all checks pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch feat/bunyip-33-catalog-entitlements 2026-06-02 17:46:07 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
psa-systems/bunyip!34
No description provided.