Distribution Specification: Packaging, Registry, and Installation

Status: Draft

Overview

This document covers how applets are:

mindmap
  root((Distribution))
    Packaged
      Build & bundle
      Checksums
      Signatures
    Published
      Registry upload
      Validation
      Security scan
    Discovered
      Browse catalog
      Search & filter
      Reviews
    Installed
      Download
      Permissions
      Migrations
    Updated
      Version check
      Rollback
    Uninstalled
      Data handling
      Cleanup

Package Format

Applet Package Structure

graph TB
    subgraph "my-applet-1.0.0.zip"
        MANIFEST[manifest.yaml]

        subgraph "dist/"
            BACKEND[backend/server.js]
            FRONTEND[frontend/pages/*.js]
            WIDGETS[frontend/widgets/*.js]
        end

        subgraph "assets/"
            ICON[icon.svg]
            SCREENSHOTS[screenshots/]
        end

        subgraph "locales/"
            EN[en.json]
            RU[ru.json]
            UZ[uz.json]
        end

        subgraph "migrations/"
            MIG1[001_initial.sql]
            MIG2[002_add_column.sql]
        end

        CHECKSUMS[checksums.json]
        SIG[signature.sig]
    end

    style MANIFEST fill:#f59e0b,stroke:#d97706,color:#fff
    style CHECKSUMS fill:#10b981,stroke:#047857,color:#fff
    style SIG fill:#8b5cf6,stroke:#5b21b6,color:#fff

Manifest Requirements

# manifest.yaml - Required fields
manifestVersion: "1.0"
id: "ai-website-chat"           # Unique identifier
version: "1.0.0"                # Semantic version
name:
  en: "AI Website Chat"
runtime:
  engine: "bun"
  entrypoint: "dist/backend/server.js"

Build Process

flowchart LR
    subgraph "Source"
        SRC[src/]
        TS[TypeScript]
        TSX[React TSX]
    end

    subgraph "Build"
        BUILD[bun build]
        MINIFY[Minify]
        TREE[Tree-shake]
    end

    subgraph "Output"
        DIST[dist/]
        PKG[package.zip]
    end

    SRC --> BUILD
    TS --> BUILD
    TSX --> BUILD
    BUILD --> MINIFY
    MINIFY --> TREE
    TREE --> DIST
    DIST --> PKG

    style BUILD fill:#3b82f6,stroke:#1e40af,color:#fff
    style PKG fill:#10b981,stroke:#047857,color:#fff

Build Pipeline:

// build.config.ts
import { defineConfig } from '@iota/applet-cli';

export default defineConfig({
  backend: {
    entrypoint: 'src/backend/server.ts',
    target: 'bun',
    minify: true,
  },
  frontend: {
    framework: 'react',
    entrypoints: {
      pages: 'src/frontend/pages/**/*.tsx',
      widgets: 'src/frontend/widgets/**/*.tsx',
    },
    splitting: true,
    minify: true,
  },
  locales: {
    source: 'src/locales',
    languages: ['en', 'ru', 'uz'],
  },
});

Checksums & Integrity

flowchart TB
    PKG[Package Files] --> HASH[SHA-256 Hash]
    HASH --> CHECKSUMS[checksums.json]

    subgraph "Verification"
        CHECKSUMS --> COMPARE{Compare Hashes}
        COMPARE -->|Match| PASS[✓ Integrity Verified]
        COMPARE -->|Mismatch| FAIL[✗ Corrupted/Tampered]
    end

    style PASS fill:#10b981,stroke:#047857,color:#fff
    style FAIL fill:#ef4444,stroke:#b91c1c,color:#fff
// checksums.json
{
  "algorithm": "sha256",
  "files": {
    "manifest.yaml": "a1b2c3d4...",
    "dist/backend/server.js": "e5f6g7h8...",
    "dist/frontend/pages/config.js": "i9j0k1l2...",
    "locales/en.json": "m3n4o5p6..."
  }
}

Verification:

func verifyPackageIntegrity(pkg *Package) error {
    checksums, err := parseChecksums(pkg.GetFile("checksums.json"))
    if err != nil {
        return err
    }

    for file, expectedHash := range checksums.Files {
        content := pkg.GetFile(file)
        actualHash := sha256.Sum256(content)

        if hex.EncodeToString(actualHash[:]) != expectedHash {
            return ErrChecksumMismatch{File: file}
        }
    }

    return nil
}

Registry Architecture

Registry Types

