openapi/openapi.yaml
Rama main · réplica de lectura del repositorio original.
openapi: 3.1.0
info:
title: Ferryman API
version: 0.1.0
servers: [{url: http://127.0.0.1:8787}]
components:
securitySchemes:
ProjectBearer:
type: http
scheme: bearer
description: Project-scoped operator token.
ActorBearer:
type: http
scheme: bearer
description: Eight-hour communications actor token returned once by the actor token endpoint.
schemas:
Error: {type: object, required: [code, message], properties: {code: {type: string}, message: {type: string}}}
Job: {type: object, required: [id, project_id, status, input], properties: {id: {type: string}, project_id: {type: string}, status: {type: string, enum: [pending_approval, queued, leased, succeeded, failed, cancelled]}, input: {type: object}, policy: {type: object}}}
Artifact: {type: object, required: [id, job_id, sha256, content_type, byte_len], properties: {id: {type: string}, job_id: {type: string}, sha256: {type: string}, content_type: {type: string}, byte_len: {type: integer}}}
CommunicationParticipant:
type: object
additionalProperties: false
required: [name, role, capabilities]
properties:
name: {type: string, pattern: '^[A-Za-z0-9._-]+$', maxLength: 128}
role: {type: string, pattern: '^[A-Za-z0-9._-]+$', maxLength: 128}
capabilities:
type: array
maxItems: 128
items: {type: string, minLength: 1, maxLength: 128}
CommunicationMessage:
type: object
required: [format, id, project_id, idempotency_key, sender, recipient, created_at, acknowledgement_deadline, payload_reference, payload, reply_required]
properties:
format: {type: string, const: ferryman-message/v1}
id: {type: string, format: uuid}
project_id: {type: string}
idempotency_key: {type: string, maxLength: 256}
sender: {type: string, maxLength: 128}
recipient: {type: string, maxLength: 128}
created_at: {type: string, format: date-time}
acknowledgement_deadline: {type: string, format: date-time}
payload_reference: {type: string, maxLength: 2048}
payload:
description: JSON limited to 256 KiB after serialization; credential-bearing keys are rejected recursively.
reply_required: {type: boolean}
CommunicationStatus:
type: object
required: [project_id, external_probes_performed, local_health, shared_health, git_health, git_live, git_inbound_active, preferred_successes, outbox_depth, acknowledgement_outbox_depth, quarantine_files, git_backoff_attempt, updated_at]
properties:
project_id: {type: string}
external_probes_performed: {type: boolean}
local_health: {type: string, enum: [healthy, unavailable, rate_limited]}
shared_health: {type: string, enum: [healthy, unavailable, rate_limited]}
git_health: {type: string, enum: [healthy, unavailable, rate_limited]}
git_live: {type: boolean}
git_inbound_active: {type: boolean}
preferred_successes: {type: integer, minimum: 0}
outbox_depth: {type: integer, minimum: 0}
acknowledgement_outbox_depth: {type: integer, minimum: 0}
oldest_outbox_age_seconds: {type: [integer, 'null'], minimum: 0}
quarantine_files: {type: integer, minimum: 0}
git_backoff_attempt: {type: integer, minimum: 0}
git_retry_after_unix_ms: {type: [integer, 'null'], minimum: 0}
updated_at: {type: string, format: date-time}
CommunicationActorRegistration:
type: object
required: [project_id, actor, actor_token, expires_at]
properties:
project_id: {type: string}
actor: {type: string}
actor_token: {type: string, writeOnly: true}
expires_at: {type: string, format: date-time}
paths:
/healthz: {get: {responses: {'200': {description: health}}}}
/v1/projects:
post: {summary: Create a project (local setup endpoint), requestBody: {required: true, content: {application/json: {schema: {type: object, required: [id,name,token]}}}}, responses: {'201': {description: created}, '400': {description: invalid, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}}
/v1/projects/{project_id}/communications:
parameters: [{name: project_id, in: path, required: true, schema: {type: string}}]
post: {security: [{ProjectBearer: []}], summary: Configure the outer-local and inner-portable project communications mapping, responses: {'200': {description: project route}, '400': {description: unsafe or mismatched route}}}
get: {security: [{ProjectBearer: []}], summary: Read the project communications mapping, responses: {'200': {description: project route}, '404': {description: no mapping}}}
delete:
security: [{ProjectBearer: []}]
summary: Unregister the hub mapping and revoke actor tokens without deleting files
responses:
'204': {description: mapping removed; attachment files remain}
'409': {description: refused because durable outbox items remain}
/v1/projects/{project_id}/communications/messages:
parameters: [{name: project_id, in: path, required: true, schema: {type: string}}]
post:
security: [{ProjectBearer: []}]
summary: Durably enqueue and deliver a project-scoped message
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required: [sender, recipient]
properties:
sender: {type: string, maxLength: 128}
recipient: {type: string, maxLength: 128}
payload_reference: {type: string, default: inline, maxLength: 2048}
payload: {default: null}
reply_required: {type: boolean, default: false}
idempotency_key: {type: [string, 'null'], maxLength: 256}
acknowledgement_timeout_seconds: {type: integer, minimum: 5, maximum: 3600, default: 30}
responses:
'201': {description: stable message and delivery receipt}
'400': {description: invalid route, payload, or idempotency conflict}
get: {security: [{ProjectBearer: []}], summary: List portable project messages, responses: {'200': {description: messages}}}
/v1/projects/{project_id}/communications/messages/{message_id}/claim:
parameters:
- {name: project_id, in: path, required: true, schema: {type: string}}
- {name: message_id, in: path, required: true, schema: {type: string, format: uuid}}
post:
security: [{ActorBearer: []}]
summary: Atomically claim a message idempotency key before execution
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required: [recipient]
properties: {recipient: {type: string}}
responses:
'200': {description: claim result; claimed false means do not execute}
'401': {description: missing, expired, or wrong actor token}
/v1/projects/{project_id}/communications/messages/{message_id}/acknowledge:
parameters:
- {name: project_id, in: path, required: true, schema: {type: string}}
- {name: message_id, in: path, required: true, schema: {type: string, format: uuid}}
post:
security: [{ActorBearer: []}]
summary: Record a separate acknowledgement and retire the outbox item
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required: [recipient]
properties: {recipient: {type: string}}
responses:
'200': {description: acknowledgement, idempotent record result, and durable delivery receipt}
'401': {description: missing, expired, or wrong actor token}
/v1/projects/{project_id}/communications/actors/{actor}/token:
parameters:
- {name: project_id, in: path, required: true, schema: {type: string}}
- {name: actor, in: path, required: true, schema: {type: string, pattern: '^[A-Za-z0-9._-]+$'}}
post:
security: [{ProjectBearer: []}]
summary: Mint an eight-hour token for one registered communications actor
responses:
'201':
description: actor token returned once
content:
application/json:
schema: {$ref: '#/components/schemas/CommunicationActorRegistration'}
/v1/projects/{project_id}/communications/actors/{actor}/messages:
parameters:
- {name: project_id, in: path, required: true, schema: {type: string}}
- {name: actor, in: path, required: true, schema: {type: string, pattern: '^[A-Za-z0-9._-]+$'}}
get:
security: [{ActorBearer: []}]
summary: List only messages addressed to this actor name or registered role
parameters:
- {name: synchronize, in: query, schema: {type: boolean, default: true}, description: Pull inbound private-Git state when shared delivery is unavailable or Git inbound mode is active.}
responses:
'200':
description: actor-scoped inbox
content:
application/json:
schema:
type: object
required: [items, synchronized_via]
properties:
items:
type: array
items: {$ref: '#/components/schemas/CommunicationMessage'}
synchronized_via: {type: [string, 'null'], enum: [local_filesystem, shared_folder, private_git, null]}
'401': {description: missing, expired, or wrong actor token}
/v1/projects/{project_id}/communications/reconcile:
parameters: [{name: project_id, in: path, required: true, schema: {type: string}}]
post: {security: [{ProjectBearer: []}], summary: Reconcile the durable outbox and transport failover state immediately, responses: {'200': {description: delivery receipts}}}
/v1/projects/{project_id}/communications/status:
parameters: [{name: project_id, in: path, required: true, schema: {type: string}}]
get:
security: [{ProjectBearer: []}]
summary: Inspect transport health, failover state, queue age, and quarantine
parameters:
- {name: probe_external, in: query, schema: {type: boolean, default: true}, description: Set false for a strictly local snapshot without Syncthing or private-Git health probes.}
responses:
'200':
description: current communications status
content:
application/json:
schema: {$ref: '#/components/schemas/CommunicationStatus'}
/v1/projects/{project_id}/jobs:
parameters: [{name: project_id, in: path, required: true, schema: {type: string}}]
post: {security: [{ProjectBearer: []}], summary: Submit an idempotent job, responses: {'202': {description: accepted, content: {application/json: {schema: {$ref: '#/components/schemas/Job'}}}}}}
get: {security: [{ProjectBearer: []}], summary: List jobs, parameters: [{name: limit, in: query, schema: {type: integer}}, {name: cursor, in: query, schema: {type: string}}, {name: status, in: query, schema: {type: string}}], responses: {'200': {description: jobs}}}
/v1/projects/{project_id}/agents:
post: {security: [{ProjectBearer: []}], summary: Create a project-role-derived agent and its Markdown profile, requestBody: {required: true, content: {application/json: {schema: {type: object, required: [role,description,persistence], properties: {role: {type: string}, description: {type: string}, persistence: {type: string, enum: [temporal, permanent]}}}}}}, responses: {'201': {description: agent created}}}
get: {security: [{ProjectBearer: []}], summary: List project agents and profile paths, responses: {'200': {description: agents}}}
/v1/projects/{project_id}/memory:
post: {security: [{ProjectBearer: []}], summary: Append bridge-owned project recovery memory, requestBody: {required: true, content: {application/json: {schema: {type: object, required: [category,content], properties: {category: {type: string}, content: {type: string}, source: {type: string}}}}}}, responses: {'201': {description: entry created}}}
get: {security: [{ProjectBearer: []}], summary: Read project recovery memory, parameters: [{name: limit, in: query, schema: {type: integer}}], responses: {'200': {description: entries}}}
/v1/projects/{project_id}/continuity-packs:
post: {security: [{ProjectBearer: []}], summary: Create an authenticated encrypted local continuity pack, responses: {'201': {description: pack created}, '500': {description: recovery key unavailable or integrity failure}}}
/v1/projects/{project_id}/continuity-packs/{pack_hash}/recover:
post: {security: [{ProjectBearer: []}], summary: Verify and reconstruct a read-only recovery workspace without dispatching work, parameters: [{name: pack_hash, in: path, required: true, schema: {type: string}}], responses: {'200': {description: resume briefing}, '500': {description: corrupt or unauthenticated pack}}}
/v1/projects/{project_id}/continuity-packs/{pack_hash}/delivery-consents:
post: {security: [{ProjectBearer: []}], summary: Create the exact consent required to send an encrypted pack to configured private Git, responses: {'201': {description: pending recovery-upload consent}}}
/v1/projects/{project_id}/continuity-packs/{pack_hash}/deliver:
post: {security: [{ProjectBearer: []}], summary: Send an approved encrypted pack and manifest to the configured private Git recovery branch, responses: {'200': {description: verified remote receipt}}}
/v1/projects/{project_id}/recovery-drill:
post: {security: [{ProjectBearer: []}], summary: Create, verify, and recover a test continuity pack without dispatching work, responses: {'200': {description: drill result}}}
/v1/projects/{project_id}/timeline:
get: {security: [{ProjectBearer: []}], summary: Cursor-paginated append-only decision timeline, parameters: [{name: after, in: query, schema: {type: integer}}, {name: category, in: query, schema: {type: string}}, {name: job_id, in: query, schema: {type: string}}], responses: {'200': {description: ordered events}}}
/v1/projects/{project_id}/policy/simulate:
post: {security: [{ProjectBearer: []}], summary: Non-mutating capability, consent, quota, and retention-policy simulation, responses: {'200': {description: simulation result}}}
/v1/projects/{project_id}/outbound-submissions:
post: {security: [{ProjectBearer: []}], summary: Create an immutable pending-consent outbound manifest; performs no delivery, responses: {'201': {description: pending submission manifest}}}
/v1/projects/{project_id}/improvement-proposals:
post: {security: [{ProjectBearer: []}], summary: Create a reviewable improvement proposal and pending consent; never modifies source, responses: {'201': {description: proposal only}}}
/v1/projects/{project_id}/jobs/{job_id}/events:
get: {security: [{ProjectBearer: []}], summary: Stream job events with Server-Sent Events, responses: {'200': {description: event stream, content: {text/event-stream: {schema: {type: string}}}}}}
/v1/projects/{project_id}/jobs/{job_id}:
get: {security: [{ProjectBearer: []}], summary: Inspect one job, responses: {'200': {description: job, content: {application/json: {schema: {$ref: '#/components/schemas/Job'}}}}, '404': {description: missing}}}
/v1/projects/{project_id}/jobs/{job_id}/approve:
post: {security: [{ProjectBearer: []}], summary: Approve a pending job, responses: {'200': {description: approved job}, '404': {description: job is not awaiting approval}}}
/v1/projects/{project_id}/jobs/{job_id}/cancel:
post: {security: [{ProjectBearer: []}], summary: Cancel a queued or leased job, responses: {'200': {description: cancelled job}, '404': {description: job cannot be cancelled}}}
/v1/projects/{project_id}/jobs/{job_id}/artifacts:
post: {security: [{ProjectBearer: []}], summary: Store an artifact body, requestBody: {required: true, content: {application/octet-stream: {schema: {type: string, format: binary}}}}, responses: {'201': {description: stored artifact, content: {application/json: {schema: {$ref: '#/components/schemas/Artifact'}}}}}}
get: {security: [{ProjectBearer: []}], summary: List a job's artifact metadata, responses: {'200': {description: artifacts}}}
/v1/projects/{project_id}/artifacts/{artifact_id}:
get: {security: [{ProjectBearer: []}], summary: Inspect artifact metadata, responses: {'200': {description: artifact, content: {application/json: {schema: {$ref: '#/components/schemas/Artifact'}}}}}}
/v1/projects/{project_id}/artifacts/{artifact_id}/content:
get: {security: [{ProjectBearer: []}], summary: Download artifact bytes, responses: {'200': {description: artifact content, content: {application/octet-stream: {schema: {type: string, format: binary}}}}}}
/v1/metrics:
get: {summary: Local queue, acknowledgement queue, quarantine, and worker-health metrics, responses: {'200': {description: metrics}}}
/v1/projects/{project_id}/workers/{worker_id}/lease:
post: {security: [{ProjectBearer: []}], summary: Atomically lease one ready job, responses: {'200': {description: lease}, '204': {description: no job}}}