Hackorda Docs
Flows

F 04 Catalog Setup

Status: ๐ŸŸข ready to test Owner: Adilet Last updated: 2026-05-08 Last verified on production: โ€”

Goal

An admin sets up the catalog tree (Organization โ†’ Product โ†’ Product Version) that test cycles attach to. This flow is the prerequisite for every cycle creation.

Actors

  • Primary โ€” Admin

Preconditions

  • Admin is signed in.
  • (Optional) Production already has the seeded Hackorda organisation, Hackorda Web product, and v0.1.0 version โ€” for an additional setup, you start from this baseline.

Trigger

Admin clicks "New" on /app/admin/organizations (or /products / /products/:id versions panel).

Happy path โ€” Step 1: create organization

#ActorUI surfaceActionAPI callDB writesSide effectsSuggested event
1Admin/app/admin/organizationsClick "New organization"โ€”โ€”โ€”โ€”
2Adminsheet formFill name, slug, type (internal/client/sponsor/partner), websitePOST /api/admin/organizationsINSERT INTO organizationsโ€”org.created
3Frontendlist re-renderstoast: "Organization created"โ€”โ€”โ€”โ€”

Happy path โ€” Step 2: create product

#ActorUI surfaceActionAPI callDB writesSide effectsSuggested event
1Admin/app/admin/productsClick "New product"โ€”โ€”โ€”โ€”
2Adminsheet formPick organization, fill name, slug, URL, discipline (web_app/website/mobile/backend/api/ml/data/infra/mixed), payout-requires-verification flagPOST /api/admin/productsINSERT INTO productsโ€”product.created
3Frontendlist re-renderstoast: "Product created"โ€”โ€”โ€”โ€”

Happy path โ€” Step 3: create product version

#ActorUI surfaceActionAPI callDB writesSide effectsSuggested event
1Admin/app/admin/products/:idOpen versions panel, click "New version"โ€”โ€”โ€”โ€”
2Admininline formFill version (e.g. v0.2.0), name, released_at, notesPOST /api/admin/products/:id/versionsINSERT INTO product_versionsโ€”product_version.created
3Frontendversions list updatestoast: "Version created"โ€”โ€”โ€”โ€”

Acceptance criteria

  • AC-1 โ€” Given the admin fills a unique slug, when they submit the org form, then a row is inserted and the slug is enforced unique.
  • AC-2 โ€” Given the admin tries to create a product without selecting an organisation, when they submit, then the form blocks with a validation error.
  • AC-3 โ€” Given a product is configured with payoutRequiresVerification = true, when later cycles inherit, then the verification gate is active by default for issues filed under those cycles (see F-10).
  • AC-4 โ€” Given the admin enters a version string identical to an existing version on the same product, when they submit, then the API returns 400 (unique on (product_id, version)).
  • AC-5 โ€” Given the org / product / version are created, when the admin opens /app/admin/test-cycles/new, then the new entries appear in the dropdowns.

Test data / fixtures

Baseline seed not required โ€” this flow creates the catalog tree. Use a unique slug like e2e-test-org to avoid colliding with seeded data.

Negative paths

#ScenarioExpected behavior
N-1Duplicate org slugAPI 400 "slug already in use"
N-2Missing required fieldsUI form validation blocks submit
N-3Non-admin callerAPI 403
N-4Delete an org with products attachedAPI 400 OR cascade โ€” verify policy on prod (currently RESTRICT)
N-5Bad URL format on products.urlUI validation OR API 400

Manual QA checklist

  • Create a new Organization with slug qa-test-org
  • Confirm it appears in the list
  • Open the org detail page โ€” basic info renders correctly
  • Create a Product under it: name QA Test Product, slug qa-test-product, URL https://example.com, discipline web_app, verification gate ON
  • Confirm the product card shows the URL link and discipline
  • Create version v0.1.0 for the product
  • Confirm version appears in versions panel
  • On /app/admin/test-cycles/new, confirm the new product + version are pickable
  • (Cleanup) Delete the version โ†’ product โ†’ organization (verify cascade or restrict behavior matches policy)

Automated test outline

test.describe("F-04 catalog setup", () => {
  test("creates org โ†’ product โ†’ version end-to-end", async ({ page, request }) => { โ€ฆ });
  test("rejects duplicate org slug", async ({ request }) => { โ€ฆ });
  test("blocks non-admin", async ({ request }) => { โ€ฆ });
});

Code references

  • Pages: src/app/app/admin/organizations/page.tsx, src/app/app/admin/products/page.tsx
  • API:
    • src/app/api/admin/organizations/route.ts (GET, POST)
    • src/app/api/admin/organizations/[id]/route.ts (GET, PATCH, DELETE)
    • src/app/api/admin/products/route.ts
    • src/app/api/admin/products/[id]/route.ts
    • src/app/api/admin/products/[id]/versions/route.ts
  • Hooks: src/hooks/test-cycles/organizations.ts, src/hooks/test-cycles/products.ts
  • Schema: organizations, products, product_versions

Events emitted (proposed)

  • org.created โ€” { org_id, slug, type, created_by }
  • org.updated โ€” { org_id, fields_changed }
  • org.deleted โ€” { org_id }
  • product.created โ€” { product_id, org_id, slug, discipline, gate_default }
  • product.updated, product.deleted โ€” analogous
  • product_version.created โ€” { version_id, product_id, version_string }

Open questions / known gaps

  • No multi-tenant isolation: every admin sees every org. (Single-tenant by design for v1; flagged in feature-matrix.md ยง3.1.)
  • No org logo / branding upload yet.
  • Product integration_provider_slug + integration_config columns exist but the only wired provider is Linear (F-15).

On this page