graph TB
    subgraph "Registry Hierarchy"
        OFFICIAL[Official Registry<br/>registry.iota.uz]
        PRIVATE[Private Registry<br/>your-company.registry.io]
        LOCAL[Local Installation<br/>Direct .zip upload]
    end

    OFFICIAL -->|Curated & Verified| SDK1[SDK Instance]
    PRIVATE -->|Organization-specific| SDK2[SDK Instance]
    LOCAL -->|Development/Air-gapped| SDK3[SDK Instance]

    style OFFICIAL fill:#3b82f6,stroke:#1e40af,color:#fff
    style PRIVATE fill:#10b981,stroke:#047857,color:#fff
    style LOCAL fill:#f59e0b,stroke:#d97706,color:#fff
Registry Type Description Use Case
Official Curated, verified, signed Public applets
Private Organization-specific Internal tools
Local Direct upload Development, air-gapped

Registry API

# OpenAPI specification for registry
openapi: 3.0.0
info:
  title: IOTA Applet Registry
  version: 1.0.0

paths:
  /api/v1/applets:
    get:
      summary: List applets
      parameters:
        - name: q
          in: query
          description: Search query
        - name: category
          in: query
        - name: page
          in: query
        - name: limit
          in: query

  /api/v1/applets/{id}:
    get:
      summary: Get applet details

  /api/v1/applets/{id}/versions:
    get:
      summary: List versions
    post:
      summary: Publish new version

  /api/v1/applets/{id}/versions/{version}/download:
    get:
      summary: Download package

Publishing Flow

sequenceDiagram
    participant Dev as Developer
    participant CLI as iota-applet CLI
    participant Reg as Registry
    participant Review as Review System

    Dev->>CLI: iota-applet build --prod
    CLI-->>Dev: package.zip created

    Dev->>CLI: iota-applet publish
    CLI->>Reg: Authenticate
    CLI->>Reg: Upload package.zip

    Reg->>Reg: Verify checksums
    Reg->>Reg: Validate manifest
    Reg->>Reg: Check version conflicts
    Reg->>Reg: Scan for vulnerabilities

    alt Official Registry
        Reg->>Review: Queue for review
        Review->>Review: Security review
        Review->>Review: Code quality check
        Review-->>Reg: Approved
    end

    Reg-->>CLI: Published successfully
    CLI-->>Dev: Applet available for installation

CLI Publishing:

# Login to registry
iota-applet login

# Publish to official registry
iota-applet publish

# Publish to private registry
iota-applet publish --registry https://private.registry.io

# Publish with signing
iota-applet publish --sign --key ~/.iota/signing-key.pem

Installation Flow

Discovery UI

graph TB
    subgraph "Admin Panel > Applets > Browse"
        SEARCH[Search applets...]
        FILTERS[Category / Sort]

        subgraph "Results"
            A1[🤖 AI Chat<br/>★★★★☆ 4.5<br/>1.2K installs]
            A2[📊 Analytics<br/>★★★★★ 5.0<br/>5.6K installs]
            A3[📦 Inventory<br/>★★★☆☆ 3.2<br/>890 installs]
        end

        A1 --> I1[Install]
        A2 --> I2[Install]
        A3 --> I3[Install]
    end

    style A1 fill:#3b82f6,stroke:#1e40af,color:#fff
    style A2 fill:#10b981,stroke:#047857,color:#fff
    style A3 fill:#f59e0b,stroke:#d97706,color:#fff

Installation Steps

flowchart TB
    START[Admin clicks Install] --> DOWNLOAD[1. Download Package]
    DOWNLOAD --> VERIFY[Verify checksums]
    VERIFY --> EXTRACT[Extract to temp]

    EXTRACT --> REVIEW[2. Permission Review]
    REVIEW --> APPROVE{Admin Approves?}
    APPROVE -->|No| CANCEL[Cancel]
    APPROVE -->|Yes| CONFIG[3. Configuration]

    CONFIG --> SECRETS[Enter required secrets]
    SECRETS --> SETTINGS[Configure settings]

    SETTINGS --> MIGRATE[4. Database Migration]
    MIGRATE --> CREATE_TABLES[Create applet tables]
    CREATE_TABLES --> RUN_MIGRATIONS[Run initial migrations]

    RUN_MIGRATIONS --> RUNTIME[5. Runtime Init]
    RUNTIME --> START_BUN[Start Bun process]
    START_BUN --> REGISTER_HTTP[Register HTTP handlers]
    REGISTER_HTTP --> SUBSCRIBE[Subscribe to events]

    SUBSCRIBE --> UI[6. UI Registration]
    UI --> NAV[Add navigation items]
    NAV --> ROUTES[Register routes]
    ROUTES --> WIDGETS[Initialize widgets]

    WIDGETS --> HOOK[7. Run onInstall hook]
    HOOK --> COMPLETE[8. Complete ✓]

    style START fill:#3b82f6,stroke:#1e40af,color:#fff
    style COMPLETE fill:#10b981,stroke:#047857,color:#fff
    style CANCEL fill:#ef4444,stroke:#b91c1c,color:#fff

Installation API

