Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Running Identus TypeScript SDK snippets in the Sprites.dev sandbox.
Extending the console with new Identus features or pages.
Authoring or validating a Docker Compose stack for the Identus components.
Binding a browser zero-knowledge proof to an issued credential (/app/zk).
Extending the agentic commerce demos (A2A, AP2, UCP, x402) that gate on Identus credentials.
Stack overview
Identus is self-sovereign identity infrastructure: a Cloud Agent (Scala REST service) backed by Postgres and a PRISM node, an optional Mediator for DIDComm, and edge-agent SDKs (TypeScript/Kotlin/Swift). The console supports three agent_connections.mode values:
simulated — in-app mock backed by Supabase tables; no external service. Always healthy.
docker — a local docker compose stack reached at http://localhost:8085/cloud-agent. Localhost only; external DIDComm peers need a tunnel.
fly — a dedicated Fly Machines deployment (Postgres + prism-node + cloud-agent machines). Serves HTTPS at the root, no /cloud-agent prefix.
Fly Machines hosts the full agent stack. Sprites.dev is only for per-user SDK snippet sandboxes and Compose Lab authoring — it cannot run container images or multi-service stacks.
Routes and components import only from *.functions.ts and */types.ts — never directly from *.server.ts. The *.server.ts files read process.env and use the Supabase admin client; importing them into the client bundle leaks secrets and breaks the build. Each *.functions.ts wraps raw logic with createServerFn({ method }) from @tanstack/react-start.
When adding a feature: put raw logic in *.server.ts, expose it via createServerFn in the matching *.functions.ts, and import the function from the route. Read env vars inside the handler, not at module scope.
Environment variables / secrets
FLY_API_TOKEN — Fly Machines + GraphQL API auth. Required for Fly provisioning.
SPRITES_TOKEN — must be the 4-part org-slug/org-id/token-id/token-value token from sprites.dev/account. A raw Fly token will not work and returns 401.
AGENT_BASE_URL, AGENT_API_KEY — placeholders injected into generated SDK snippets for the Sprites sandbox; not read by the main server.
Docker Hub images (pinned — GHCR is not anonymously pullable): docker.io/identus/identus-cloud-agent:1.40.0, docker.io/identus/prism-node:2.5.0, docker.io/postgres:13-alpine (see the Postgres pin invariant — 16 breaks the Flyway migrations).
Database tables
All RLS-scoped by user_id (service_role has full access): profiles, user_roles (+ app_role enum + has_role()), agent_connections, activity_log, saved_dids, credential_records, sim_connections, sim_presentations, credential_schemas, sprite_boxes, sprite_snippets, compose_files.
Hard-won invariants
Strip /cloud-agent from stored Fly URLs — agentBaseUrl() does this for mode === "fly" because direct Fly deploys serve at root (no APISIX gateway).
JVM must prefer IPv6 on Fly's private 6PN network: JAVA_TOOL_OPTIONS=-Djava.net.preferIPv6Addresses=true -Djava.net.preferIPv4Stack=false -XX:MaxRAMPercentage=70 on both prism-node and cloud-agent.
Health-check grace_period is 300s — first boot migrates four databases; a shorter period makes Fly restart mid-migration.
GHCR images require auth; use the public Docker Hub tags with explicit versions, never :latest.
Postgres init creates four databases (pollux, connect, agent, node) to avoid schema-migration collisions.
Identus 1.40 connects as dedicated Postgres roles, not as the superuser. The Flyway migrations authenticate as pollux-application-user, connect-application-user and agent-application-user. If they don't exist the agent exits with Main child exited normally with code: 1 and the real cause (ERROR: role "pollux-application-user" does not exist) sits many frames deep inside a ZIO/cats resource-acquisition trace. The Postgres init script must CREATE ROLE each one (LOGIN + password) and grant it usage on the schema plus all tables/sequences in its database, per database.
Pin Postgres to 13-alpine for the agent. On 16-alpine the bundled Flyway migrations fail with a syntax error near FORMAT (reserved from Postgres 14 on). Symptom looks like a corrupt migration, not a version problem. Do not bump this pin without re-running a first-boot migration on a fresh volume.
Fly private DNS keys off fly_process_group metadata, not the machine name. Machines created without config.metadata.fly_process_group = <name> are simply not in private DNS, and the agent dies with UnknownHostException on the Postgres/prism host. Set it on every machine in the stack.
Derive DEFAULT_WALLET_SEED deterministically (e.g. hash of the app name + a stored salt), never randomly per boot. A rotating seed makes the agent's wallet resource acquisition fail after any restart, with the same opaque ZIO trace.
A crash-looping machine never stays up long enough for the log endpoint to return anything useful; a short that cats/tails the JVM log inside the machine is what actually surfaces the exception. Keep the log window generous (the fatal line often precedes many pages of Hikari shutdown noise) and extract the first / line for the UI.
Colors, gradients, and shadows come from the semantic tokens in src/styles.css. Never hardcode text-white, bg-black, or hex utilities in components.
Long machine identifiers (DIDs, 0x addresses, hashes, JWTs) never sit inline in prose. Render them with shortenId/TruncatedMono from src/components/MonoValue.tsx, or as a values: [{ label, value }] row on a TranscriptStep; the full value stays available in the raw envelope / JSON block.
Header rows that mix text with fixed-size widgets use grid-cols-[minmax(0,1fr)_auto] on mobile promoted to flex at sm:, with min-w-0 on text containers and shrink-0 on icons.
Marketing pages (/, /learn, /nhs, /docs) share MarketingHeader — a session-aware burger menu on mobile. The console shows the active agent mode via ModeBadge, and mobile forms pin their primary action with StickyActionBar.
The project uses exactOptionalPropertyTypes: pass optional props with ...(x ? { prop: x } : {}) rather than prop: x ?? undefined.
Call provisionFlyAgent in src/lib/identus/fly.functions.ts with region, app name, and admin credentials.
The function creates: Fly app → allocates shared IPv4 + v6 → Postgres machine → prism-node machine → cloud-agent machine.
Poll readiness via awaitAgentReady (in src/lib/identus.functions.ts) or the AgentReadinessWatcher component.
Success:readiness_status === "ready" and all four probe checks (system, did-registrar, issuance, connections) pass.
2. Debug an unhealthy Fly agent
Run flyMachineDiagnostics for the app name.
Inspect per-machine state, Fly health-check output, and events (oomKilled, exitCode).
Apply the diagnosis: OOM → redeploy with 4 GB+ memory; crash-loop → verify JAVA_TOOL_OPTIONS and DB init; unauthorized → verify the Docker Hub image tag and token.
Cross-check agentBaseUrl() strips /cloud-agent for mode === "fly".
See failure-modes for the full heuristic.
3. Rotate admin API key on a Fly agent
Call rotateFlyAdminKey — mints a 32-char key, updates Fly machine env (ADMIN_TOKEN, DEFAULT_WALLET_AUTH_API_KEY), restarts the machine, verifies health.
The stored agent_connections row is updated with the new key.
Success: health probe returns 200 with the new key.
4. Add a new Identus console page or flow
Add types to src/lib/identus/types.ts if needed.
Implement raw logic in src/lib/identus/*.server.ts.
Expose via createServerFn in src/lib/identus.functions.ts.
Create route src/routes/app.<feature>.tsx; import only from *.functions.ts / types.ts.
Add a nav link in src/components/AppShell.tsx if it belongs in the console.
5. Use the Sprites sandbox for SDK snippets
Verify SPRITES_TOKEN is the 4-part format.
Call ensureSandbox to create/retrieve the user's box.
SDK snippets read AGENT_BASE_URL and AGENT_API_KEY from process.env — the sandbox injects the active agent's credentials.
Run via the Sprites exec endpoint; parse the 0x03 <exitCode> framing. See sprites-quirks.
6. Author or validate a Docker Compose file
Use DEFAULT_COMPOSE in src/lib/sprites/compose.server.ts as the canonical Identus stack template (agent + prism-node + postgres with healthchecks, named network, restart: unless-stopped).
Validate with the Python validator: flags insecure passwords, missing healthchecks, weak dependency conditions.
Save to compose_files table. Do not attempt to run containers inside Sprites — it is for authoring/linting only.
7. Issue a credential without a DIDComm connection
Call listIssuerDids — it resolves each DID and returns only those whose document exposes an assertionMethod key, with excluded DIDs and reasons for the UI.
Choose target = "connectionless" when no established connection exists. issueCredential omits connectionId, sets goalCode/JWT format, and stores the returned invitation_url on credential_records.
Include a dob claim if the credential should be usable by the ZK age proof.
Success: the offer record has an invitation URL and no "Missing connectionId" 400.
See credential-issuance.
8. Bind a zero-knowledge proof to an issued credential
listZkCredentials returns credentials that have a signed JWT, plus any commitment from an earlier ZK presentation.
extractBirthYear (in src/lib/zk-claims.ts) resolves the birth year; credentials without one cannot prove age and must be shown as such, never silently offered.
The browser derives the binding with credentialBinding(jwt) (SHA-256 → two 128-bit field limbs) and proves with Noir + UltraHonk. The JWT never leaves the page.
recordZkPresentation stores the commitment, public inputs, and timing into sim_presentations and writes an activity entry.
Success:verified === true and a presentation.zk_verified activity row.
For the prover mechanics see the noir-zk-browser skill and zk-integration.
9. Repair a Fly agent's DIDComm endpoint
Symptom: invitations produced by the agent carry a placeholder or port-less host, so no remote wallet can answer them.
flyMachineDiagnostics reports the machine's DIDCOMM_SERVICE_URL and whether internal port 8090 is published.
Run the repair function (repairAgentEndpoints via fly.functions.ts) — it adds the 8090 service, rewrites DIDCOMM_SERVICE_URL to https://<app>.fly.dev:8090, and restarts the machine.
Success: a freshly created invitation decodes to a serviceEndpoint on the real app host, and re-running diagnostics shows no endpoint warning.
10. Ship a breaking change to sandbox snippets
Edit the template in src/lib/sprites/snippets.ts (or delegation-snippets.ts).
Bump that entry's version string — STARTER_VERSIONS is derived from it and drives stale-copy detection in sandbox.functions.ts.
The Sandbox UI flags saved snippets whose template_version is older and offers "Reset starter snippets".
Any REST snippet must keep REST_PRELUDE at the top so simulated mode explains itself instead of throwing Invalid URL.
Success: a user with an old saved copy sees the stale badge and gets working code after resetting.
11. Repair the agent database (missing Postgres roles)
Symptom: prism-node is healthy, the cloud agent restarts forever, and the boot log ends in
Main child exited normally with code: 1. Deeper in the trace:
ERROR: role "pollux-application-user" does not exist.
Read the boot log with the machine exec path (not the log stream) to confirm the role error
rather than guessing at OOM or DNS.
Run the "Fix agent DB" repair: destroy and recreate the Postgres machine with the role-aware
init script (four databases plus the three *-application-user roles and their grants),
then restart the cloud-agent machine.
If the agent instead fails with a syntax error near FORMAT, the Postgres image drifted off
13-alpine — repin and recreate the machine.
Success:probeAgent passes all four checks and the agent no longer restarts.
Read boot logs through the machine exec API, not the log stream.
exec
ERROR
Caused by
Recreating the Postgres machine is the only fix for missing roles. Env edits and restarts cannot retro-create a role inside an existing volume — expose an explicit "Fix agent DB" repair action instead of telling users to redeploy the whole stack.
Unique indexes on stack/connection tables must be scoped by kind. A single unique (user_id) index stops an Identus record and a Midnight record from coexisting for the same user; scope it (user_id, kind) or provisioning the second stack silently overwrites the first.
Every Fly status function must degrade gracefully when FLY_API_TOKEN is absent. Return an unconfigured state the UI can render; throwing inside a loader/status query blanks the whole page and hides the actual "add the secret" instruction.
Agent memory default is 4 GB; lower values get OOM-killed during first-boot migration.
A Fly app is unreachable until a public IP is allocated (shared v4 + v6); allocate during provisioning and expose a repair action for older apps.
Cap any single Fly readiness poll at 60s. Longer timeout values are rejected by the Machines API with a 400.
Fly resources can vanish outside the console. Treat 404 from destroyFlyApp/machine reads as "already gone" and mark the stored connection orphaned instead of erroring.
Only a publisheddid:prism carrying an assertionMethod key can sign a credential offer — see credential-issuance.
DIDComm invitations must advertise a reachable host. The cloud-agent machine publishes port 8090 (http+tls) and DIDCOMM_SERVICE_URL must be https://<app>.fly.dev:8090 — a placeholder host makes every invitation undeliverable. Fix existing apps with repairAgentEndpoints (exposed as the "Repair DIDComm endpoint" action), not a redeploy.
Sandbox starter snippets are versioned (STARTER_SNIPPETS[].version in src/lib/sprites/snippets.ts). A breaking SDK/API change requires bumping that version so saved copies are flagged stale and offered a reset; silently editing the template leaves existing users on broken code.
REST snippets begin with REST_PRELUDE, which fails fast with a plain-English "configure a real agent" message. Without it an empty AGENT_BASE_URL surfaces as a bare TypeError: Invalid URL in simulated mode.
In the delegation / x402 gate the human principal (credential subject) and the AI agent (mandate subject) are different DIDs. Compare principal↔credential-subject and agent↔mandate-subject; cross-comparing them is the classic false "credential mismatch" rejection.
The ZK prover must report per-phase progress with per-phase timeouts and an explicit retry path. A stalled WASM/module download otherwise looks like a frozen page with no way out.
The ZK age proof needs a date-of-birth claim on the credential (dob, dateOfBirth, birthDate, birthYear, snake_case variants). Issuance templates include dob so the happy path stays provable.