🎥 Videography & Film · project teamwork

Collaborator Registry

Collaborator Registry mints a did:prism for project teamwork so identity travels with the person, not the platform.

DID Registrar· publish a did:prism
Section · Identus

The primitive.

full primer →

Videographers tap once and the app mints a published did:prism for project teamwork — a decentralised identifier they own outright, resolvable by anyone, portable off this platform the day they leave it.

Why this primitiveDID Registrar fits project teamwork in Videography & Film because the people and works involved need a portable identifier they own — not a row in someone else's platform database that disappears when the platform does.

Primitive
`POST /did-registrar/dids` with a document template (curve `secp256k1`, purposes `authentication` + `assertionMethod`) then `POST /did-registrar/dids/{didRef}/publications` — the Identus Cloud Agent mints a decentralised identifier the holder controls and publishes it so anyone can resolve it
Drives the UI as
a one-tap identity button that mints a portable DID for the artist, studio, or work and shows its resolvable document
Appendix · Agent mode

Pick how the agent runs.

Start here. It is the only mode that reliably fits a one-shot, 5-credit Lovable build, and you can swap the base URL for a real agent later without touching the UI.

Appendix · Secrets

No keys required.

The simulated agent runs entirely inside the app. Paste the prompt and build — nothing to configure, nothing to wait for.

Switch to Docker or Fly.io above when you want a real Cloud Agent.

Appendix · Mega-prompt

The build prompt.

Paste into a fresh Lovable project. The prompt below is written for the Simulated agent mode. read the build strategy →

Build "Collaborator Registry" as a ONE-SHOT Lovable build.
Single-page TanStack Start app. Cut scope ruthlessly.

CONCEPT
Creators can publish their skills and roles, allowing teams to verify qualifications and contributions in collaborative projects.
Discipline: Videography & Film (project teamwork).
Recipe: DID Registrar (publish a did:prism) as the single Hyperledger Identus primitive.
Why Identus: DID Registrar fits project teamwork in Videography & Film because the people and works involved need a portable identifier they own — not a row in someone else's platform database that disappears when the platform does.

LOVABLE BUDGET (HARD CAP: ONE-SHOT, ~5 CREDITS TOTAL):
This prompt MUST ship a working demo on the FIRST message with zero follow-ups.
- ONE TanStack Start app, ONE route (`src/routes/index.tsx`). No auth, no nav.
- ONE server module `src/lib/identus.functions.ts` with `createServerFn` wrappers.
- ONE client surface wired to it (form + result panel).
- NO database, NO Lovable Cloud, NO file uploads, NO extra integrations.
- Libraries: template defaults + `zod`. Nothing else.
- If a feature is not in the user flow below, do not build it.

IDENTUS PRIMER (assume the reader has never used Identus)
- Hyperledger Identus is open-source self-sovereign identity: an issuer signs a
  W3C Verifiable Credential, a holder keeps it in their wallet, and a verifier
  checks it without phoning the issuer.
- The Cloud Agent is a REST service. Everything below is plain HTTP + JSON.
  Docs: https://identus.io/documentation/develop/ ·
  https://github.com/hyperledger-identus/cloud-agent
- Identifiers are `did:prism:<hex>` — created unpublished, then published so
  others can resolve them. Only a PUBLISHED DID carrying an `assertionMethod`
  key can sign a credential.
