openapi: 3.1.0
info:
  title: Kazi JavaScript Functions API
  version: 0.1.0
  summary: Deploy and invoke small JavaScript functions in fresh V8 runtimes.
  description: |
    Kazi is a Lambda-style JavaScript function service. A deployment contains
    one JavaScript function expression. Kazi stores immutable source versions
    in Azure Blob, points a current manifest at the active version, and creates
    a fresh V8 runtime for each invocation.

    Function resources are private to a verified Worklyn user. API callers send
    a Worklyn session token or PAT as a Bearer credential. Browser users sign in
    through Worklyn and receive an opaque Kazi session cookie. Kazi derives the
    owner userId from Worklyn verification and never trusts a caller-supplied
    userId.
servers:
  - url: http://127.0.0.1:8790
    description: Default local Kazi server
security: []
tags:
  - name: Functions
    description: Function deployment and discovery.
  - name: Invocation
    description: Execute deployed functions.
  - name: System
    description: Dashboard, API description, and health.

x-kazi-runtime:
  executionModel:
    artifact: One JavaScript function expression
    isolation: A fresh V8 JsRuntime is created and dropped for every invocation
    input: Any JSON value
    output: Any JSON-serializable value; undefined is returned as null
    asynchronous: Functions may return a value or a Promise
    persistence: JavaScript globals do not persist between invocations
    defaultWallClockLimitMs: 5000
    defaultV8HeapLimitMiB: 64
  functionContract:
    signature: async function (input, context) { return result; }
    arguments:
      input: The JSON request body supplied to the invocation endpoint
      context:
        userId: Verified Worklyn owner and invoker ID
        functionName: Name of the deployed function
        version: SHA-256-derived immutable source version
    example: |
      async function (input, context) {
        const response = await fetch(input.url);
        return {
          functionName: context.functionName,
          userId: context.userId,
          version: context.version,
          status: response.status,
          body: await response.text(),
        };
      }
  supportedJavaScriptApis:
    language:
      - ECMAScript syntax and built-ins provided by the pinned V8 engine
      - Promise and async/await
      - JSON, Date, Math, RegExp, Map, Set, ArrayBuffer, and typed arrays
    urls:
      - URL
      - URLSearchParams
    encoding:
      - TextEncoder
      - TextDecoder
      - TextEncoderStream
      - TextDecoderStream
      - atob
      - btoa
    eventsAndCancellation:
      - Event
      - EventTarget
      - CustomEvent
      - ErrorEvent
      - AbortController
      - AbortSignal
      - AbortSignal.timeout
    scheduling:
      - setTimeout
      - clearTimeout
      - setInterval
      - clearInterval
      - performance.now
    streams:
      - ReadableStream
      - WritableStream
      - TransformStream
      - ByteLengthQueuingStrategy
      - CountQueuingStrategy
    http:
      - fetch
      - Headers
      - Request
      - Response
      - FormData
      - Blob
      - File
    cryptography:
      - crypto
      - crypto.randomUUID
      - crypto.getRandomValues
      - crypto.subtle
      - Crypto
      - CryptoKey
      - SubtleCrypto
    diagnostics:
      - console.log
      - console.info
      - console.warn
      - console.error
      - console.debug
    errors:
      - DOMException
    persistentState:
      - kazi.kv.get
      - kazi.kv.set
      - kazi.kv.delete
      - kazi.kv.list
  unsupportedJavaScriptApis:
    - Node.js APIs, including process, Buffer, require, and node:* modules
    - Deno namespace and Deno APIs
    - Filesystem access
    - Environment-variable access
    - Child processes and shell commands
    - Native addons
    - npm, CommonJS, ES module imports, and remote module imports
    - TypeScript compilation
    - Persistent globals between invocations
    - Deno.cron and in-isolate scheduled jobs
  deploymentArtifact:
    sourceContentType: application/javascript
    maxSourceBytes: 524288
    bundling: Dependencies must be bundled into the single function expression before deployment

x-kazi-origin-policy:
  browserOrigins:
    - https://worklyn.com
    - https://*.worklyn.com
    - https://worklyn.me
    - https://*.worklyn.me
    - Configured local development origin
  behavior: Requests carrying any other Origin header receive HTTP 403
  nonBrowserClients: Requests without Origin remain subject to Worklyn Bearer authentication
  note: Origin filtering is defense in depth and does not replace bearer or browser-session authentication

