Specify fenced activation and source credential contracts
Define bounded reservation recovery, exact activation replay fencing, immutable source identities, and deployment-owned secret and target policy boundaries. Align the OpenAPI contract and add a split-store activation safety refinement.
This commit is contained in:
parent
3881514e4b
commit
033cfeaac6
1 changed files with 877 additions and 49 deletions
|
|
@ -257,6 +257,113 @@ paths:
|
|||
$ref: '#/components/responses/NotFound'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
/v1/source-credentials:
|
||||
post:
|
||||
operationId: createSourceCredential
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/IdempotencyKey'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SourceCredentialCreate'
|
||||
responses:
|
||||
'201':
|
||||
description: Credential metadata and first immutable version created, or the original response replayed.
|
||||
headers:
|
||||
Idempotency-Expires-At:
|
||||
$ref: '#/components/headers/IdempotencyExpiresAt'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SourceCredential'
|
||||
'409':
|
||||
$ref: '#/components/responses/IdempotencyConflict'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
get:
|
||||
operationId: listSourceCredentials
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/Cursor'
|
||||
- $ref: '#/components/parameters/Limit'
|
||||
responses:
|
||||
'200':
|
||||
description: Authorized credential metadata page without secret material.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SourceCredentialPage'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
/v1/source-credentials/{credential_id}:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/CredentialId'
|
||||
get:
|
||||
operationId: getSourceCredential
|
||||
responses:
|
||||
'200':
|
||||
description: Authorized credential metadata without secret material.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SourceCredential'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
/v1/source-credentials/{credential_id}/versions:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/CredentialId'
|
||||
post:
|
||||
operationId: rotateSourceCredential
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/IdempotencyKey'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SourceCredentialRotate'
|
||||
responses:
|
||||
'201':
|
||||
description: New immutable version created and selected for future grants, or the original response replayed.
|
||||
headers:
|
||||
Idempotency-Expires-At:
|
||||
$ref: '#/components/headers/IdempotencyExpiresAt'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SourceCredential'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'409':
|
||||
$ref: '#/components/responses/IdempotencyConflict'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
/v1/source-credentials/{credential_id}/revoke:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/CredentialId'
|
||||
post:
|
||||
operationId: revokeSourceCredential
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/IdempotencyKey'
|
||||
responses:
|
||||
'200':
|
||||
description: Credential revoked for future grants, or the original response replayed.
|
||||
headers:
|
||||
Idempotency-Expires-At:
|
||||
$ref: '#/components/headers/IdempotencyExpiresAt'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SourceCredential'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'409':
|
||||
$ref: '#/components/responses/IdempotencyConflict'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
/v1/deletions/{operation_id}:
|
||||
parameters:
|
||||
- name: operation_id
|
||||
|
|
@ -308,11 +415,13 @@ paths:
|
|||
$ref: '#/components/schemas/ClaimRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Allocated authority or replay of the same claim operation.
|
||||
description: Reserved and durably authorized attempt, or replay of the same claim operation. The authority is non-executable until activateAttempt succeeds.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ClaimResponse'
|
||||
'409':
|
||||
$ref: '#/components/responses/IdempotencyConflict'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
/internal/v1/attempts/{attempt_id}/renew:
|
||||
|
|
@ -335,6 +444,70 @@ paths:
|
|||
$ref: '#/components/responses/PreconditionFailed'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
/internal/v1/attempts/{attempt_id}/activate:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/AttemptId'
|
||||
post:
|
||||
operationId: activateAttempt
|
||||
security:
|
||||
- serviceAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AttemptActivateRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Exact authority activated, or a prior successful activation replayed while its lease and durable executing tuple still agree.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ActivationResponse'
|
||||
'409':
|
||||
$ref: '#/components/responses/PreconditionFailed'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
/internal/v1/attempts/{attempt_id}/resolved-source:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/AttemptId'
|
||||
post:
|
||||
operationId: pinAttemptResolvedSource
|
||||
security:
|
||||
- serviceAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AttemptResolvedSourceRequest'
|
||||
responses:
|
||||
'204':
|
||||
description: Resolved source pinned once, or the exact operation replayed.
|
||||
'409':
|
||||
$ref: '#/components/responses/PreconditionFailed'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
/internal/v1/attempts/{attempt_id}/checkpoint:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/AttemptId'
|
||||
post:
|
||||
operationId: persistAttemptCheckpoint
|
||||
security:
|
||||
- serviceAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AttemptCheckpointRequest'
|
||||
responses:
|
||||
'204':
|
||||
description: Validated checkpoint persisted, or the exact operation replayed.
|
||||
'409':
|
||||
$ref: '#/components/responses/PreconditionFailed'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
/internal/v1/attempts/{attempt_id}/progress:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/AttemptId'
|
||||
|
|
@ -422,6 +595,12 @@ components:
|
|||
required: true
|
||||
schema:
|
||||
$ref: '#/components/schemas/OpaqueId'
|
||||
CredentialId:
|
||||
name: credential_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/components/schemas/CredentialId'
|
||||
headers:
|
||||
IdempotencyExpiresAt:
|
||||
description: Earliest time at which the server may forget this key.
|
||||
|
|
@ -461,6 +640,14 @@ components:
|
|||
description: Internal stable principal identifier mapped to one exact OIDC issuer and subject pair.
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/OpaqueId'
|
||||
CredentialId:
|
||||
description: Owner-authorized source credential registry identifier.
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/OpaqueId'
|
||||
CredentialVersionId:
|
||||
description: Immutable application credential-version identifier; backend secret-store coordinates are never exposed.
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/OpaqueId'
|
||||
Target:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
|
|
@ -506,10 +693,15 @@ components:
|
|||
minLength: 1
|
||||
maxLength: 1024
|
||||
description: Exact attempt-scoped internal prefix selected by the service.
|
||||
HttpSource:
|
||||
ResultPath:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 4096
|
||||
description: NFC-normalized relative POSIX path validated under architecture.md.
|
||||
HttpSourceSubmission:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [kind, url]
|
||||
required: [kind, url, result_path]
|
||||
properties:
|
||||
kind:
|
||||
const: http
|
||||
|
|
@ -517,13 +709,16 @@ components:
|
|||
type: string
|
||||
format: uri
|
||||
maxLength: 8192
|
||||
credential_ref:
|
||||
type: string
|
||||
maxLength: 512
|
||||
ObjectStoreSource:
|
||||
writeOnly: true
|
||||
description: Sensitive execution input. Public projections expose only display_url.
|
||||
result_path:
|
||||
$ref: '#/components/schemas/ResultPath'
|
||||
credential_id:
|
||||
$ref: '#/components/schemas/CredentialId'
|
||||
ObjectStoreSourceSubmission:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [kind, store, bucket, key]
|
||||
required: [kind, store, bucket, key, result_path]
|
||||
properties:
|
||||
kind:
|
||||
const: object_store
|
||||
|
|
@ -542,10 +737,33 @@ components:
|
|||
version_id:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
credential_ref:
|
||||
type: string
|
||||
maxLength: 512
|
||||
HuggingFaceSource:
|
||||
result_path:
|
||||
$ref: '#/components/schemas/ResultPath'
|
||||
credential_id:
|
||||
$ref: '#/components/schemas/CredentialId'
|
||||
HuggingFaceSelection:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
minProperties: 1
|
||||
anyOf:
|
||||
- required: [paths]
|
||||
- required: [prefixes]
|
||||
properties:
|
||||
paths:
|
||||
type: array
|
||||
minItems: 1
|
||||
maxItems: 1024
|
||||
uniqueItems: true
|
||||
items:
|
||||
$ref: '#/components/schemas/ResultPath'
|
||||
prefixes:
|
||||
type: array
|
||||
minItems: 1
|
||||
maxItems: 1024
|
||||
uniqueItems: true
|
||||
items:
|
||||
$ref: '#/components/schemas/ResultPath'
|
||||
HuggingFaceSourceSubmission:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [kind, repository_type, repository_id, revision]
|
||||
|
|
@ -562,35 +780,102 @@ components:
|
|||
type: string
|
||||
minLength: 1
|
||||
maxLength: 256
|
||||
include:
|
||||
type: array
|
||||
maxItems: 1024
|
||||
uniqueItems: true
|
||||
items:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 1024
|
||||
credential_ref:
|
||||
type: string
|
||||
maxLength: 512
|
||||
Source:
|
||||
selection:
|
||||
$ref: '#/components/schemas/HuggingFaceSelection'
|
||||
credential_id:
|
||||
$ref: '#/components/schemas/CredentialId'
|
||||
SourceSubmission:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/HttpSource'
|
||||
- $ref: '#/components/schemas/ObjectStoreSource'
|
||||
- $ref: '#/components/schemas/HuggingFaceSource'
|
||||
- $ref: '#/components/schemas/HttpSourceSubmission'
|
||||
- $ref: '#/components/schemas/ObjectStoreSourceSubmission'
|
||||
- $ref: '#/components/schemas/HuggingFaceSourceSubmission'
|
||||
discriminator:
|
||||
propertyName: kind
|
||||
mapping:
|
||||
http: '#/components/schemas/HttpSource'
|
||||
object_store: '#/components/schemas/ObjectStoreSource'
|
||||
hugging_face: '#/components/schemas/HuggingFaceSource'
|
||||
http: '#/components/schemas/HttpSourceSubmission'
|
||||
object_store: '#/components/schemas/ObjectStoreSourceSubmission'
|
||||
hugging_face: '#/components/schemas/HuggingFaceSourceSubmission'
|
||||
HttpSourceProjection:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [kind, display_url, result_path]
|
||||
properties:
|
||||
kind:
|
||||
const: http
|
||||
display_url:
|
||||
type: string
|
||||
format: uri
|
||||
maxLength: 8192
|
||||
description: Sanitized URL without userinfo, sensitive query values, or fragments.
|
||||
result_path:
|
||||
$ref: '#/components/schemas/ResultPath'
|
||||
credential_id:
|
||||
$ref: '#/components/schemas/CredentialId'
|
||||
ObjectStoreSourceProjection:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [kind, store, bucket, key, result_path]
|
||||
properties:
|
||||
kind:
|
||||
const: object_store
|
||||
store:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 128
|
||||
bucket:
|
||||
type: string
|
||||
minLength: 3
|
||||
maxLength: 63
|
||||
key:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 4096
|
||||
version_id:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
result_path:
|
||||
$ref: '#/components/schemas/ResultPath'
|
||||
credential_id:
|
||||
$ref: '#/components/schemas/CredentialId'
|
||||
HuggingFaceSourceProjection:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [kind, repository_type, repository_id, revision]
|
||||
properties:
|
||||
kind:
|
||||
const: hugging_face
|
||||
repository_type:
|
||||
type: string
|
||||
enum: [model, dataset, space]
|
||||
repository_id:
|
||||
type: string
|
||||
pattern: '^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$'
|
||||
revision:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 256
|
||||
selection:
|
||||
$ref: '#/components/schemas/HuggingFaceSelection'
|
||||
credential_id:
|
||||
$ref: '#/components/schemas/CredentialId'
|
||||
SourceProjection:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/HttpSourceProjection'
|
||||
- $ref: '#/components/schemas/ObjectStoreSourceProjection'
|
||||
- $ref: '#/components/schemas/HuggingFaceSourceProjection'
|
||||
discriminator:
|
||||
propertyName: kind
|
||||
mapping:
|
||||
http: '#/components/schemas/HttpSourceProjection'
|
||||
object_store: '#/components/schemas/ObjectStoreSourceProjection'
|
||||
hugging_face: '#/components/schemas/HuggingFaceSourceProjection'
|
||||
JobSubmission:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [source]
|
||||
properties:
|
||||
source:
|
||||
$ref: '#/components/schemas/Source'
|
||||
$ref: '#/components/schemas/SourceSubmission'
|
||||
target:
|
||||
description: Fields omitted here are taken from the configured default target before authorization.
|
||||
$ref: '#/components/schemas/TargetOverride'
|
||||
|
|
@ -598,6 +883,97 @@ components:
|
|||
type: string
|
||||
minLength: 1
|
||||
maxLength: 200
|
||||
SourceCredentialKind:
|
||||
type: string
|
||||
enum: [basic_auth, bearer_token, http_headers, object_store, hugging_face]
|
||||
CredentialMaterial:
|
||||
type: object
|
||||
minProperties: 1
|
||||
maxProperties: 64
|
||||
additionalProperties:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 16384
|
||||
writeOnly: true
|
||||
description: Write-only adapter credential fields. Values are never returned or logged.
|
||||
SourceCredentialCreate:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [label, kind, material]
|
||||
properties:
|
||||
label:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 200
|
||||
kind:
|
||||
$ref: '#/components/schemas/SourceCredentialKind'
|
||||
material:
|
||||
$ref: '#/components/schemas/CredentialMaterial'
|
||||
SourceCredentialRotate:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [material]
|
||||
properties:
|
||||
material:
|
||||
$ref: '#/components/schemas/CredentialMaterial'
|
||||
SourceCredentialVersion:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [id, created_at]
|
||||
properties:
|
||||
id:
|
||||
$ref: '#/components/schemas/CredentialVersionId'
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
CredentialGrantVersion:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [credential_id, version_id]
|
||||
properties:
|
||||
credential_id:
|
||||
$ref: '#/components/schemas/CredentialId'
|
||||
version_id:
|
||||
$ref: '#/components/schemas/CredentialVersionId'
|
||||
SourceCredential:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [id, owner, label, kind, status, active_version, created_at, updated_at]
|
||||
properties:
|
||||
id:
|
||||
$ref: '#/components/schemas/CredentialId'
|
||||
owner:
|
||||
$ref: '#/components/schemas/PrincipalId'
|
||||
label:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 200
|
||||
kind:
|
||||
$ref: '#/components/schemas/SourceCredentialKind'
|
||||
status:
|
||||
type: string
|
||||
enum: [active, revoked]
|
||||
active_version:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/SourceCredentialVersion'
|
||||
- type: 'null'
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
SourceCredentialPage:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [items, next_cursor]
|
||||
properties:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SourceCredential'
|
||||
next_cursor:
|
||||
type: [string, 'null']
|
||||
JobPhase:
|
||||
type: string
|
||||
enum: [Queued, Running, RetryWait, Cancelling, Succeeded, Failed, Cancelled]
|
||||
|
|
@ -802,7 +1178,7 @@ components:
|
|||
lifecycle:
|
||||
$ref: '#/components/schemas/JobLifecycle'
|
||||
source:
|
||||
$ref: '#/components/schemas/Source'
|
||||
$ref: '#/components/schemas/SourceProjection'
|
||||
target:
|
||||
$ref: '#/components/schemas/Target'
|
||||
authoritative_attempt:
|
||||
|
|
@ -978,7 +1354,7 @@ components:
|
|||
ExecutionEnvelope:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [id, job_id, attempt_id, fence, worker_incarnation, source, resolved_source_ref, target, staging_target, execution_grant_ref, checkpoint_ref]
|
||||
required: [id, job_id, attempt_id, fence, worker_incarnation, source, sensitive_source_ref, credential_version, resolved_source_ref, target, staging_target, execution_grant_ref, checkpoint_ref]
|
||||
properties:
|
||||
id:
|
||||
$ref: '#/components/schemas/OpaqueId'
|
||||
|
|
@ -992,7 +1368,14 @@ components:
|
|||
worker_incarnation:
|
||||
$ref: '#/components/schemas/OpaqueId'
|
||||
source:
|
||||
$ref: '#/components/schemas/Source'
|
||||
$ref: '#/components/schemas/SourceProjection'
|
||||
sensitive_source_ref:
|
||||
description: Opaque grant-bound reference to the complete accepted source input; credential_version identifies any pinned credential separately.
|
||||
$ref: '#/components/schemas/OpaqueId'
|
||||
credential_version:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/CredentialGrantVersion'
|
||||
- type: 'null'
|
||||
resolved_source_ref:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/OpaqueId'
|
||||
|
|
@ -1016,6 +1399,15 @@ components:
|
|||
$ref: '#/components/schemas/AttemptAuthority'
|
||||
execution:
|
||||
$ref: '#/components/schemas/ExecutionEnvelope'
|
||||
ActivationResponse:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [authority, execution]
|
||||
properties:
|
||||
authority:
|
||||
$ref: '#/components/schemas/AttemptAuthority'
|
||||
execution:
|
||||
$ref: '#/components/schemas/ExecutionEnvelope'
|
||||
AttemptMutation:
|
||||
type: object
|
||||
required: [operation_id, fence, worker_incarnation]
|
||||
|
|
@ -1031,6 +1423,28 @@ components:
|
|||
unevaluatedProperties: false
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AttemptMutation'
|
||||
AttemptActivateRequest:
|
||||
unevaluatedProperties: false
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AttemptMutation'
|
||||
AttemptResolvedSourceRequest:
|
||||
unevaluatedProperties: false
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AttemptMutation'
|
||||
- type: object
|
||||
required: [resolved_source_ref]
|
||||
properties:
|
||||
resolved_source_ref:
|
||||
$ref: '#/components/schemas/OpaqueId'
|
||||
AttemptCheckpointRequest:
|
||||
unevaluatedProperties: false
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AttemptMutation'
|
||||
- type: object
|
||||
required: [checkpoint_ref]
|
||||
properties:
|
||||
checkpoint_ref:
|
||||
$ref: '#/components/schemas/OpaqueId'
|
||||
AttemptProgressRequest:
|
||||
unevaluatedProperties: false
|
||||
allOf:
|
||||
|
|
|
|||
Loading…
Reference in a new issue