type InstallationManager struct {
    registry      RegistryClient
    storage       PackageStorage
    migrator      MigrationRunner
    runtimeMgr    RuntimeManager
    permissionMgr PermissionManager
}

func (m *InstallationManager) Install(ctx context.Context, req InstallRequest) error {
    // 1. Download package
    pkg, err := m.registry.Download(req.AppletID, req.Version)
    if err != nil {
        return fmt.Errorf("download failed: %w", err)
    }

    // 2. Verify integrity
    if err := verifyPackageIntegrity(pkg); err != nil {
        return fmt.Errorf("integrity check failed: %w", err)
    }

    // 3. Parse manifest
    manifest, err := parseManifest(pkg.GetFile("manifest.yaml"))
    if err != nil {
        return fmt.Errorf("invalid manifest: %w", err)
    }

    // 4. Check permissions are approved
    if !req.PermissionsApproved {
        return ErrPermissionsNotApproved
    }

    // 5. Store package
    if err := m.storage.Store(manifest.ID, manifest.Version, pkg); err != nil {
        return fmt.Errorf("storage failed: %w", err)
    }

    // 6. Run migrations
    if err := m.migrator.InstallApplet(manifest); err != nil {
        m.storage.Remove(manifest.ID, manifest.Version)
        return fmt.Errorf("migration failed: %w", err)
    }

    // 7. Initialize runtime
    if err := m.runtimeMgr.InitializeApplet(manifest); err != nil {
        m.migrator.RollbackApplet(manifest)
        m.storage.Remove(manifest.ID, manifest.Version)
        return fmt.Errorf("runtime init failed: %w", err)
    }

    // 8. Register permissions
    if err := m.permissionMgr.RegisterAppletPermissions(manifest); err != nil {
        return fmt.Errorf("permission registration failed: %w", err)
    }

    // 9. Run onInstall hook
    if manifest.Lifecycle.OnInstall != "" {
        if err := m.runtimeMgr.Execute(ctx, manifest.ID, manifest.Lifecycle.OnInstall); err != nil {
            log.Warn("onInstall hook failed", "error", err)
        }
    }

    return nil
}

Update Flow

Update Detection

sequenceDiagram
    participant Checker as Update Checker
    participant Storage as Package Storage
    participant Registry as Registry
    participant Admin as Admin UI

    loop Periodic Check
        Checker->>Storage: List installed applets
        Storage-->>Checker: [applet_a@1.0, applet_b@2.1]

        Checker->>Registry: Get latest versions
        Registry-->>Checker: [applet_a@1.2, applet_b@2.1]

        Checker->>Checker: Compare versions
        Checker-->>Admin: Updates available: applet_a
    end

Update Process

flowchart TB
    START[Update Available] --> DOWNLOAD[1. Download new version]
    DOWNLOAD --> COMPARE[2. Compare permissions]

    COMPARE --> NEW{New permissions?}
    NEW -->|Yes| APPROVAL[Require approval]
    NEW -->|No| CONTINUE[Continue]
    APPROVAL --> CONTINUE

    CONTINUE --> OLD_HOOK[3. Run onUpdate OLD version]
    OLD_HOOK --> STOP[4. Stop running instance]
    STOP --> GRACEFUL[Graceful shutdown]

    GRACEFUL --> MIGRATE[5. Run migrations]
    MIGRATE --> SCHEMA[Apply schema changes]

    SCHEMA --> REPLACE[6. Replace package files]
    REPLACE --> ATOMIC[Atomic swap]

    ATOMIC --> START_NEW[7. Start new version]
    START_NEW --> INIT[Initialize runtime]

    INIT --> NEW_HOOK[8. Run onUpdate NEW version]
    NEW_HOOK --> COMPLETE[Update Complete ✓]

    style START fill:#f59e0b,stroke:#d97706,color:#fff
    style COMPLETE fill:#10b981,stroke:#047857,color:#fff

Rollback Support:

func (m *InstallationManager) Update(ctx context.Context, appletID string, newVersion string) error {
    // Get current version for rollback
    current, err := m.storage.GetInstalled(appletID)
    if err != nil {
        return err
    }

    // Create rollback point
    rollback := m.createRollbackPoint(current)

    // Attempt update
    err = m.performUpdate(ctx, appletID, newVersion)
    if err != nil {
        // Rollback on failure
        if rollbackErr := m.rollback(rollback); rollbackErr != nil {
            return fmt.Errorf("update failed: %w, rollback failed: %v", err, rollbackErr)
        }
        return fmt.Errorf("update failed, rolled back: %w", err)
    }

    // Clean up rollback point
    m.cleanRollbackPoint(rollback)

    return nil
}

Uninstallation

Uninstall Flow

