{
  "title": "Collab platform executable module contract",
  "protocol_version": 1,
  "service_host": {
    "guide": "/docs/service",
    "cli": "platform.exe service install|start|stop|restart|status|uninstall",
    "default_service_name": "CollabPlatform",
    "account": "LocalService with per-service SID directory grants",
    "module_supervision": "At most 3 automatic restart attempts; intentional stop is respected",
    "module_status_fields": [
      "process_id",
      "restart_count"
    ],
    "logs": "logs/platform.log; 5 MiB threshold and 3 backups"
  },
  "transport": "HashiCorp go-plugin net/rpc with MuxBroker reverse calls",
  "guide": "/docs/module-guide.txt",
  "sdk": "/devkit.zip",
  "go_import": "collab.local/platform/modulekit",
  "go_business_interface": [
    "Describe() Descriptor",
    "Execute(Request, Host) (Response, error)"
  ],
  "entrypoint": "modulekit.Serve(&BusinessImplementation{})",
  "host_services": [
    "identity.get",
    "records.list",
    "records.create"
  ],
  "field_components": [
    "text",
    "textarea",
    "select"
  ],
  "actions": [
    "create",
    "list"
  ],
  "limits": {
    "request_body_bytes": 65536,
    "record_bytes": 32768,
    "rpc_wait_seconds": 10,
    "multi_user_auth": true,
    "sandbox": false,
    "custom_frontend": false
  },
  "http_endpoints": [
    {
      "method": "GET",
      "path": "/",
      "description": "Set local browser session cookie"
    },
    {
      "method": "GET",
      "path": "/api/bootstrap",
      "description": "Get session CSRF token, authenticated actor with permissions, and requires_setup"
    },
    {
      "method": "GET",
      "path": "/api/modules",
      "description": "Module discovery, status, descriptor and grants"
    },
    {
      "method": "POST",
      "path": "/api/modules/register",
      "body": {
        "id": "workorder",
        "capabilities": [
          "identity.get",
          "records.list",
          "records.create"
        ]
      }
    },
    {
      "method": "POST",
      "path": "/api/modules/{id}/start",
      "body": {}
    },
    {
      "method": "POST",
      "path": "/api/modules/{id}/stop",
      "body": {}
    },
    {
      "method": "POST",
      "path": "/api/modules/{id}/unregister",
      "body": {}
    },
    {
      "method": "POST",
      "path": "/api/actions/{id}/create",
      "body": {
        "request_id": "unique-request-id-min-16",
        "payload": {}
      }
    },
    {
      "method": "POST",
      "path": "/api/actions/{id}/list",
      "body": {
        "request_id": "unique-request-id-min-16",
        "payload": {}
      }
    },
    {
      "method": "GET",
      "path": "/api/audit"
    },
    {
      "method": "POST",
      "path": "/api/auth/setup",
      "body": {
        "name": "管理员",
        "password": "<at-least-12-characters>"
      }
    },
    {
      "method": "POST",
      "path": "/api/auth/login",
      "body": {
        "username": "local-admin",
        "password": "<password>"
      }
    },
    {
      "method": "POST",
      "path": "/api/auth/logout",
      "body": {}
    },
    {
      "method": "POST",
      "path": "/api/auth/password",
      "body": {
        "current_password": "<current>",
        "password": "<new>"
      }
    },
    {
      "method": "GET",
      "path": "/api/admin",
      "description": "Role-filtered departments, users, roles, permission catalog and configuration revision"
    },
    {
      "method": "POST",
      "path": "/api/admin/departments",
      "body": {
        "revision": 0,
        "department": {
          "id": "finance",
          "name": "财务部",
          "parent_id": ""
        }
      }
    },
    {
      "method": "POST",
      "path": "/api/admin/users",
      "body": {
        "revision": 1,
        "user": {
          "id": "alice",
          "name": "员工",
          "department_id": "finance",
          "role_ids": [
            "employee"
          ],
          "enabled": true
        },
        "password": "<initial-or-reset-password>"
      }
    },
    {
      "method": "POST",
      "path": "/api/admin/roles",
      "body": {
        "revision": 2,
        "role": {
          "id": "custom",
          "name": "业务只读",
          "description": "",
          "permissions": [
            "modules.read",
            "business.read"
          ],
          "builtin": false
        }
      }
    },
    {
      "method": "GET",
      "path": "/api/publishing"
    },
    {
      "method": "POST",
      "path": "/api/publishing/tokens",
      "body": {
        "module_id": "leave",
        "expires_hours": 24
      }
    },
    {
      "method": "POST",
      "path": "/api/publishing/tokens/{id}/revoke",
      "body": {}
    },
    {
      "method": "POST",
      "path": "/api/publishing/submissions/{id}/test",
      "body": {}
    },
    {
      "method": "POST",
      "path": "/api/publishing/submissions/{id}/approve",
      "body": {
        "capabilities": [
          "identity.get",
          "records.list",
          "records.create"
        ],
        "note": "批准发布"
      }
    },
    {
      "method": "POST",
      "path": "/api/publishing/submissions/{id}/reject",
      "body": {
        "note": "请补充测试样例"
      }
    }
  ],
  "error_codes": [
    "INVALID_ARGUMENT",
    "UNAUTHENTICATED",
    "FORBIDDEN",
    "CONFLICT",
    "NOT_FOUND",
    "UNAVAILABLE",
    "INTERNAL"
  ],
  "auth": {
    "mode": "local-rbac",
    "cookie": "collab_local",
    "csrf_header": "X-CSRF-Token",
    "registration": "trusted binaries only, capability grant is explicit administrator approval",
    "login": "POST /api/auth/login with username/password; returned csrf_token replaces bootstrap token",
    "setup": "First run: POST /api/auth/setup with name/password creates local-admin; password minimum 12 characters",
    "permissions": "Module-scoped <id>.read/create checked in HTTP and reverse RPC. Legacy business.read/create explicitly cover all present and future modules. Roles union; module-scoped users discover only allowed modules unless granted modules.read."
  },
  "manifest_example": {
    "id": "reimbursement",
    "name": "费用报销",
    "version": "0.1.0",
    "protocol": 1,
    "entry": "reimbursement.exe",
    "capabilities": [
      "identity.get",
      "records.list",
      "records.create"
    ],
    "permissions": [
      {
        "id": "reimbursement.read",
        "name": "查看报销",
        "action": "list"
      },
      {
        "id": "reimbursement.create",
        "name": "提交报销",
        "action": "create"
      }
    ],
    "role_templates": [
      {
        "id": "reimbursement-user",
        "name": "报销申请人",
        "description": "查看及登记本模块业务，可在后台调整",
        "permissions": [
          "reimbursement.read",
          "reimbursement.create"
        ]
      },
      {
        "id": "reimbursement-viewer",
        "name": "报销只读用户",
        "description": "只允许查看本模块业务记录",
        "permissions": [
          "reimbursement.read"
        ]
      }
    ]
  },
  "module_permissions": {
    "actions": {
      "list": "<module-id>.read",
      "create": "<module-id>.create"
    },
    "role_templates": "Optional namespaced roles, limited to own module permissions. Create missing roles on registration, never auto-assign or overwrite edits.",
    "unregister": "Retains role grants and inactive catalog entries.",
    "record_columns": "Optional list of backend-derived display columns: name/label."
  },
  "module_publication": {
    "version": "0.4",
    "submit": {
      "method": "POST",
      "path": "/api/module-submissions",
      "content_type": "application/zip",
      "auth": "Scoped Bearer token; browser cookie is not required",
      "headers": [
        "Authorization",
        "X-Submission-Request-ID"
      ],
      "max_bytes": 67108864
    },
    "status": {
      "method": "GET",
      "path": "/api/module-submissions/{id}",
      "auth": "same module token or module administrator"
    },
    "states": [
      "pending",
      "testing",
      "ready",
      "publishing",
      "published",
      "failed",
      "rejected"
    ],
    "simulation": "Temporary data/module directories, executes trusted EXE, no OS sandbox; report bound to SHA256",
    "rollback": "Previous package backed up and restored on failed startup; production records not replaced",
    "admin_permission": "modules.manage"
  },
  "email_login": {
    "configuration": [
      "RESEND_API_KEY",
      "OA_MAIL_FROM",
      "OA_ADMIN_EMAIL (initial binding only)"
    ],
    "default_when_configured": "email_code",
    "password_login": false,
    "requires": "enabled existing user with unique email; browser session, CSRF and same Origin",
    "request": {
      "method": "POST",
      "path": "/api/auth/email/request",
      "body": {
        "email": "user@example.com"
      },
      "response": {
        "challenge_id": "opaque id",
        "expires_in": 300,
        "resend_after": 60
      }
    },
    "verify": {
      "method": "POST",
      "path": "/api/auth/email/verify",
      "body": {
        "email": "user@example.com",
        "code": "six digits",
        "challenge_id": "opaque id"
      }
    },
    "limits": {
      "expiry_seconds": 300,
      "resend_seconds": 60,
      "max_attempts": 5,
      "email_sends_per_hour": 5,
      "ip_sends_per_hour": 20,
      "global_sends_per_hour": 100
    },
    "security": "single use, browser binding, hashed codes in memory, no public registration"
  },
  "ui_style": {
    "id": "adminui-v1",
    "required": true,
    "guide": "/docs/ui-style.md",
    "preview": "/docs/ui-style",
    "sdk_files": [
      "UI_STYLE.md",
      "ui/index.html"
    ],
    "rendering": "platform generates module forms and tables from Describe and record_columns; no custom frontend assets in v1 ZIP"
  },
  "workflow_extension": {
    "platform_version": "0.6.0",
    "protocol": 1,
    "actions": [
      "edit",
      "submit",
      "publish",
      "reject"
    ],
    "permission": "<module-id>.<action>",
    "request": "POST /api/actions/{id}/{action} with request_id and payload",
    "descriptor_action": {
      "name": "publish",
      "label": "审核并发布",
      "fields": [],
      "statuses": [
        "待审核"
      ],
      "owner_only": false
    },
    "identity_get_addition": "permissions: string[]",
    "storage": "records.create appends immutable revisions; records.list reads them",
    "legacy_grants": "business.create does not grant workflow permissions"
  },
  "personal_api_keys": {
    "create": "POST /api/account/api-keys",
    "list": "GET /api/account/api-keys",
    "revoke": "POST /api/account/api-keys/{id}/revoke",
    "create_payload": {
      "name": "Agent",
      "expires_days": 30
    },
    "optional_scopes": "permissions: null follows live account; [] grants none; array narrows to named grants",
    "authentication": "Bearer API Key for business and administrative APIs; browser session and CSRF for key lifecycle",
    "applications": "Registered users submit candidates and query their own submissions; modules.manage required for simulation and approval"
  }
}