fix recording timing and sync call contracts
management-images / build-and-publish (push) Successful in 34s

This commit is contained in:
2026-09-17 17:43:37 +08:00
parent fa6925010b
commit d17bbe15d9
6 changed files with 773 additions and 19 deletions
+12 -12
View File
@@ -263,6 +263,18 @@ components:
recordings: {type: array, items: {type: object}}
delivery: {type: object}
snapshot_at: {type: string, format: date-time}
Problem:
type: object
additionalProperties: false
required: [type, title, status, code, detail, request_id, retryable]
properties:
type: {type: string, format: uri-reference}
title: {type: string}
status: {type: integer}
code: {type: string}
detail: {type: string}
request_id: {type: string}
retryable: {type: boolean}
responses:
Unauthorized:
description: Unauthorized
@@ -289,15 +301,3 @@ components:
content:
application/problem+json:
schema: {$ref: '#/components/schemas/Problem'}
Problem:
type: object
additionalProperties: false
required: [type, title, status, code, detail, request_id, retryable]
properties:
type: {type: string, format: uri-reference}
title: {type: string}
status: {type: integer}
code: {type: string}
detail: {type: string}
request_id: {type: string}
retryable: {type: boolean}
+561
View File
@@ -1,3 +1,4 @@
---
# yaml-language-server: $schema=https://json-schema.org/draft/2020-12/schema
openapi: 3.1.0
info:
@@ -16,8 +17,12 @@ servers:
description: SaaS read-only service network
tags:
- name: health
- name: admin-providers
- name: admin-trunks
- name: admin-cells
- name: admin-status
- name: admin-statistics
- name: admin-audit
- name: saas-readonly
paths:
/healthz/live:
@@ -31,11 +36,69 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Health'
/admin/v1/providers:
get:
tags: [admin-providers]
operationId: listProviders
security: [{SipAdminBearer: []}]
responses:
'200':
description: Provider directory
content:
application/json:
schema: {$ref: '#/components/schemas/ProviderList'}
'401': {$ref: '#/components/responses/Unauthorized'}
/admin/v1/providers/{provider_id}:
parameters:
- {$ref: '#/components/parameters/ProviderId'}
get:
tags: [admin-providers]
operationId: getProvider
security: [{SipAdminBearer: []}]
responses:
'200':
description: Provider
content:
application/json:
schema: {$ref: '#/components/schemas/ProviderResponse'}
'401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'}
put:
tags: [admin-providers]
operationId: upsertProvider
security: [{SipAdminBearer: []}]
parameters:
- {$ref: '#/components/parameters/IfMatch'}
- {$ref: '#/components/parameters/RequestId'}
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/ProviderInput'}
responses:
'200':
description: Updated provider
content:
application/json:
schema: {$ref: '#/components/schemas/ProviderResponse'}
'201':
description: Created provider
content:
application/json:
schema: {$ref: '#/components/schemas/ProviderResponse'}
'400': {$ref: '#/components/responses/BadRequest'}
'401': {$ref: '#/components/responses/Unauthorized'}
'403': {$ref: '#/components/responses/Forbidden'}
'409': {$ref: '#/components/responses/Conflict'}
/admin/v1/trunks:
get:
tags: [admin-trunks]
operationId: listAdminTrunks
security: [{SipAdminBearer: []}]
parameters:
- {$ref: '#/components/parameters/ProviderFilter'}
- {$ref: '#/components/parameters/CellIdFilter'}
- {$ref: '#/components/parameters/TrunkStatusFilter'}
responses:
'200':
description: All Trunks, including unpublished revisions
@@ -88,6 +151,54 @@ paths:
'400': {$ref: '#/components/responses/BadRequest'}
'401': {$ref: '#/components/responses/Unauthorized'}
'409': {$ref: '#/components/responses/Conflict'}
/admin/v1/trunks/{trunk_id}/validate:
parameters:
- {$ref: '#/components/parameters/TrunkId'}
post:
tags: [admin-trunks]
operationId: validateTrunk
security: [{SipAdminBearer: []}]
requestBody:
required: false
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
revision: {type: integer, minimum: 1}
responses:
'200':
description: Validation issues, compatible Cells, and impact preview
content:
application/json:
schema: {type: object}
'401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'}
/admin/v1/trunks/{trunk_id}/verifications:
parameters:
- {$ref: '#/components/parameters/TrunkId'}
post:
tags: [admin-trunks]
operationId: addTrunkVerification
security: [{SipAdminBearer: []}]
parameters:
- {$ref: '#/components/parameters/IfMatch'}
- {$ref: '#/components/parameters/RequestId'}
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/VerificationRequest'
responses:
'200':
description: Versioned verification record
content:
application/json:
schema: {type: object}
'401': {$ref: '#/components/responses/Unauthorized'}
'409': {$ref: '#/components/responses/Conflict'}
/admin/v1/trunks/{trunk_id}/publish:
parameters:
- {$ref: '#/components/parameters/TrunkId'}
@@ -220,6 +331,18 @@ paths:
/admin/v1/cells/{cell_id}:
parameters:
- {$ref: '#/components/parameters/CellId'}
get:
tags: [admin-cells]
operationId: getCell
security: [{SipAdminBearer: []}]
responses:
'200':
description: Registered Cell
content:
application/json:
schema: {$ref: '#/components/schemas/Cell'}
'401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'}
put:
tags: [admin-cells]
operationId: registerCell
@@ -245,6 +368,206 @@ paths:
schema: {$ref: '#/components/schemas/Cell'}
'401': {$ref: '#/components/responses/Unauthorized'}
'409': {$ref: '#/components/responses/Conflict'}
/admin/v1/cells/{cell_id}/observations:
parameters:
- {$ref: '#/components/parameters/CellId'}
post:
tags: [admin-cells]
operationId: ingestCellObservation
security: [{SipAdminBearer: []}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/ObservationInput'}
responses:
'201':
description: >-
Observation accepted for the current boot and monotonic sequence
content:
application/json:
schema: {type: object}
'401': {$ref: '#/components/responses/Unauthorized'}
'409': {$ref: '#/components/responses/Conflict'}
/admin/v1/egress-pools:
get:
tags: [admin-cells]
operationId: listEgressPools
security: [{SipAdminBearer: []}]
responses:
'200':
description: Fixed egress pool directory
content:
application/json:
schema: {type: object}
'401': {$ref: '#/components/responses/Unauthorized'}
/admin/v1/sip-status:
get:
tags: [admin-status]
operationId: getSipStatus
security: [{SipAdminBearer: []}]
parameters:
- {$ref: '#/components/parameters/CellIdsFilter'}
- {$ref: '#/components/parameters/CellIdFilter'}
- {$ref: '#/components/parameters/ProviderFilter'}
responses:
'200':
description: >-
Cell/trunk status matrix with freshness and missing sources
content:
application/json:
schema: {$ref: '#/components/schemas/StatusResponse'}
'401': {$ref: '#/components/responses/Unauthorized'}
/admin/v1/cells/{cell_id}/sip-status:
parameters:
- {$ref: '#/components/parameters/CellId'}
get:
tags: [admin-status]
operationId: getCellSipStatus
security: [{SipAdminBearer: []}]
parameters:
- {$ref: '#/components/parameters/TrunkFilter'}
responses:
'200':
description: One Cell status
content:
application/json:
schema: {$ref: '#/components/schemas/StatusItem'}
'401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'}
/admin/v1/providers/{provider_id}/status:
parameters:
- {$ref: '#/components/parameters/ProviderId'}
get:
tags: [admin-status]
operationId: getProviderStatus
security: [{SipAdminBearer: []}]
responses:
'200':
description: Provider status matrix
content:
application/json:
schema: {$ref: '#/components/schemas/StatusResponse'}
'401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'}
/admin/v1/statistics/outbound/summary:
get:
tags: [admin-statistics]
operationId: getOutboundSummary
security: [{SipAdminBearer: []}]
parameters:
- {$ref: '#/components/parameters/From'}
- {$ref: '#/components/parameters/To'}
- {$ref: '#/components/parameters/StatsMode'}
- {$ref: '#/components/parameters/ProviderFilter'}
- {$ref: '#/components/parameters/TrunkFilter'}
- {$ref: '#/components/parameters/CellIdFilter'}
- {$ref: '#/components/parameters/EgressFilter'}
responses:
'200':
description: Cohort and interval metrics with completeness metadata
content:
application/json:
schema: {$ref: '#/components/schemas/StatisticsSummary'}
'401': {$ref: '#/components/responses/Unauthorized'}
'422': {$ref: '#/components/responses/BadRequest'}
/admin/v1/statistics/outbound/timeseries:
get:
tags: [admin-statistics]
operationId: getOutboundTimeseries
security: [{SipAdminBearer: []}]
parameters:
- {$ref: '#/components/parameters/From'}
- {$ref: '#/components/parameters/To'}
- {$ref: '#/components/parameters/StatsMode'}
- {$ref: '#/components/parameters/Granularity'}
- {$ref: '#/components/parameters/ProviderFilter'}
- {$ref: '#/components/parameters/TrunkFilter'}
- {$ref: '#/components/parameters/CellIdFilter'}
- {$ref: '#/components/parameters/EgressFilter'}
responses:
'200':
description: Bounded UTC time series
content:
application/json:
schema: {$ref: '#/components/schemas/TimeseriesResponse'}
'401': {$ref: '#/components/responses/Unauthorized'}
'422': {$ref: '#/components/responses/BadRequest'}
/admin/v1/call-attempts:
get:
tags: [admin-statistics]
operationId: listCallAttempts
security: [{SipAdminBearer: []}]
parameters:
- {$ref: '#/components/parameters/From'}
- {$ref: '#/components/parameters/To'}
- {$ref: '#/components/parameters/StatsMode'}
- {$ref: '#/components/parameters/ProviderFilter'}
- {$ref: '#/components/parameters/TrunkFilter'}
- {$ref: '#/components/parameters/CellIdFilter'}
- {$ref: '#/components/parameters/Limit'}
- {$ref: '#/components/parameters/Cursor'}
- {$ref: '#/components/parameters/EgressFilter'}
responses:
'200':
description: Redacted raw attempt facts
content:
application/json:
schema: {type: object}
'401': {$ref: '#/components/responses/Unauthorized'}
/admin/v1/call-attempts/{attempt_id}:
parameters:
- name: attempt_id
in: path
required: true
schema: {type: string}
get:
tags: [admin-statistics]
operationId: getCallAttempt
security: [{SipAdminBearer: []}]
responses:
'200':
description: One redacted attempt fact
content:
application/json:
schema: {type: object}
'401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'}
/admin/v1/audit:
get:
tags: [admin-audit]
operationId: listAudit
security: [{SipAdminBearer: []}]
parameters:
- {$ref: '#/components/parameters/Limit'}
- {$ref: '#/components/parameters/ResourceFilter'}
- {$ref: '#/components/parameters/RequestFilter'}
- {$ref: '#/components/parameters/ActorFilter'}
responses:
'200':
description: Immutable audit entries
content:
application/json:
schema: {type: object}
'401': {$ref: '#/components/responses/Unauthorized'}
/admin/v1/operations/{operation_id}:
parameters:
- name: operation_id
in: path
required: true
schema: {type: string}
get:
tags: [admin-audit]
operationId: getOperation
security: [{SipAdminBearer: []}]
responses:
'200':
description: Durable operation state for retry/recovery
content:
application/json:
schema: {type: object}
'401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'}
/readonly/v1/sip/trunks:
get:
tags: [saas-readonly]
@@ -290,6 +613,11 @@ components:
Dedicated SaaS read-only credential. It cannot publish, modify, disable,
rollback, or access Cell management.
parameters:
ProviderId:
name: provider_id
in: path
required: true
schema: {type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'}
TrunkId:
name: trunk_id
in: path
@@ -306,6 +634,81 @@ components:
required: true
description: Exact latest revision required for CAS; quotes are accepted.
schema: {type: integer, minimum: 0}
CellIdsFilter:
name: cell_ids
in: query
required: false
schema: {type: string}
CellIdFilter:
name: cell_id
in: query
required: false
schema: {type: string}
ProviderFilter:
name: provider_id
in: query
required: false
schema: {type: string}
TrunkFilter:
name: trunk_id
in: query
required: false
schema: {type: string}
TrunkStatusFilter:
name: status
in: query
required: false
schema: {type: string}
ResourceFilter:
name: resource_id
in: query
required: false
schema: {type: string}
ActorFilter:
name: actor
in: query
required: false
schema: {type: string}
From:
name: from
in: query
required: false
schema: {type: string, format: date-time}
To:
name: to
in: query
required: false
schema: {type: string, format: date-time}
StatsMode:
name: mode
in: query
required: false
schema: {type: string, enum: [mock, mixed, real]}
EgressFilter:
name: egress_pool_id
in: query
required: false
schema: {type: string}
Granularity:
name: granularity
in: query
required: false
schema: {type: string, enum: [minute, hour, day]}
Limit:
name: limit
in: query
required: false
schema: {type: integer, minimum: 1, maximum: 200}
Cursor:
name: cursor
in: query
required: false
schema: {type: string}
RequestFilter:
name: request_id
in: query
required: false
schema: {type: string}
RequestId:
name: X-Request-ID
in: header
@@ -341,6 +744,9 @@ components:
Mode:
type: string
enum: [mock, real]
StatsMode:
type: string
enum: [mock, mixed, real]
Health:
type: object
additionalProperties: false
@@ -374,10 +780,31 @@ components:
writeOnly: true
description: >-
Secret-store reference only; plaintext credentials are forbidden.
VerificationRequest:
type: object
additionalProperties: false
required: [revision, check_name, result]
properties:
revision: {type: integer, minimum: 1}
check_name:
type: string
enum:
- transport
- registration_auth
- caller_id_rules
- codec
- capacity
- whitelist
result:
type: string
enum: [confirmed, failed, unknown, not_applicable]
evidence_ref: {type: string, maxLength: 512}
checked_by: {type: string, maxLength: 128}
TrunkConfig:
type: object
additionalProperties: false
required:
- provider_id
- display_name
- enabled
- sip
@@ -388,6 +815,9 @@ components:
- max_concurrency
- max_cps
properties:
provider_id:
type: string
pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
display_name: {type: string, minLength: 1, maxLength: 256}
enabled: {type: boolean}
sip: {$ref: '#/components/schemas/SipConfig'}
@@ -432,6 +862,7 @@ components:
properties:
revision: {type: integer, minimum: 1}
state: {type: string, enum: [draft, publishing, published, superseded]}
config_sha256: {type: string, pattern: '^[a-f0-9]{64}$'}
created_at: {type: string, format: date-time}
created_by: {type: string}
AdminTrunk:
@@ -439,8 +870,10 @@ components:
required:
- mode
- trunk_id
- provider_id
- latest_revision
- active_revision
- active_status
- status
- compatible_cell_ids
- latest
@@ -449,8 +882,10 @@ components:
properties:
mode: {$ref: '#/components/schemas/Mode'}
trunk_id: {type: string}
provider_id: {type: string}
latest_revision: {type: integer, minimum: 1}
active_revision: {type: integer, minimum: 0}
active_status: {type: string, enum: [draft, published, disabled]}
status: {type: string, enum: [draft, published, disabled]}
updated_at: {type: string, format: date-time}
compatible_cell_ids: {type: array, items: {type: string}}
@@ -470,17 +905,20 @@ components:
asterisk_allow:
type: array
items: {type: string, enum: [alaw, ulaw]}
config_sha256: {type: string, pattern: '^[a-f0-9]{64}$'}
ReadonlyTrunk:
type: object
required:
- mode
- trunk_id
- provider_id
- revision
- status
- config
properties:
mode: {$ref: '#/components/schemas/Mode'}
trunk_id: {type: string}
provider_id: {type: string}
revision: {type: integer, minimum: 1}
status: {type: string, const: published}
updated_at: {type: string, format: date-time}
@@ -508,6 +946,10 @@ components:
cell_id: {type: string}
revision: {type: integer, minimum: 1}
config: {$ref: '#/components/schemas/CellConfig'}
cloud_instance_id: {type: string}
instance_name: {type: string}
region: {type: string}
boot_id: {type: string}
updated_at: {type: string, format: date-time}
updated_by: {type: string}
Publication:
@@ -519,6 +961,11 @@ components:
cell_id: {type: string}
status: {type: string, enum: [pending, applied, failed]}
error_code: {type: [string, 'null']}
applied_at: {type: [string, 'null'], format: date-time}
target_digest: {type: string}
local_revision: {type: integer, minimum: 0}
local_digest: {type: string}
operation_id: {type: string}
updated_at: {type: string, format: date-time}
AuditEntry:
type: object
@@ -552,6 +999,118 @@ components:
request_id: {type: [string, 'null']}
details_json: {type: string}
created_at: {type: string, format: date-time}
ProviderInput:
type: object
additionalProperties: false
required: [display_name, lifecycle]
properties:
display_name: {type: string, minLength: 1, maxLength: 256}
notes: {type: string, maxLength: 2000}
lifecycle: {type: string, enum: [active, archived]}
Provider:
allOf:
- {$ref: '#/components/schemas/ProviderInput'}
- type: object
required: [provider_id, revision, trunk_count, created_at, updated_at]
properties:
provider_id: {type: string}
revision: {type: integer, minimum: 1}
trunk_count: {type: integer, minimum: 0}
created_at: {type: string, format: date-time}
updated_at: {type: string, format: date-time}
updated_by: {type: string}
ProviderList:
type: object
required: [mode, providers]
properties:
mode: {$ref: '#/components/schemas/Mode'}
providers: {type: array, items: {$ref: '#/components/schemas/Provider'}}
ProviderResponse:
type: object
required: [mode, provider]
properties:
mode: {$ref: '#/components/schemas/Mode'}
provider: {$ref: '#/components/schemas/Provider'}
ObservationInput:
type: object
additionalProperties: false
required: [cell_id, boot_id, sequence, observed_at, source, states]
properties:
observation_id: {type: string}
cell_id: {type: string}
trunk_id: {type: string}
boot_id: {type: string, minLength: 1}
sequence: {type: integer, minimum: 1}
observed_at: {type: string, format: date-time}
source: {type: string}
config_revision: {type: integer, minimum: 0}
states: {type: object}
occupancy: {type: object}
sample_id: {type: string}
StatusItem:
type: object
required: [mode, cell_id, availability, complete, missing_sources]
properties:
mode: {$ref: '#/components/schemas/Mode'}
cell_id: {type: string}
availability: {type: string, enum: [healthy, stale, unknown, disabled]}
complete: {type: boolean}
observed_at: {type: [string, 'null'], format: date-time}
received_at: {type: [string, 'null'], format: date-time}
observation_age_seconds: {type: [integer, 'null'], minimum: 0}
clock_skew: {type: boolean}
reason: {type: string}
boot_id: {type: string}
sequence: {type: integer, minimum: 1}
config_revision: {type: integer, minimum: 1}
config_status: {type: string}
egress_pool_id: {type: string}
eligibility: {type: string}
missing_sources: {type: array, items: {type: string}}
states: {type: object}
occupancy: {type: object}
trunks: {type: array, items: {type: object}}
publication: {type: object}
StatusResponse:
type: object
required: [mode, complete, cells]
properties:
mode: {$ref: '#/components/schemas/Mode'}
complete: {type: boolean}
generated_at: {type: string, format: date-time}
data_as_of: {type: [string, 'null'], format: date-time}
coverage: {type: object}
cells: {type: array, items: {$ref: '#/components/schemas/StatusItem'}}
StatisticsSummary:
type: object
required: [mode, from, to, complete, metrics]
properties:
mode: {$ref: '#/components/schemas/StatsMode'}
from: {type: string, format: date-time}
to: {type: string, format: date-time}
timezone: {type: string}
definition_version: {type: string}
filters: {type: object}
complete: {type: boolean}
missing_sources: {type: array, items: {type: string}}
unresolved_count: {type: integer, minimum: 0}
data_as_of: {type: [string, 'null'], format: date-time}
metrics: {type: object}
failure_reasons: {type: array, items: {type: object}}
realtime: {type: object}
TimeseriesResponse:
type: object
required: [mode, from, to, granularity, complete, series]
properties:
mode: {$ref: '#/components/schemas/StatsMode'}
from: {type: string, format: date-time}
to: {type: string, format: date-time}
timezone: {type: string}
definition_version: {type: string}
filters: {type: object}
granularity: {type: string, enum: [minute, hour, day]}
complete: {type: boolean}
series: {type: array, items: {type: object}}
ErrorResponse:
type: object
required: [error]
@@ -562,3 +1121,5 @@ components:
properties:
code: {type: string}
message: {type: string}
fields: {type: object}
request_id: {type: string}