paths:
  /:
    get:
      tags: [System]
      operationId: getDashboard
      summary: View the deployed-functions dashboard
      responses:
        "200":
          description: Read-only HTML dashboard.
          content:
            text/html:
              schema:
                type: string

  /openapi.yaml:
    get:
      tags: [System]
      operationId: getOpenApiSpec
      summary: Download this OpenAPI document
      responses:
        "200":
          description: Kazi OpenAPI 3.1 specification.
          content:
            application/yaml:
              schema:
                type: string

  /llm.txt:
    get:
      tags: [System]
      operationId: getLlmGuide
      summary: Get concise machine-readable Kazi usage guidance
      responses:
        "200":
          description: Authentication, function contract, examples, APIs, and limits.
          content:
            text/plain:
              schema:
                type: string

  /auth/worklyn/start:
    get:
      tags: [System]
      operationId: startWorklynLogin
      summary: Start browser login with Worklyn
      description: Creates a short-lived login state and redirects to worklyn.me.
      responses:
        "302":
          description: Redirect to Worklyn login.

  /auth/worklyn/callback:
    get:
      tags: [System]
      operationId: completeWorklynLogin
      summary: Complete the Worklyn browser login callback
      description: |
        Validates the state-bound callback, exchanges the one-shot `_authcode`
        server-to-server, verifies the returned Worklyn bearer, creates an opaque
        Kazi browser session, and redirects to the dashboard.
      parameters:
        - name: state
          in: query
          required: true
          schema:
            type: string
            format: uuid
        - name: _authcode
          in: query
          required: true
          schema:
            type: string
      responses:
        "302":
          description: Redirect to the dashboard on success or with `auth_error=1` on failure.

  /healthz:
    get:
      tags: [System]
      operationId: getHealth
      summary: Check whether the Kazi process is accepting requests
      responses:
        "200":
          description: Process is healthy.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"

  /api/functions:
    get:
      tags: [Functions]
      operationId: listFunctions
      summary: List current function deployments and runtime statistics
      description: |
        Reads current manifests from Azure Blob and joins them with statistics
        held by this Kazi process. Only functions owned by the authenticated
        Worklyn user are returned. Invocation statistics reset on process restart.
      security:
        - WorklynBearer: []
        - KaziSessionCookie: []
      parameters:
        - name: atomId
          in: query
          required: false
          schema: { type: string, minLength: 1, maxLength: 128 }
          description: Return only functions scoped to this parent atom.
      responses:
        "200":
          description: Functions currently available for invocation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FunctionListResponse"
              examples:
                oneFunction:
                  value:
                    functions:
                      - name: hello
                        version: 4c71ecb3b427f6ce265a30ae
                        deployedAt: 1784512345678
                        sourceBytes: 118
                        endpoint: /functions/hello
                        invocations: 24
                        errors: 1
                        averageDurationMs: 3.8
                        lastInvokedAt: 1784512400000
                    storageOffline: false
        "500":
          $ref: "#/components/responses/InternalError"

    post:
      tags: [Functions]
      operationId: deployFunction
      summary: Validate and deploy a JavaScript function
      description: |
        The source must be a complete function expression, not a module or a
        script containing declarations around the function. Kazi starts a V8
        runtime and evaluates the source before uploading it. It writes the
        immutable version blob first and the current manifest last.

        Kazi verifies the Worklyn credential and derives ownership from the
        returned `userId`. Do not include a `userId` in the request body. When
        `atomId` is supplied, Kazi verifies access and creates or updates a
        `tool` child atom under that parent using a stable `kazi://function/...`
        source identifier.
      security:
        - WorklynBearer: []
        - KaziSessionCookie: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeployFunctionRequest"
            examples:
              hello:
                summary: Deploy an asynchronous greeting function
                value:
                  name: hello
                  atomId: 9cb4cc4f-7996-4c95-bfc2-b5fc1d7fe383
                  source: |-
                    async function (input, context) {
                      return {
                        message: `Hello ${input.name}`,
                        version: context.version,
                      };
                    }
              fetchUrl:
                summary: Deploy a function using the supported fetch API
                value:
                  name: fetch-status
                  source: |-
                    async function (input) {
                      const response = await fetch(input.url, {
                        signal: AbortSignal.timeout(2000),
                      });
                      return {
                        status: response.status,
                        contentType: response.headers.get("content-type"),
                      };
                    }
      responses:
        "201":
          description: Function source was uploaded and promoted to current.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FunctionManifest"
        "400":
          description: Invalid name, request body, source size, or JavaScript function.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "502":
          $ref: "#/components/responses/AuthUnavailable"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/apps:
    get:
      tags: [Functions]
      operationId: listPublicApps
      summary: List the authenticated user's registered public applications
      security:
        - WorklynBearer: []
        - KaziSessionCookie: []
      responses:
        "200":
          description: Registered applications and their Desk proxy paths.
          content:
            application/json:
              schema:
                type: object
                required: [apps]
                properties:
                  apps:
                    type: array
                    items: { $ref: "#/components/schemas/PublicApp" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Functions]
      operationId: registerPublicApp
      summary: Map public action names to deployed private functions
      description: Register functions first. Put only returned proxyPaths in generated browser code.
      security:
        - WorklynBearer: []
        - KaziSessionCookie: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, actions]
              properties:
                name: { type: string, maxLength: 120 }
                actions:
                  type: object
                  additionalProperties: { type: string }
            example:
              name: Invoice helper
              actions: { calculate: invoice-calculator }
      responses:
        "201":
          description: Application registered.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PublicApp" }
        "400": { $ref: "#/components/responses/InternalError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /functions/{name}:
    post:
      tags: [Invocation]
      operationId: invokeFunction
      summary: Invoke the current version of a function
      description: |
        Passes the JSON request body as `input`, creates the invocation context,
        starts a fresh V8 runtime, executes the function, and drops the runtime.
        The function is resolved only in the authenticated Worklyn user's
        namespace. There is no owner override and no anonymous invocation.
      security:
        - WorklynBearer: []
        - KaziSessionCookie: []
      parameters:
        - $ref: "#/components/parameters/FunctionName"
      requestBody:
        required: false
        description: Any JSON value passed to the function as `input`. An empty body becomes null.
        content:
          application/json:
            schema: {}
            examples:
              objectInput:
                value:
                  name: Thandi
                  url: https://example.com
      responses:
        "200":
          description: Function completed and returned a JSON-serializable value.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InvocationResponse"
              example:
                result:
                  message: Hello Thandi
                  version: 4c71ecb3b427f6ce265a30ae
                version: 4c71ecb3b427f6ce265a30ae
                durationMs: 3.2
        "400":
          description: Invalid function name or JSON input.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: No current manifest exists for this function.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Function threw, rejected, exceeded a runtime limit, or returned an unsupported value.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InvocationErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "502":
          $ref: "#/components/responses/AuthUnavailable"

  /api/me:
    get:
      tags: [System]
      operationId: getCurrentKaziUser
      summary: Get the verified Worklyn identity for the current session
      security:
        - WorklynBearer: []
        - KaziSessionCookie: []
      responses:
        "200":
          description: Verified Worklyn identity.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CurrentUserResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "502":
          $ref: "#/components/responses/AuthUnavailable"

  /api/logout:
    post:
      tags: [System]
      operationId: logoutKaziBrowserSession
      summary: Delete the current local Kazi browser session
      description: Does not revoke the underlying Worklyn credential.
      responses:
        "204":
          description: Local session deleted and cookie expired.

  /api/schedules:
    get:
      tags: [Functions]
      operationId: listFunctionSchedules
      summary: List native Kazi function schedules and their latest run
      security:
        - WorklynBearer: []
        - KaziSessionCookie: []
      responses:
        "200":
          description: Registered Kazi function schedules.
          content:
            application/json:
              schema:
                type: object
                required: [schedules]
                properties:
                  schedules:
                    type: array
                    items: { $ref: "#/components/schemas/FunctionScheduleView" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "503": { $ref: "#/components/responses/InternalError" }
    post:
      tags: [Functions]
      operationId: createFunctionSchedule
      summary: Schedule a deployed function with a five-field UTC cron expression
      security:
        - WorklynBearer: []
        - KaziSessionCookie: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateFunctionSchedule" }
            example:
              name: Daily cleanup
              functionName: cleanup
              cron: 0 1 * * *
              input: { retentionDays: 30 }
              enabled: true
      responses:
        "201":
          description: Schedule created and durably indexed.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FunctionSchedule" }
        "400": { $ref: "#/components/responses/InternalError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/schedules/{scheduleId}:
    parameters:
      - name: scheduleId
        in: path
        required: true
        schema: { type: string, pattern: "^sch_[a-f0-9]{32}$" }
    get:
      tags: [Functions]
      operationId: getFunctionSchedule
      summary: Get a function schedule
      security: [{ WorklynBearer: [] }, { KaziSessionCookie: [] }]
      responses:
        "200": { description: Schedule, content: { application/json: { schema: { $ref: "#/components/schemas/FunctionSchedule" } } } }
        "404": { $ref: "#/components/responses/InternalError" }
    patch:
      tags: [Functions]
      operationId: updateFunctionSchedule
      summary: Update, pause, or resume a function schedule
      security: [{ WorklynBearer: [] }, { KaziSessionCookie: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PatchFunctionSchedule" }
      responses:
        "200": { description: Updated schedule, content: { application/json: { schema: { $ref: "#/components/schemas/FunctionSchedule" } } } }
        "404": { $ref: "#/components/responses/InternalError" }
    delete:
      tags: [Functions]
      operationId: deleteFunctionSchedule
      summary: Delete a function schedule
      security: [{ WorklynBearer: [] }, { KaziSessionCookie: [] }]
      responses:
        "204": { description: Schedule deleted. Existing run history is retained. }
        "404": { $ref: "#/components/responses/InternalError" }

  /api/schedules/{scheduleId}/runs:
    get:
      tags: [Functions]
      operationId: listFunctionScheduleRuns
      summary: List execution history for a function schedule
      security: [{ WorklynBearer: [] }, { KaziSessionCookie: [] }]
      parameters:
        - name: scheduleId
          in: path
          required: true
          schema: { type: string, pattern: "^sch_[a-f0-9]{32}$" }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
      responses:
        "200":
          description: Newest runs first.
          content:
            application/json:
              schema:
                type: object
                required: [runs]
                properties:
                  runs:
                    type: array
                    items: { $ref: "#/components/schemas/ScheduleRun" }

components:
  securitySchemes:
    WorklynBearer:
      type: http
      scheme: bearer
      bearerFormat: Worklyn session or PAT
      description: |
        Worklyn session token or `pat_` credential. Kazi verifies it through
        `worklyn.me/verify` and derives the owner from the returned userId.
    KaziSessionCookie:
      type: apiKey
      in: cookie
      name: kazi_session
      description: Opaque browser session created by Login with Worklyn.

  parameters:
    FunctionName:
      name: name
      in: path
      required: true
      description: Lowercase function name, 1-63 characters.
      schema:
        type: string
        minLength: 1
        maxLength: 63
        pattern: "^[a-z][a-z0-9-]{0,62}$"
        examples: [hello, invoice-v2]

  responses:
    Unauthorized:
      description: A valid Worklyn Bearer or Kazi browser session is required.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    AuthUnavailable:
      description: Kazi could not reach Worklyn to verify the credential.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    InternalError:
      description: Kazi or its Azure Blob dependency failed.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

  schemas:
    HealthResponse:
      type: object
      additionalProperties: false
      required: [ok]
      properties:
        ok:
          type: boolean
          const: true

    DeployFunctionRequest:
      type: object
      additionalProperties: false
      required: [name, source]
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 63
          pattern: "^[a-z][a-z0-9-]{0,62}$"
          description: Stable name used in the invocation URL and Azure Blob prefix.
          example: hello
        source:
          type: string
          minLength: 1
          maxLength: 524288
          description: |
            Complete JavaScript function expression with the signature
            `function (input, context)` or `async function (input, context)`.
            ES modules, imports, TypeScript, Node.js APIs, Deno APIs, filesystem
            access, environment access, and child processes are not supported.
          example: |-
            async function (input, context) {
              const id = crypto.randomUUID();
              return { id, input, version: context.version };
            }
        atomId:
          type: string
          minLength: 1
          maxLength: 128
          pattern: "^[A-Za-z0-9_-]+$"
          description: |
            Parent Worklyn atom that scopes this function. Kazi verifies the
            caller can access it and upserts a safe `tool` child atom. Omit only
            for an intentionally account-global function.

    FunctionManifest:
      type: object
      additionalProperties: false
      required: [ownerUserId, name, version, deployedAt, sourceBytes, sourceBlob]
      properties:
        ownerUserId:
          type: string
          description: Worklyn userId derived from the verified deployment credential.
        name:
          type: string
        version:
          type: string
          description: First 24 hexadecimal characters of the source SHA-256 digest.
          pattern: "^[a-f0-9]{24}$"
        deployedAt:
          type: integer
          format: int64
          description: Unix epoch milliseconds when this version became current.
        sourceBytes:
          type: integer
          minimum: 1
          maximum: 524288
        sourceBlob:
          type: string
          description: Container-relative immutable Azure Blob source path.
        atomId:
          type: string
          description: Parent Worklyn atom for an atom-scoped function.
        toolAtomId:
          type: string
          description: Synchronized `tool` child atom id.

    PublicApp:
      type: object
      required: [appId, name, actions, proxyPaths, createdAt]
      properties:
        appId: { type: string, pattern: "^app_[a-f0-9]{32}$" }
        name: { type: string }
        actions:
          type: object
          additionalProperties: { type: string }
        proxyPaths:
          type: object
          additionalProperties: { type: string }
        createdAt: { type: integer, format: int64 }

    FunctionView:
      type: object
      additionalProperties: false
      required:
        - name
        - version
        - deployedAt
        - sourceBytes
        - endpoint
        - invocations
        - errors
        - averageDurationMs
      properties:
        name:
          type: string
        version:
          type: string
        deployedAt:
          type: integer
          format: int64
        sourceBytes:
          type: integer
        endpoint:
          type: string
          example: /functions/hello
        atomId:
          type: string
          description: Parent Worklyn atom for an atom-scoped function.
        toolAtomId:
          type: string
          description: Synchronized `tool` child atom id.
        invocations:
          type: integer
          format: int64
          minimum: 0
        errors:
          type: integer
          format: int64
          minimum: 0
        averageDurationMs:
          type: number
          format: double
          minimum: 0
        lastInvokedAt:
          type: [integer, "null"]
          format: int64
          description: Unix epoch milliseconds, or null if this process has not invoked the function.

    FunctionListResponse:
      type: object
      additionalProperties: false
      required: [functions, storageOffline]
      properties:
        functions:
          type: array
          items:
            $ref: "#/components/schemas/FunctionView"
        storageOffline:
          type: boolean
          description: True only when the server was explicitly started in read-only local Blob offline mode.

    InvocationResponse:
      type: object
      additionalProperties: false
      required: [result, version, durationMs]
      properties:
        result:
          description: Arbitrary JSON value returned by the deployed function.
        version:
          type: string
          description: Function version that handled the invocation.
        durationMs:
          type: number
          format: double
          minimum: 0
          description: Host-measured V8 execution duration in milliseconds.

    ErrorResponse:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error:
          type: string

    InvocationErrorResponse:
      allOf:
        - $ref: "#/components/schemas/ErrorResponse"
        - type: object
          required: [version, durationMs]
          properties:
            version:
              type: string
            durationMs:
              type: number
              format: double
              minimum: 0

    VerifiedUser:
      type: object
      additionalProperties: false
      required: [userId]
      properties:
        userId:
          type: string
        name:
          type: [string, "null"]
        assistantName:
          type: [string, "null"]

    CurrentUserResponse:
      type: object
      additionalProperties: false
      required: [user]
      properties:
        user:
          $ref: "#/components/schemas/VerifiedUser"

    CreateFunctionSchedule:
      type: object
      additionalProperties: false
      required: [name, functionName, cron]
      properties:
        name: { type: string, minLength: 1, maxLength: 64 }
        functionName: { type: string, pattern: "^[a-z][a-z0-9-]{0,62}$" }
        cron:
          type: string
          description: Five-field UTC cron expression (minute hour day-of-month month day-of-week).
        input: {}
        enabled: { type: boolean, default: true }

    PatchFunctionSchedule:
      type: object
      additionalProperties: false
      properties:
        name: { type: string, minLength: 1, maxLength: 64 }
        cron: { type: string }
        input: {}
        enabled: { type: boolean }

    FunctionSchedule:
      allOf:
        - $ref: "#/components/schemas/CreateFunctionSchedule"
        - type: object
          required: [id, ownerUserId, nextRunAt, createdAt, updatedAt]
          properties:
            id: { type: string, pattern: "^sch_[a-f0-9]{32}$" }
            ownerUserId: { type: string }
            nextRunAt: { type: integer, format: int64 }
            activeRunId: { type: [string, "null"] }
            createdAt: { type: integer, format: int64 }
            updatedAt: { type: integer, format: int64 }

    FunctionScheduleView:
      allOf:
        - $ref: "#/components/schemas/FunctionSchedule"
        - type: object
          properties:
            lastRun: { anyOf: [{ $ref: "#/components/schemas/ScheduleRun" }, { type: "null" }] }

    ScheduleRun:
      type: object
      required: [runId, scheduleId, functionName, scheduledAt, status, startedAt]
      properties:
        runId: { type: string }
        scheduleId: { type: string }
        functionName: { type: string }
        functionVersion: { type: [string, "null"] }
        scheduledAt: { type: integer, format: int64 }
        status: { type: string, enum: [running, succeeded, failed, timed_out] }
        startedAt: { type: integer, format: int64 }
        finishedAt: { type: [integer, "null"], format: int64 }
        durationMs: { type: [number, "null"] }
        error: { type: [string, "null"] }
        resultSummary: {}
