openapi: 3.1.0 info: title: Metallo Site Coordination protocol version: 1.0.0 description: >- Owned by Site Coordination. Adapted from architecture-reset 48e60e542 contracts/integration-ports/src/site/protocol. Runtime programs authenticate with short-lived workload credentials over HTTPS. Drafts reuse the owned 1C instruction without sequence; Site Coordination allocates it. Outcomes record command handling; observation results carry the reported workload state. servers: - url: https://site-coordination.example.invalid security: - runtimeBearer: [] paths: /v1/site/commands/pull: post: operationId: pullSiteCommands description: >- Read ordered commands belonging to the authenticated Runtime's configured environment. The caller cannot choose an environment in this request. Reading commands never acknowledges them. requestBody: required: true content: application/json: schema: {$ref: '#/components/schemas/PullRequest'} responses: '200': description: Ordered command page, including the durable acknowledgement cursor. content: application/json: schema: {$ref: '#/components/schemas/CommandPull'} '401': {$ref: '#/components/responses/Unauthorised'} '403': {$ref: '#/components/responses/Forbidden'} '413': {$ref: '#/components/responses/TooLarge'} '415': {$ref: '#/components/responses/UnsupportedMediaType'} '422': {$ref: '#/components/responses/InvalidRequest'} '503': {$ref: '#/components/responses/Unavailable'} default: {$ref: '#/components/responses/Failure'} /v1/site/commands/acknowledge: post: operationId: acknowledgeSiteCommand description: >- Record handling of a command for the authenticated Runtime's environment. The outcome must match the stored command's identity and next sequence. Repeating the same outcome is safe. Observation results report workload state. requestBody: required: true content: application/json: schema: {$ref: '#/components/schemas/CommandOutcome'} responses: '200': description: The outcome was recorded or replayed without repeating the write. content: application/json: schema: {$ref: '#/components/schemas/AcknowledgementResponse'} '401': {$ref: '#/components/responses/Unauthorised'} '403': {$ref: '#/components/responses/Forbidden'} '404': {$ref: '#/components/responses/NotFound'} '409': {$ref: '#/components/responses/Conflict'} '413': {$ref: '#/components/responses/TooLarge'} '415': {$ref: '#/components/responses/UnsupportedMediaType'} '422': {$ref: '#/components/responses/InvalidRequest'} '503': {$ref: '#/components/responses/Unavailable'} default: {$ref: '#/components/responses/Failure'} /v1/site/commands/progress: post: operationId: reportSiteCommandProgress description: >- Say why the authenticated Runtime's next pending command cannot be handled yet, with a catalogued code and never provider text (#1053). The command stays pending. Each report has its own ID, and repeating a report with the same ID is safe. A report for a command that is already acknowledged, or is not the next one, is refused, and so is a report whose reportedAt is older than the newest one recorded for that command (report_superseded): a delayed request never replaces a newer reason. Reports are best effort: the Runtime never waits for one. requestBody: required: true content: application/json: schema: {$ref: '#/components/schemas/CommandProgress'} responses: '200': description: The report was recorded or replayed without repeating the write. content: application/json: schema: {$ref: '#/components/schemas/ProgressResponse'} '401': {$ref: '#/components/responses/Unauthorised'} '403': {$ref: '#/components/responses/Forbidden'} '404': {$ref: '#/components/responses/NotFound'} '409': {$ref: '#/components/responses/Conflict'} '413': {$ref: '#/components/responses/TooLarge'} '415': {$ref: '#/components/responses/UnsupportedMediaType'} '422': {$ref: '#/components/responses/InvalidRequest'} '503': {$ref: '#/components/responses/Unavailable'} default: {$ref: '#/components/responses/Failure'} /v1/site/commands/run-logs/{commandId}: post: operationId: uploadRunLogs description: >- Send the logs that a read command asked for (#1056). The body is the encoded Log lines of one read, up to 5 MiB, and the headers say when they were captured and whether they were cut. The command must be the authenticated Runtime's own, of a read-logs kind and not yet answered, and the permit it names must still run, or the upload is refused. A command uploads one copy: the same body again is a replay, and a different one is refused (log_copy_conflict). A late upload from an older command leaves a newer copy alone (superseded). The text is kept for the viewer of the permit and never travels in a command's result, which cannot exceed 64 KiB. The command is answered by its own acknowledgement after the upload. parameters: - name: commandId in: path required: true schema: {$ref: 'site-execution.v1.yaml#/components/schemas/Id'} - name: Pharos-Log-Captured-At in: header required: true description: When the Runtime read the logs. schema: {$ref: 'site-execution.v1.yaml#/components/schemas/Timestamp'} - name: Pharos-Log-Truncated in: header required: true description: Whether older text was left out so that the body fits. schema: {type: string, enum: ['true', 'false']} requestBody: required: true content: application/json: schema: {$ref: '#/components/schemas/RunLogLines'} responses: '200': description: The copy was stored, was a replay of the stored one, or was older than the stored one. content: application/json: schema: {$ref: '#/components/schemas/RunLogUploadResponse'} '401': {$ref: '#/components/responses/Unauthorised'} '403': {$ref: '#/components/responses/Forbidden'} '404': {$ref: '#/components/responses/NotFound'} '409': {$ref: '#/components/responses/Conflict'} '413': {$ref: '#/components/responses/TooLarge'} '415': {$ref: '#/components/responses/UnsupportedMediaType'} '422': {$ref: '#/components/responses/InvalidRequest'} '503': {$ref: '#/components/responses/Unavailable'} default: {$ref: '#/components/responses/Failure'} /v1/site/commands/run-logs/final/{runId}/{attemptId}: post: operationId: uploadFinalRunLogs description: >- Send the final logs of a failed service (#1056). Before the Runtime removes a Deployment or KServe service that failed, it reads at most 1 MiB of its log text and sends the encoded Log lines here once, with the headers of uploadRunLogs. No command asks for them: the Run and Attempt must be a started service of the authenticated Runtime's environment that has not ended, or the upload is refused (log_attempt_ended once it has ended). The first copy is kept for seven days and answers the Run's logs route after the service is gone; the same body again is a replay, and any later copy is ignored (superseded). A Runtime that cannot send it removes the service all the same. parameters: - name: runId in: path required: true schema: {$ref: 'site-execution.v1.yaml#/components/schemas/Id'} - name: attemptId in: path required: true schema: {$ref: 'site-execution.v1.yaml#/components/schemas/Id'} - name: Pharos-Log-Captured-At in: header required: true description: When the Runtime read the logs. schema: {$ref: 'site-execution.v1.yaml#/components/schemas/Timestamp'} - name: Pharos-Log-Truncated in: header required: true description: Whether older text was left out so that the body fits. schema: {type: string, enum: ['true', 'false']} requestBody: required: true content: application/json: schema: {$ref: '#/components/schemas/RunLogLines'} responses: '200': description: The copy was stored, was a replay of the stored one, or arrived after it. content: application/json: schema: {$ref: '#/components/schemas/RunLogUploadResponse'} '401': {$ref: '#/components/responses/Unauthorised'} '403': {$ref: '#/components/responses/Forbidden'} '409': {$ref: '#/components/responses/Conflict'} '413': {$ref: '#/components/responses/TooLarge'} '415': {$ref: '#/components/responses/UnsupportedMediaType'} '422': {$ref: '#/components/responses/InvalidRequest'} '503': {$ref: '#/components/responses/Unavailable'} default: {$ref: '#/components/responses/Failure'} components: securitySchemes: runtimeBearer: type: http scheme: bearer bearerFormat: short-lived workload token description: >- Credential for the Runtime program, scoped to one configured environment. Kubernetes Runtimes use projected ServiceAccount tokens with the site-control audience. Human login tokens and provider execution credentials are separate. responses: Unauthorised: description: Missing, invalid or expired Runtime credential. headers: WWW-Authenticate: schema: {type: string, const: Bearer} content: &error_content application/json: schema: {$ref: '#/components/schemas/ProtocolError'} Forbidden: description: The verified Runtime identity cannot access this environment. content: *error_content TooLarge: description: The request exceeds the configured body limit. content: *error_content UnsupportedMediaType: description: The request must use application/json. content: *error_content InvalidRequest: description: Invalid command, outcome or pagination input. content: *error_content NotFound: description: The command does not exist in the authenticated environment. content: *error_content Conflict: description: The outcome conflicts with recorded evidence or skips a sequence. content: *error_content Unavailable: description: Authentication authority or persistence is temporarily unavailable. content: *error_content Failure: description: The request failed without exposing credentials or internal details. content: *error_content schemas: PullRequest: type: object additionalProperties: false properties: afterSequence: {type: integer, minimum: 0, maximum: 9007199254740991, default: 0} limit: {type: integer, minimum: 1, maximum: 100, default: 50} AcknowledgementResponse: type: object additionalProperties: false required: [kind, acknowledgedSequence] properties: kind: {type: string, enum: [recorded, replayed]} acknowledgedSequence: {type: integer, minimum: 0, maximum: 9007199254740991} CommandProgress: type: object additionalProperties: false required: [reportId, commandId, environment, sequence, reportedAt, code] properties: reportId: {$ref: 'site-execution.v1.yaml#/components/schemas/Id'} commandId: {$ref: 'site-execution.v1.yaml#/components/schemas/Id'} environment: {$ref: 'site-execution.v1.yaml#/components/schemas/ExecutionEnvironment'} sequence: {type: integer, minimum: 1, maximum: 9007199254740991} reportedAt: {$ref: 'site-execution.v1.yaml#/components/schemas/Timestamp'} code: &waiting_code {type: string, pattern: '^[a-z][a-z0-9-]{0,63}$'} ProgressResponse: type: object additionalProperties: false required: [kind] properties: kind: {type: string, enum: [recorded, replayed]} RunLogLines: type: array maxItems: 100000 items: {$ref: '#/components/schemas/RunLogLine'} RunLogLine: type: object additionalProperties: false required: [timestamp, level, message, source_status] properties: timestamp: {$ref: 'site-execution.v1.yaml#/components/schemas/Timestamp'} level: {type: string, enum: [info, warning, error]} message: {type: string} source_status: {type: string, pattern: '^[A-Z][A-Z_]{0,31}$'} RunLogUploadResponse: type: object additionalProperties: false required: [kind] properties: kind: {type: string, enum: [stored, replayed, superseded]} ProtocolError: type: object additionalProperties: false required: [code] properties: code: {type: string, pattern: '^[a-z][a-z_]{0,63}$'} BatchStartDraft: &draft_schema type: object additionalProperties: false required: &draft_required [commandId, environment, issuedAt, kind, payload, contractVersion, deliveryMetadata] properties: &draft commandId: {$ref: 'site-execution.v1.yaml#/components/schemas/Id'} environment: {$ref: 'site-execution.v1.yaml#/components/schemas/ExecutionEnvironment'} issuedAt: {$ref: 'site-execution.v1.yaml#/components/schemas/Timestamp'} contractVersion: {type: string, const: site-execution.pharos-platform.eu/v1} deliveryMetadata: {$ref: 'site-execution.v1.yaml#/components/schemas/DeliveryMetadata'} kind: {type: string, const: queued-batch.start} payload: {$ref: 'site-execution.v1.yaml#/components/schemas/BatchStart'} SessionStartDraft: <<: *draft_schema properties: <<: *draft kind: {type: string, const: interactive-session.start} payload: {$ref: 'site-execution.v1.yaml#/components/schemas/SessionStart'} ServiceStartDraft: <<: *draft_schema properties: <<: *draft kind: {type: string, const: reconciled-service.apply} payload: {$ref: 'site-execution.v1.yaml#/components/schemas/ServiceStart'} BatchCancelDraft: <<: *draft_schema properties: <<: *draft kind: {type: string, const: queued-batch.cancel} payload: {$ref: 'site-execution.v1.yaml#/components/schemas/Cancel'} SessionCancelDraft: <<: *draft_schema properties: <<: *draft kind: {type: string, const: interactive-session.terminate} payload: {$ref: 'site-execution.v1.yaml#/components/schemas/Cancel'} ServiceCancelDraft: <<: *draft_schema properties: <<: *draft kind: {type: string, const: reconciled-service.terminate} payload: {$ref: 'site-execution.v1.yaml#/components/schemas/Cancel'} BatchPrepareDraft: <<: *draft_schema properties: {<<: *draft, kind: {type: string, const: queued-batch.prepare}, payload: {$ref: 'site-execution.v1.yaml#/components/schemas/BatchPrepare'}} BatchObserveDraft: <<: *draft_schema properties: {<<: *draft, kind: {type: string, const: queued-batch.observe}, payload: {$ref: 'site-execution.v1.yaml#/components/schemas/NativeTarget'}} SessionPrepareSourceDraft: <<: *draft_schema properties: {<<: *draft, kind: {type: string, const: interactive-session.prepare-source}, payload: {$ref: 'site-execution.v1.yaml#/components/schemas/SessionPrepare'}} SessionObserveDraft: <<: *draft_schema properties: {<<: *draft, kind: {type: string, const: interactive-session.observe}, payload: {$ref: 'site-execution.v1.yaml#/components/schemas/NativeTarget'}} ServiceObserveDraft: <<: *draft_schema properties: {<<: *draft, kind: {type: string, const: reconciled-service.observe}, payload: {$ref: 'site-execution.v1.yaml#/components/schemas/NativeTarget'}} BatchReadLogsDraft: <<: *draft_schema properties: {<<: *draft, kind: {type: string, const: queued-batch.read-logs}, payload: {$ref: 'site-execution.v1.yaml#/components/schemas/ReadLogs'}} ServiceReadLogsDraft: <<: *draft_schema properties: {<<: *draft, kind: {type: string, const: reconciled-service.read-logs}, payload: {$ref: 'site-execution.v1.yaml#/components/schemas/ReadLogs'}} CommandDraft: oneOf: - {$ref: '#/components/schemas/BatchStartDraft'} - {$ref: '#/components/schemas/SessionStartDraft'} - {$ref: '#/components/schemas/ServiceStartDraft'} - {$ref: '#/components/schemas/BatchCancelDraft'} - {$ref: '#/components/schemas/SessionCancelDraft'} - {$ref: '#/components/schemas/ServiceCancelDraft'} - {$ref: '#/components/schemas/BatchPrepareDraft'} - {$ref: '#/components/schemas/BatchObserveDraft'} - {$ref: '#/components/schemas/SessionPrepareSourceDraft'} - {$ref: '#/components/schemas/SessionObserveDraft'} - {$ref: '#/components/schemas/ServiceObserveDraft'} - {$ref: '#/components/schemas/BatchReadLogsDraft'} - {$ref: '#/components/schemas/ServiceReadLogsDraft'} discriminator: {propertyName: kind} StartResult: description: The provider reference assigned when start/apply was handled; no workload state is implied. type: object additionalProperties: false required: [providerRunReference] properties: providerRunReference: {$ref: 'site-execution.v1.yaml#/components/schemas/Text'} CancelResult: description: Accepted means the cancellation was handled; native termination may still be in progress. type: object additionalProperties: false required: [status] properties: status: {type: string, enum: [accepted, already-terminal, not-found]} PrepareResult: type: object additionalProperties: false required: [fulfilmentReference] properties: fulfilmentReference: {$ref: 'site-execution.v1.yaml#/components/schemas/Text'} SourcePreparationResult: type: object additionalProperties: false required: [sourceMounts] properties: sourceMounts: type: array items: type: object additionalProperties: false required: [logicalName, repositoryId, mountBinding] properties: logicalName: {$ref: 'site-execution.v1.yaml#/components/schemas/Text'} repositoryId: {$ref: 'site-execution.v1.yaml#/components/schemas/Id'} branch: {$ref: 'site-execution.v1.yaml#/components/schemas/Branch'} mountBinding: {$ref: 'site-execution.v1.yaml#/components/schemas/Text'} BatchObservation: type: object additionalProperties: false required: [state, observedAt, providerRunReference] properties: <<: &observation state: {type: string, enum: [queued, running, succeeded, failed, cancelled, rejected]} observedAt: {$ref: 'site-execution.v1.yaml#/components/schemas/Timestamp'} providerRunReference: {$ref: 'site-execution.v1.yaml#/components/schemas/Text'} cleanupComplete: type: boolean const: true description: >- Sent only with a terminal state, once the Site has finished the Attempt's stage-out and cleanup: nothing native acts as the member for it any more. Execution Core then releases the account the accepted work held (#766). Absent means the cleanup is not confirmed. failureCode: type: string enum: [member-consent-missing, member-consent-lost, member-consent-expired, member-consent-revoked] description: >- A terminal member consent refusal. Sent only with state failed and cleanupComplete true. The member must sign in again before starting another Run; this Attempt is never retried. failure: $ref: '#/components/schemas/BatchFailure' waiting: <<: *waiting_code description: >- Why the native workload is not running yet, for example that it cannot be scheduled or its image cannot be pulled: a catalogued code, never provider text, and only with a state that is not terminal (#1053). BatchFailure: type: object additionalProperties: false required: [stage, nativeState, jobId] description: >- Bounded native failure facts, only with failed state and completed cleanup. The failing stage is separate from the scheduler state and exit evidence. Missing exit fields mean unknown; zero is preserved, including when the scheduler marks the job failed. No stdout, stderr or provider message. properties: stage: {type: string, enum: [stage-in, compute, stage-out]} nativeState: {type: string, minLength: 1, maxLength: 64, pattern: '^[^\x00-\x1f\x7f]+$'} jobId: {type: string, minLength: 1, maxLength: 128, pattern: '^[^\x00-\x1f\x7f]+$'} exitCode: {type: integer, minimum: 0, maximum: 255} signal: {type: integer, minimum: 0, maximum: 127} derivedExitCode: {type: integer, minimum: 0, maximum: 255} derivedSignal: {type: integer, minimum: 0, maximum: 127} AccessEndpoint: type: object additionalProperties: false required: [address] properties: address: {type: string, minLength: 1, maxLength: 2048, pattern: '^\S(?:[\s\S]*\S)?$'} SessionObservation: type: object additionalProperties: false required: [state, observedAt, providerRunReference, suspension] properties: <<: *observation state: {type: string, enum: [starting, ready, idle, suspending, suspended, resuming, failed, terminated, rejected]} suspension: {type: string, enum: [supported, unsupported]} accessEndpoint: {$ref: '#/components/schemas/AccessEndpoint'} lastActivityAt: {$ref: 'site-execution.v1.yaml#/components/schemas/Timestamp'} ServiceObservation: type: object additionalProperties: false required: [state, observedAt, providerRunReference, appliedRevision, desiredReplicas, readyReplicas] properties: <<: *observation state: {type: string, enum: [reconciling, healthy, degraded, failed, terminated, rejected]} appliedRevision: {$ref: 'site-execution.v1.yaml#/components/schemas/Text'} desiredReplicas: {type: integer, minimum: 0, maximum: 9007199254740991} readyReplicas: {type: integer, minimum: 0, maximum: 9007199254740991} cleanupComplete: type: boolean const: true description: >- Sent only with a terminal state, after exact native service, endpoint, temporary helpers, repository credentials and ephemeral workspace cleanup have completed. Absent means the cleanup is not confirmed and accepted work must retain its account. endpoint: type: object additionalProperties: false required: [address, portName] properties: address: {type: string, minLength: 1, maxLength: 2048, pattern: '^\S(?:[\s\S]*\S)?$'} portName: {$ref: 'site-execution.v1.yaml#/components/schemas/Text'} waiting: <<: *waiting_code description: >- Why the native workload is not running yet, for example that it cannot be scheduled or its image cannot be pulled: a catalogued code, never provider text, and only with a state that is not terminal (#1053). ReadLogsResult: description: >- What was read and never the text: the text went to the upload route of this command, and a result cannot exceed 64 KiB. `digest` is the SHA-256 of the body that was uploaded. type: object additionalProperties: false required: [byteCount, truncated, capturedAt, digest] properties: byteCount: {type: integer, minimum: 0, maximum: 5242880} truncated: {type: boolean} capturedAt: {$ref: 'site-execution.v1.yaml#/components/schemas/Timestamp'} digest: {type: string, pattern: '^[0-9a-f]{64}$'} StartSucceeded: &succeeded type: object additionalProperties: false required: &succeeded_required [commandId, environment, sequence, kind, completedAt, runId, attemptId, memberId, projectId, status, result] properties: <<: &correlation commandId: {$ref: 'site-execution.v1.yaml#/components/schemas/Id'} environment: {$ref: 'site-execution.v1.yaml#/components/schemas/ExecutionEnvironment'} sequence: {type: integer, minimum: 1, maximum: 9007199254740991} completedAt: {$ref: 'site-execution.v1.yaml#/components/schemas/Timestamp'} runId: {$ref: 'site-execution.v1.yaml#/components/schemas/Id'} attemptId: {$ref: 'site-execution.v1.yaml#/components/schemas/Id'} memberId: {$ref: 'site-execution.v1.yaml#/components/schemas/Id'} projectId: {$ref: 'site-execution.v1.yaml#/components/schemas/Id'} kind: {type: string, enum: [queued-batch.start, interactive-session.start, reconciled-service.apply]} status: {type: string, const: succeeded} result: {$ref: '#/components/schemas/StartResult'} CancelSucceeded: type: object additionalProperties: false required: *succeeded_required properties: <<: *correlation kind: {type: string, enum: [queued-batch.cancel, interactive-session.terminate, reconciled-service.terminate]} status: {type: string, const: succeeded} result: {$ref: '#/components/schemas/CancelResult'} PrepareSucceeded: <<: *succeeded properties: <<: *correlation kind: {type: string, const: queued-batch.prepare} status: {type: string, const: succeeded} result: {$ref: '#/components/schemas/PrepareResult'} SourcePreparationSucceeded: <<: *succeeded properties: <<: *correlation kind: {type: string, const: interactive-session.prepare-source} status: {type: string, const: succeeded} result: {$ref: '#/components/schemas/SourcePreparationResult'} BatchObservationSucceeded: <<: *succeeded properties: <<: *correlation kind: {type: string, const: queued-batch.observe} status: {type: string, const: succeeded} result: {$ref: '#/components/schemas/BatchObservation'} SessionObservationSucceeded: <<: *succeeded properties: <<: *correlation kind: {type: string, const: interactive-session.observe} status: {type: string, const: succeeded} result: {$ref: '#/components/schemas/SessionObservation'} ServiceObservationSucceeded: <<: *succeeded properties: <<: *correlation kind: {type: string, const: reconciled-service.observe} status: {type: string, const: succeeded} result: {$ref: '#/components/schemas/ServiceObservation'} BatchReadLogsSucceeded: <<: *succeeded properties: <<: *correlation kind: {type: string, const: queued-batch.read-logs} status: {type: string, const: succeeded} result: {$ref: '#/components/schemas/ReadLogsResult'} ServiceReadLogsSucceeded: <<: *succeeded properties: <<: *correlation kind: {type: string, const: reconciled-service.read-logs} status: {type: string, const: succeeded} result: {$ref: '#/components/schemas/ReadLogsResult'} CommandFailed: type: object additionalProperties: false required: [commandId, environment, sequence, kind, completedAt, runId, attemptId, memberId, projectId, status, errorCode] properties: <<: *correlation kind: {type: string, enum: [queued-batch.start, interactive-session.start, reconciled-service.apply, queued-batch.cancel, interactive-session.terminate, reconciled-service.terminate, queued-batch.prepare, queued-batch.observe, interactive-session.prepare-source, interactive-session.observe, reconciled-service.observe, queued-batch.read-logs, reconciled-service.read-logs]} status: {type: string, const: failed} errorCode: {type: string, pattern: '^[a-z][a-z0-9-]{0,63}$'} CommandOutcome: oneOf: - {$ref: '#/components/schemas/StartSucceeded'} - {$ref: '#/components/schemas/CancelSucceeded'} - {$ref: '#/components/schemas/CommandFailed'} - {$ref: '#/components/schemas/PrepareSucceeded'} - {$ref: '#/components/schemas/SourcePreparationSucceeded'} - {$ref: '#/components/schemas/BatchObservationSucceeded'} - {$ref: '#/components/schemas/SessionObservationSucceeded'} - {$ref: '#/components/schemas/ServiceObservationSucceeded'} - {$ref: '#/components/schemas/BatchReadLogsSucceeded'} - {$ref: '#/components/schemas/ServiceReadLogsSucceeded'} CommandPull: description: >- Commands are ordered and contiguous within a page and belong to one environment. An explicit read cursor may page ahead of acknowledgedSequence. nextAfterSequence is required exactly when hasMore is true and equals the final returned sequence. Reading a page never acknowledges a command. type: object additionalProperties: false required: [acknowledgedSequence, commands, hasMore] properties: acknowledgedSequence: {type: integer, minimum: 0, maximum: 9007199254740991} commands: type: array items: {$ref: 'site-execution.v1.yaml#/components/schemas/ExecutionCommand'} hasMore: {type: boolean} nextAfterSequence: {type: integer, minimum: 1, maximum: 9007199254740991}