- Agents talk to each other over DIDComm; a Mediator relays messages to wallets
  that are not always online (https://github.com/hyperledger-identus/mediator).
- Browser/mobile holders use the TypeScript SDK
  (https://github.com/hyperledger-identus/sdk-ts) — not needed for this build.
- Auth to the agent is the `apikey: <AGENT_API_KEY>` header. Server-side only.

RUNTIME CONVENTIONS (this template — follow exactly)
- Stack: TanStack Start v1 + React 19 + Vite 7 + Tailwind v4 (`src/styles.css`).
  There is NO `src/App.tsx`, no react-router-dom, no `src/pages`.
- Routes live in `src/routes`; the home page is `src/routes/index.tsx` and uses
  `createFileRoute("/")({ component: Page, head: () => ({ meta: [...] }) })`.
  Never edit `src/routeTree.gen.ts`.
- Server logic: `import { createServerFn } from "@tanstack/react-start";` in a
  client-safe module such as `src/lib/identus.functions.ts`. Shape is
  `createServerFn({ method: "POST" }).inputValidator((d) => schema.parse(d)).handler(async ({ data }) => {...})`.
  Call it from the client with `useServerFn(fn)` or directly inside an event handler.
- Routes and components import ONLY from `*.functions.ts` (and plain type modules) —
  never from a `*.server.ts` file. Raw agent logic lives in `*.server.ts`, is
  imported by the `.functions.ts` wrapper, and never reaches the client bundle.
- Read `process.env.AGENT_BASE_URL` / `process.env.AGENT_API_KEY` INSIDE the
  handler — never at module scope (env is injected at call time).
- The server runtime is a Cloudflare-style Worker: use `fetch`, `crypto.randomUUID()`,
  `Buffer`. No child_process, no sharp, no native modules.
- TypeScript runs with `exactOptionalPropertyTypes`: pass optional props as
  `...(x ? { prop: x } : {})`, not `prop: x ?? undefined`.
- Toasts: `sonner` (`import { toast } from "sonner"`), and render `<Toaster />`
  once in `src/routes/__root.tsx`. `@/hooks/use-toast` does NOT exist here.
- Colours come from semantic tokens in `src/styles.css` — no hardcoded
  `text-white` / `bg-black` / `bg-[#hex]` in components.
- Give `src/routes/index.tsx` its own `head()` with a real title and description.

MODE — SIMULATED AGENT (no external service, no secrets)
Do NOT call a real Identus Cloud Agent. Implement `src/lib/identus.server.ts` as a
deterministic in-memory simulator that mirrors the Cloud Agent's REST shapes, and
import it from `src/lib/identus.functions.ts`. Derive ids from a hash of the input
so SSR and hydration agree (no Math.random, no Date.now in render).

FIXTURE SHAPES (return exactly these keys)
- DID:
  { "did": "did:prism:<64 hex>", "longFormDid": "did:prism:<64 hex>:<base64url>",
    "status": "PUBLISHED",
    "document": { "id": "did:prism:<64 hex>",
      "verificationMethod": [{ "id": "#auth-1", "type": "JsonWebKey2020", "curve": "secp256k1" },
                             { "id": "#assert-1", "type": "JsonWebKey2020", "curve": "secp256k1" }],
      "authentication": ["#auth-1"], "assertionMethod": ["#assert-1"], "service": [] } }
- Connection:
  { "connectionId": "<uuid-like>", "state": "InvitationGenerated",
    "invitation": { "id": "<uuid-like>",
      "invitationUrl": "https://my.domain/path?_oob=<base64url json>" },
    "theirDid": null }
  Advance to "ConnectionResponseSent" (and fill `theirDid`) on the second poll.
- Credential record:  (always include a `dob` claim so an age/ZK proof stays possible)
  { "recordId": "<uuid-like>", "protocolState": "OfferSent",
    "claims": { "dob": "1994-05-02", ... },
    "issuingDID": "did:prism:...", "credentialFormat": "JWT",
    "credential": "<header>.<payload>.<signature>" }  // three base64url parts, well-formed but unsigned
  Advance OfferSent -> RequestReceived -> CredentialSent across polls.
- Presentation:
  { "presentationId": "<uuid-like>", "status": "RequestSent", "verified": false, "data": {} }
  Advance RequestSent -> PresentationReceived -> PresentationVerified, then set
  `verified: true` and `data` to the disclosed claims only.

UI REQUIREMENT
Show a small badge reading "Simulated agent — no cryptographic verification" so a
judge knows nothing is being faked silently. Keep every shape identical to the real
API so switching to a live agent is a one-line base-URL change.

MODE GOTCHAS
Never claim cryptographic verification in the copy while in simulated mode — label it clearly. Keep the simulator pure (no timers, no randomness that breaks re-renders) so SSR and hydration agree.

CLOUD AGENT API REFERENCE (everything you need — no other docs required)
All calls: base URL `AGENT_BASE_URL`, headers
`{ "content-type": "application/json", apikey: AGENT_API_KEY }`.

GET  /_system/health                      -> { version } (use for a status pill)

POST /did-registrar/dids
  body { documentTemplate: { publicKeys: [{ id, purpose: "authentication"|"assertionMethod", curve: "secp256k1" }], services: [] } }
  -> { longFormDid, status: "CREATED" }
POST /did-registrar/dids/{didRef}/publications  -> { scheduledOperation: { id, didRef } }
GET  /did-registrar/dids/{didRef}         -> { did, longFormDid, status: "CREATED"|"PUBLICATION_PENDING"|"PUBLISHED" }
GET  /dids/{did}                          -> resolved DID document
  (check `assertionMethod` is non-empty before offering the DID as an issuer)

POST /connections
  body { label, goalCode: "connect", goal }
  -> { connectionId, state: "InvitationGenerated", invitation: { invitationUrl, id } }
POST /connection-invitations
  body { invitation: "<oob base64url string from the invitationUrl ?_oob= param>" }
GET  /connections/{connectionId}
  -> { state } : InvitationGenerated -> ConnectionRequestReceived -> ConnectionResponseSent
GET  /connections                         -> { contents: [...] }

POST /issue-credentials/credential-offers
  body { claims: { ... }, issuingDID, credentialFormat: "JWT", automaticIssuance: true,
         connectionId? | (goalCode + goal for connectionless) }
  -> { recordId, protocolState: "OfferSent", invitation?: { invitationUrl } }
GET  /issue-credentials/records/{recordId}
  -> { protocolState } : OfferSent -> RequestReceived -> CredentialSent, plus `credential` (JWT string)
GET  /issue-credentials/records           -> { contents: [...] }

POST /present-proof/presentations
  body { connectionId, proofs: [], options: { challenge, domain },
         claims: { "<attr>": {} } }   // or anoncredPresentationRequest for ZK predicates
  -> { presentationId, status: "RequestSent" }
GET  /present-proof/presentations/{presentationId}
  -> { status } : RequestSent -> PresentationReceived -> PresentationVerified | PresentationVerificationFailed
  plus `data` (the disclosed claims)

Errors are RFC-7807 JSON: { status, title, detail }. Surface `detail` in the UI —
it names the real problem (unpublished DID, missing connectionId, bad apikey).

SERVER SNIPPET — DID Registrar (create + publish a did:prism)
```ts
// src/lib/identus.functions.ts
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";

export const mintDid = createServerFn({ method: "POST" })
  .inputValidator((d) => z.object({ label: z.string().min(1) }).parse(d))
  .handler(async ({ data }) => {
    const base = process.env.AGENT_BASE_URL!.replace(/\/$/, "");
    const headers = { "content-type": "application/json", apikey: process.env.AGENT_API_KEY! };

    const created = await fetch(`${base}/did-registrar/dids`, {
      method: "POST", headers,
      body: JSON.stringify({
        documentTemplate: {
          publicKeys: [
            { id: "auth-1", purpose: "authentication", curve: "secp256k1" },
            { id: "assert-1", purpose: "assertionMethod", curve: "secp256k1" },
          ],
          services: [],
        },
      }),
    }).then((r) => r.json());

    await fetch(`${base}/did-registrar/dids/${created.longFormDid}/publications`, {
      method: "POST", headers,
    });

    // Poll until the DID reports PUBLISHED, then resolve the document.
    return { did: created.longFormDid, label: data.label };
  });
```

USER FLOW (build exactly this, nothing more)
1. The user types a label (their name, a company, a work).
2. Press "Mint identity" -> server function creates and publishes a did:prism.
3. Show the DID, its status (`CREATED` -> `PUBLISHED`), and the resolved document JSON.
4. A copy button for the DID string, and a short plain-English explainer of what it is.

DESIGN
Editorial, high-contrast, generous whitespace. One accent colour used sparingly.
Show the protocol honestly: render the state machine and the raw JSON envelope in
a collapsible panel so a judge can see the real Identus record. Truncate DIDs and
JWTs in prose (first 12 + last 6 chars) with the full value behind a copy button.
Mobile first — a judge will open this on a phone.

MARKET (for the pitch slide, not the UI)
TAM $1.1B — global video editing software market · SAM $100M — creator wellness and productivity tools · SOM $5M — AI mental health aids for editors

GOTCHAS (universal — apply to every agent call in this build)
- Only a PUBLISHED DID with an `assertionMethod` key can sign a credential
  offer. Create -> publish -> wait for `PUBLISHED` before issuing, or you get a
  cryptic 422/500. Before showing an issuer picker, resolve each DID with
  `GET /dids/{did}` and offer ONLY those whose document exposes `assertionMethod`;
  list the excluded ones with the reason instead of hiding them.
- Put a `dob` claim on every issued credential (ISO date). Without a date-of-birth
  claim an age / zero-knowledge proof is impossible later.
- On a direct Fly deploy the agent serves at the app ROOT — strip any trailing
  `/cloud-agent` from the base URL. On the local docker compose stack, KEEP it.
- Every issuance/presentation endpoint is asynchronous: you POST, then POLL the
  record's `protocolState` (`OfferSent` -> `CredentialSent`,
  `RequestSent` -> `PresentationVerified`). Never assume the POST finished the job.
- Connectionless issuance omits `connectionId` and needs a `goalCode`; if you
  send neither a `connectionId` nor a `goalCode` you get "Missing connectionId".
- The human principal and any AI agent acting for them are DIFFERENT DIDs.
  Compare principal to credential subject, agent to mandate subject — never cross them.
- Never print a raw DID, JWT or hash inline in prose: truncate (first 12 + last 6)
  and keep the full value behind a copy button or in the raw JSON panel.
- First boot of a real agent migrates four databases: allow ~5 minutes and >= 4 GB
  of memory before deciding it is broken.
- Never call the agent from the browser. Every fetch lives inside a
  `createServerFn` handler so `AGENT_API_KEY` stays server-side.

REFERENCE MATERIAL (if you need more than the above)
- Identus docs: https://identus.io/documentation/develop/
- Cloud Agent (OpenAPI + compose examples): https://github.com/hyperledger-identus/cloud-agent
- TypeScript SDK (browser/wallet holders): https://github.com/hyperledger-identus/sdk-ts
- Mediator (DIDComm relay for offline wallets): https://github.com/hyperledger-identus/mediator
- Umbrella repo: https://github.com/hyperledger-identus/hyperledger-identus
- Reference console built with this stack: https://github.com/arunnadarasa/identus
- Full machine-readable brief for your own LLM: https://identusprompts.lovable.app/llms-full.txt

DELIVERABLE
A working single-page demo where the flow above completes end-to-end, plus a
one-paragraph README explaining which Identus primitive is used and how to point
the app at a real Cloud Agent.
Built for the Hyperledger Identus Catalyst — organised by StreetKode Fam during Indian Krump Festival 14.
Appendix · Market

Market sizing.

TAM
$1.1B
global video editing software market
SAM
$100M
creator wellness and productivity tools
SOM
$5M
AI mental health aids for editors

Indicative figures for hackathon pitches — refine with your own research before raising.

See also

Adjacent entries.