flowchart TB
    START[Admin clicks Uninstall] --> CONFIRM[1. Confirmation Dialog]

    CONFIRM --> CHOICE{Data handling?}
    CHOICE -->|Keep 30 days| SOFT[Soft delete]
    CHOICE -->|Export & delete| EXPORT[Export to file]
    CHOICE -->|Delete now| HARD[Hard delete]

    SOFT --> DISABLE[2. Run onDisable hook]
    EXPORT --> DISABLE
    HARD --> DISABLE

    DISABLE --> STOP[3. Stop runtime]
    STOP --> CANCEL_TASKS[Cancel scheduled tasks]
    CANCEL_TASKS --> UNSUB[Unsubscribe events]
    UNSUB --> STOP_BUN[Stop Bun process]

    STOP_BUN --> UNREG[4. Unregister UI]
    UNREG --> REMOVE_NAV[Remove navigation]
    REMOVE_NAV --> UNREG_ROUTES[Unregister routes]
    UNREG_ROUTES --> REMOVE_WIDGETS[Remove widgets]

    REMOVE_WIDGETS --> HOOK[5. Run onUninstall hook]

    HOOK --> DATA[6. Handle data]
    DATA --> CLEAN[7. Remove package files]
    CLEAN --> COMPLETE[8. Complete ✓]

    style START fill:#ef4444,stroke:#b91c1c,color:#fff
    style COMPLETE fill:#10b981,stroke:#047857,color:#fff

Data Handling Options

Option Description Use Case
Soft Delete Rename tables with _deleted_ prefix, keep 30 days Can reinstall
Export & Delete Export to JSON/CSV, then drop Data backup
Hard Delete DROP TABLE IF EXISTS immediately Clean removal

Multi-Tenant Considerations

Tenant-Specific Installation

graph TB
    subgraph "Global Installation"
        APPLET[AI Chat Applet v1.0]
    end

    subgraph "Tenant Configurations"
        T1[Tenant A<br/>Enabled, GPT-4]
        T2[Tenant B<br/>Enabled, Claude-3]
        T3[Tenant C<br/>Disabled]
    end

    APPLET --> T1
    APPLET --> T2
    APPLET --> T3

    style APPLET fill:#3b82f6,stroke:#1e40af,color:#fff
    style T1 fill:#10b981,stroke:#047857,color:#fff
    style T2 fill:#10b981,stroke:#047857,color:#fff
    style T3 fill:#9ca3af,stroke:#6b7280,color:#fff
type TenantAppletInstallation struct {
    AppletID   string
    TenantID   uuid.UUID
    Version    string
    Enabled    bool
    Config     JSONB
    Secrets    map[string]string  // Encrypted
    InstalledAt time.Time
    InstalledBy uint
}

Security Considerations

Package Signing

flowchart LR
    subgraph "Signing"
        PKG[Package] --> HASH[SHA-256 Hash]
        HASH --> SIGN[Sign with Private Key]
        SIGN --> SIG[signature.sig]
    end

    subgraph "Verification"
        SIG --> VERIFY[Verify with Public Key]
        PKG --> VERIFY
        VERIFY --> RESULT{Valid?}
        RESULT -->|Yes| TRUSTED[✓ Trusted]
        RESULT -->|No| UNTRUSTED[✗ Untrusted]
    end

    style TRUSTED fill:#10b981,stroke:#047857,color:#fff
    style UNTRUSTED fill:#ef4444,stroke:#b91c1c,color:#fff

Vulnerability Scanning

type SecurityScanner struct {
    vulnerabilityDB VulnerabilityDatabase
    staticAnalyzer  StaticAnalyzer
}

func (s *SecurityScanner) Scan(pkg *Package) (*ScanReport, error) {
    report := &ScanReport{}

    // Check for known vulnerabilities in dependencies
    deps := extractDependencies(pkg)
    for _, dep := range deps {
        vulns := s.vulnerabilityDB.Check(dep.Name, dep.Version)
        report.Vulnerabilities = append(report.Vulnerabilities, vulns...)
    }

    // Static analysis of code
    findings := s.staticAnalyzer.Analyze(pkg.GetFile("dist/backend/server.js"))
    report.StaticAnalysis = findings

    // Check permissions for suspicious patterns
    manifest := parseManifest(pkg.GetFile("manifest.yaml"))
    if hasSuspiciousPermissions(manifest) {
        report.Warnings = append(report.Warnings, "Suspicious permission combination")
    }

    return report, nil
}

Installation Restrictions

type InstallationPolicy struct {
    AllowUnsigned      bool
    RequiredSigners    []string  // Required signer IDs
    BlockedApplets     []string  // Blocked applet IDs
    AllowedRegistries  []string  // Allowed registry URLs
    RequireReview      bool      // Manual review required
}

Next Steps


Back to top

IOTA SDK - Multi-tenant Business Management Platform