{
  "schemaVersion": 1,
  "name": "Report Desk agent publishing contract",
  "canonicalUrl": "https://reportdesk.pages.dev/post/agent-publishing.json",
  "humanGuide": "https://reportdesk.pages.dev/post/",
  "managementConsole": "https://reportdesk.pages.dev/manage/",
  "apiBase": "https://reportdesk.pages.dev",
  "authentication": {
    "type": "http-bearer",
    "headerTemplate": "Authorization: Bearer ${REPORTDESK_PASSCODE}",
    "secretEnvironmentVariable": "REPORTDESK_PASSCODE",
    "readerCookieIsWriteAuthority": false,
    "instructions": [
      "Obtain the passcode from the owner through a private channel only when ready to publish.",
      "Keep it in memory or a secret environment variable; never place it in a URL, report package, source file, commit, log, screenshot, or final response.",
      "Never print, echo, summarize, or otherwise disclose the passcode."
    ]
  },
  "authority": {
    "scope": "full-site-administrator",
    "warning": "The shared passcode can create, alter, publish, and delete every project and report.",
    "defaultBehavior": "Create new content only. Do not overwrite, move, unpublish, or delete existing content without an explicit owner instruction for the exact target."
  },
  "transport": {
    "recommended": "Direct server-side HTTPS requests from the agent runtime.",
    "browserCorsOrigin": "https://reportdesk-admin.pages.dev",
    "note": "Browser JavaScript from other origins is rejected. Non-browser agents should send direct HTTPS requests and omit the Origin header."
  },
  "workflow": [
    {
      "step": 1,
      "action": "Inspect the current management catalog",
      "request": {
        "method": "GET",
        "path": "/api/manage/catalog"
      },
      "result": "Use projects, reports, and current revisions to avoid collisions or stale writes."
    },
    {
      "step": 2,
      "action": "Create the project only when it does not already exist",
      "request": {
        "method": "PUT",
        "path": "/api/manage/projects",
        "contentType": "application/json",
        "exampleBody": {
          "id": "example-project",
          "name": "Example Project",
          "summary": "Reports for this project.",
          "isPublished": true
        }
      },
      "result": "A published parent project is required for the final report URL to resolve."
    },
    {
      "step": 3,
      "action": "Create report metadata as a draft",
      "request": {
        "method": "PUT",
        "path": "/api/manage/reports",
        "contentType": "application/json",
        "exampleBody": {
          "id": "example-report",
          "projectId": "example-project",
          "sourcePath": "",
          "title": "Example Report",
          "date": "8 August 2026",
          "kind": "Research report",
          "description": "A concise description of the report.",
          "tags": [
            "example"
          ],
          "expectedRevision": 0
        }
      },
      "result": "Read report.revision from the response. Creation requires expectedRevision 0."
    },
    {
      "step": 4,
      "action": "Upload every package file one at a time",
      "request": {
        "method": "PUT",
        "pathTemplate": "/api/manage/files?reportId={reportId}&path={urlEncodedRelativePath}&expectedRevision={currentRevision}",
        "contentType": "The file's actual MIME type",
        "body": "Raw file bytes"
      },
      "result": "After every successful upload, replace currentRevision with file.revision from the response. Upload a root index.html before publishing."
    },
    {
      "step": 5,
      "action": "Publish the completed report",
      "request": {
        "method": "POST",
        "path": "/api/manage/publish",
        "contentType": "application/json",
        "exampleBody": {
          "id": "example-report",
          "expectedRevision": "Use the revision returned by the final file upload"
        }
      },
      "result": "A successful response returns status published and the final revision. The canonical URL is https://reportdesk.pages.dev/{reportId}/."
    },
    {
      "step": 6,
      "action": "Verify the canonical report URL",
      "request": {
        "method": "GET",
        "pathTemplate": "/{reportId}/"
      },
      "result": "Confirm the page loads, local assets resolve, interaction still works, and the global Report Desk search bar is present once."
    }
  ],
  "revisionProtocol": [
    "Every report metadata, file, publish, and delete mutation requires expectedRevision.",
    "Use 0 only when creating a new report.",
    "Read the new revision from each successful response; never guess after a failed request.",
    "On HTTP 409 revision_conflict, stop, refresh /api/manage/catalog, and reconcile with the owner instead of overwriting concurrent work."
  ],
  "packageRules": {
    "requiredRootFile": "index.html",
    "reportIdPattern": "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
    "paths": "Relative forward-slash paths only; no leading slash, trailing slash, empty segment, dot segment, traversal, backslash, or control character.",
    "maximumFileBytes": 26214400,
    "maximumTrackedStorageBytes": 5368709120,
    "maximumTags": 50,
    "remoteAssets": "Prefer local package assets. Preserve the report's own HTML, CSS, JavaScript, images, and interaction."
  },
  "dangerousOperations": [
    {
      "method": "DELETE",
      "path": "/api/manage/reports",
      "body": {
        "id": "report-id",
        "expectedRevision": "current revision"
      },
      "rule": "Use only after explicit owner confirmation for the exact report. Deletes metadata and stored files."
    },
    {
      "method": "DELETE",
      "pathTemplate": "/api/manage/files?reportId={reportId}&path={urlEncodedRelativePath}&expectedRevision={currentRevision}",
      "rule": "Use only after explicit owner confirmation for the exact file."
    },
    {
      "method": "DELETE",
      "path": "/api/manage/projects",
      "body": {
        "id": "project-id"
      },
      "rule": "Use only after explicit owner confirmation. A project containing reports cannot be deleted."
    }
  ],
  "errorShape": {
    "example": {
      "error": {
        "code": "revision_conflict",
        "message": "The report revision does not match expectedRevision.",
        "details": {
          "currentRevision": 4
        }
      }
    }
  },
  "completionReport": [
    "Project ID and report ID",
    "Canonical report URL",
    "Final revision",
    "Number of uploaded files",
    "Verification performed",
    "Any warning or unresolved issue",
    "Never include the passcode"
  ]
}
