Documentation ziprack/1SDK 0.3.0

Building an Applet

The Manifest

On this page

Introduction

manifest.json sits at the root of the zip and is your applet's entire declaration of intent. It names the applet, states which capabilities it calls, and describes the shape of everything the Rack will need to validate, store, index, report on and render.

Declaration is mandatory, not advisory. A call to a capability the manifest did not declare is refused at runtime with undeclared_capability. The manifest is also what a reviewer reads when a new version widens what your applet can reach; see capability-diff review.

Core Fields

Every manifest starts with the same handful of core fields:

JSON
{
    "schema": "ziprack/1",
    "slug": "my-applet",
    "name": "My Applet",
    "version": "1.0.0",
    "entry": "index.html",
    "requires": { "capabilities": [], "profile": [] },
    "media": []
}
Field Rules
schema Required. Exactly ziprack/1.
slug Required. ^[a-z0-9]+(-[a-z0-9]+)*$, max 100. Identifies the applet on the Rack. Immutable.
name Required, max 255. Shown to participants and administrators.
version Required. MAJOR.MINOR.PATCH with optional -prerelease, max 50. Unique per applet.
entry Optional, default index.html. Relative, no .., ends in .html.
requires Required, even if empty. See The Requires Block.
media Optional. See Media.
roles Optional. The applet's own roles. See Roles.

Everything else at the top level belongs to a capability. A section whose owning capability is not declared is a validation error, and so is a section that no capability the Rack implements owns.

The Requires Block

The requires block lists the capabilities your applet calls and the profile attributes it would like released:

JSON
"requires": {
    "capabilities": ["auth@1", "attempt@1", "activity@1", "ai@1"],
    "profile": ["display_name", "business_unit", "locale"]
}
  • Each capability reference matches ^[a-z][a-z0-9_]*@\d+$. Declaring a capability twice is an error, and so is declaring one the Rack does not implement. The error names what it does implement.
  • profile lists the attribute keys you want released, and requires auth@1. Keys match ^[a-z][a-z0-9_]*$. A key the deployment does not release is simply absent at launch, so degrade gracefully.
  • Declaring attempt@1 has a side effect: it pins every participant to the version they started on. See Versioning.

Roles

You may declare your applet's own roles in a roles block:

JSON
"roles": [
    { "name": "dungeon-master", "label": "Dungeon Master", "description": "Runs the session." },
    { "name": "spectator", "grant": "guests" }
]

roles is a core key, so it needs no capability. Your applet names its roles; the Rack decides who holds them, and every launch tells your applet which ones the participant holds. See Roles for how they resolve.

Field Rules
name Required. Lowercase words joined by hyphens, ^[a-z][a-z0-9]*(-[a-z0-9]+)*$, max 40, unique. Not one of the built-in roles: rack-administrator, applet-author, member, guest.
label Optional, max 60. Shown to whoever assigns the role. Defaults to name.
description Optional, max 255.
grant Optional. Who holds it before anyone is assigned: everyone, members, guests or assigned (the default, meaning nobody).

A manifest may declare at most 20 roles.

Capability Sections

Each capability owns one top-level section of the manifest:

Section Capability Shape
segments[] attempt@1 {key, label, values[{key,label}]} — dimensions reports filter by
activities[] activity@1 {id, type, label, scale?, segment?, sequence?} — required, at least one
reporting.metrics[] activity@1 (+ attempt@1) {key, label, pre, post} — pairs two declared activities
collections[] db@1 {name, scope, schema, indexed?, quota?, permissions?, seed?}
uploads[] storage@1 {name, scope, mime[], max_bytes?, max_files?}
prompts[] ai@1 {id, mode, template, vars?, output_schema?, budget?, model?}
broadcast broadcast@1 {channels?, facilitator_events?}

Each section is specified in full on its capability's page. Two cross-cutting rules are worth stating here.

Scopes are declarations, not runtime choices. db@1 collections and storage@1 uploads each declare a scope: attempt, user, room or applet. An attempt scope requires attempt@1, while a room scope requires broadcast@1. The scope answers, at declaration time, what happens to the data when an attempt completes.

Quotas and budgets are ceilings you may lower, not raise. Each Rack sets hard maxima. A manifest asking for more fails validation, and a manifest asking for nothing gets the deployment's defaults.

A Complete Example

Let's take a look at an applet exercising every capability at once. Of course, most applets need only three or four:

JSON
{
    "schema": "ziprack/1",
    "slug": "team-summit",
    "name": "Team Summit",
    "version": "2.1.0",
    "entry": "index.html",

    "requires": {
        "capabilities": ["auth@1", "attempt@1", "activity@1", "db@1", "storage@1", "ai@1", "broadcast@1"],
        "profile": ["display_name", "business_unit", "locale"]
    },

    "media": [
        { "id": "intro.welcome", "provider": "vimeo", "key": "912345678" },
        { "id": "debrief.clip", "provider": "youtube", "key": "dQw4w9WgXcQ" }
    ],

    "roles": [
        { "name": "judge", "label": "Judge", "description": "Scores the final pitches." }
    ],

    "segments": [
        {
            "key": "track",
            "label": "Track",
            "values": [
                { "key": "rd", "label": "R&D" },
                { "key": "commercial", "label": "Commercial" }
            ]
        }
    ],

    "activities": [
        { "id": "intro.confidence", "type": "rating", "label": "How confident do you feel about the new operating model?", "scale": { "min": 1, "max": 6 }, "sequence": 1 },
        { "id": "team.pitch", "type": "text", "label": "In one sentence, what is your team's pitch?", "segment": "track", "sequence": 2 },
        { "id": "outro.confidence", "type": "rating", "label": "How confident do you feel about the new operating model?", "scale": { "min": 1, "max": 6 }, "sequence": 3 }
    ],

    "reporting": {
        "metrics": [
            { "key": "confidence", "label": "Confidence", "pre": "intro.confidence", "post": "outro.confidence" }
        ]
    },

    "collections": [
        {
            "name": "ideas",
            "scope": "room",
            "schema": {
                "type": "object",
                "properties": {
                    "title":  { "type": "string", "maxLength": 120 },
                    "body":   { "type": "string", "maxLength": 2000 },
                    "status": { "type": "string", "enum": ["draft", "posted", "picked"] },
                    "votes":  { "type": "integer", "minimum": 0 }
                },
                "required": ["title", "status"]
            },
            "indexed": ["status", "votes"],
            "quota": { "max_records": 500, "max_bytes": 16384 },
            "permissions": { "update": "owner", "delete": "facilitator" }
        }
    ],

    "uploads": [
        { "name": "artifacts", "scope": "room", "mime": ["image/png", "image/jpeg"], "max_bytes": 5242880, "max_files": 10 }
    ],

    "prompts": [
        {
            "id": "score_pitch",
            "mode": "analysis",
            "template": "Score this team pitch for clarity and specificity.\n\nPitch: {{pitch}}\nTrack: {{track}}",
            "vars": {
                "type": "object",
                "properties": { "pitch": { "type": "string" }, "track": { "type": "string" } },
                "required": ["pitch"]
            },
            "output_schema": {
                "type": "object",
                "properties": {
                    "clarity":  { "type": "integer", "minimum": 1, "maximum": 5 },
                    "feedback": { "type": "string" }
                },
                "required": ["clarity", "feedback"]
            },
            "budget": { "max_calls_per_attempt": 3, "max_output_tokens": 400 }
        }
    ],

    "broadcast": {
        "channels": ["main", "board"],
        "facilitator_events": ["advance", "lock", "reveal"]
    }
}

Validation Errors

When you upload, every validation error is reported at once, keyed by manifest path, in a single message.

These are the checks that most often catch people out:

Error Cause
Unknown manifest section [x] No capability the Rack implements owns that top-level key.
Manifest section [collections] requires the [db] capability The section is there; the declaration is not.
This Rack does not implement [db@1]. Implemented: … Wrong capability or wrong version for this deployment.
Activity references unknown segment [track] activities[].segment must name a declared segment key.
Metric references unknown activity [outro.x] pre and post must be declared activity ids.
reporting.metrics needs the attempt capability Metrics are computed over completed attempts.
A room-scoped collection requires the [broadcast] capability Scope implies capability.
Indexed path [votes] must be a string, integer, number or boolean property declared in the schema You can only index what you declared, and only scalars.
Only applet-scoped collections can be seeded A seed is shared reference data; see Seeding.
Role names are lowercase words joined by hyphens, like "dungeon-master". roles[].name has capitals, underscores or spaces.
Role [guest] is one every Rack provides …; it cannot be declared. rack-administrator, applet-author, member and guest are built in.
Duplicate role [judge]. Role names are unique.
A role's grant is one of everyone, members, guests or assigned. Any other grant.
Placeholder {{pitch}} is not declared in vars.properties Prompt templates and their vars schema must agree.
Analysis prompts need an output_schema of type object mode: "analysis" is structured output by definition.
The $room channel is reserved for the Rack Pick another channel name.