2026-10-12 20:27:35 +08:00
|
|
|
|
openapi: 3.0.3
|
|
|
|
|
|
info:
|
|
|
|
|
|
title: Self-operated Analytics Internal Egress Authority
|
|
|
|
|
|
version: 1.0.0
|
|
|
|
|
|
paths:
|
2026-10-13 22:23:48 +08:00
|
|
|
|
/internal/lastleg/self-operated-analytics/egress/runtime:
|
|
|
|
|
|
get:
|
|
|
|
|
|
operationId: getSelfoperatedEgressRuntime
|
|
|
|
|
|
summary: 读取脱敏的实际出口配置快照
|
|
|
|
|
|
description: >-
|
|
|
|
|
|
仅内部服务令牌,不提供公网路由。直接读取 Egress 执行所用的同一配置实例,
|
|
|
|
|
|
不读取数据库、不预留或消费许可、不调用供应商。响应不是部署验证结论,
|
|
|
|
|
|
不包含 verified、镜像版本、原始账号、grant、Token 或 URL。
|
|
|
|
|
|
intervalMs 为当前配置间隔,持久账户间隔可能更严格,须另行只读核验。
|
|
|
|
|
|
pendingMaxSeconds 为实际配置经 1 至 120 秒裁剪后的上限;permitMaxSeconds 为单次许可上限,
|
|
|
|
|
|
VALIDATION 仍可能受验证截止及 grant 截止进一步缩短。
|
|
|
|
|
|
security: [{ internalServiceToken: [] }]
|
|
|
|
|
|
responses:
|
|
|
|
|
|
'200':
|
|
|
|
|
|
description: ApiResponse 成功响应,data 为只读白名单快照
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
properties:
|
|
|
|
|
|
code: { type: string }
|
|
|
|
|
|
data: { $ref: '#/components/schemas/RuntimeSnapshot' }
|
|
|
|
|
|
'401': { description: 内部令牌无效 }
|
|
|
|
|
|
'503': { description: 内部令牌未配置 }
|
2026-10-12 20:27:35 +08:00
|
|
|
|
/internal/lastleg/self-operated-analytics/egress/authorize:
|
|
|
|
|
|
post:
|
|
|
|
|
|
operationId: authorizeSelfoperatedEgressAttempt
|
|
|
|
|
|
summary: 一次性消费已持久预留的领星请求许可
|
|
|
|
|
|
description: >-
|
|
|
|
|
|
仅内部服务令牌。客户端已登记的 context 和 operation 必须完全匹配;
|
|
|
|
|
|
requestHash 是 Integration 对 OMP 路径和有序业务 JSON 的摘要,首次消费绑定,
|
|
|
|
|
|
后续完成必须完全匹配。同 attemptId 再次消费拒绝。allowedAttempts 固定 1,
|
2026-10-13 22:23:48 +08:00
|
|
|
|
expiresAt 最多 30 秒;VALIDATION 不超过验证根截止及 grant 截止。
|
|
|
|
|
|
BUSINESS 不受人为会话或 grant 截止阻断,但保留短许可过期、身份、显式停止及账户保护。
|
|
|
|
|
|
OFF 默认拒绝;BUSINESS 仅接受 production 或 test,人工根及合法派生保持原身份,
|
|
|
|
|
|
连续计划根另须连续开关与持久化 planner 身份。独占交接证明及接续计数由服务端配置核验。
|
2026-10-12 20:27:35 +08:00
|
|
|
|
security: [{ internalServiceToken: [] }]
|
|
|
|
|
|
requestBody:
|
|
|
|
|
|
required: true
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema: { $ref: '#/components/schemas/AuthorizationRequest' }
|
|
|
|
|
|
responses:
|
|
|
|
|
|
'200':
|
|
|
|
|
|
description: HTTP 200;许可 code=200,正常拒绝 code=B0001,data 为显式预算决策
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
properties:
|
|
|
|
|
|
code: { type: string }
|
|
|
|
|
|
data:
|
|
|
|
|
|
oneOf:
|
|
|
|
|
|
- { $ref: '#/components/schemas/AttemptPermit' }
|
|
|
|
|
|
- { $ref: '#/components/schemas/AuthorizationDenial' }
|
|
|
|
|
|
'401': { description: 内部令牌无效 }
|
|
|
|
|
|
'503': { description: 内部令牌未配置 }
|
|
|
|
|
|
/internal/lastleg/self-operated-analytics/egress/complete:
|
|
|
|
|
|
post:
|
|
|
|
|
|
operationId: completeSelfoperatedEgressAttempt
|
|
|
|
|
|
summary: 记录真实尝试次数和账号冷却
|
|
|
|
|
|
description: >-
|
|
|
|
|
|
回调 context、operation、requestHash 必须匹配已消费许可。actualHttpAttempts 仅 0 或 1;
|
|
|
|
|
|
即使 0 也不退回预留,响应丢失同样保留保守消耗。重复已确认完成为幂等成功。
|
|
|
|
|
|
429 对账号全部采集族持久冷却;验证根 DEAD、run FAILED、禁止自动续跑。
|
|
|
|
|
|
未确认结果的许可到期后保守冷却,不使用换日、换仓、重试或重启清零预算。
|
|
|
|
|
|
security: [{ internalServiceToken: [] }]
|
|
|
|
|
|
requestBody:
|
|
|
|
|
|
required: true
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema: { $ref: '#/components/schemas/CompletionRequest' }
|
|
|
|
|
|
responses:
|
|
|
|
|
|
'200':
|
|
|
|
|
|
description: ApiResponse code 字符串 200,data 为布尔 true
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
properties:
|
|
|
|
|
|
code: { type: string }
|
|
|
|
|
|
data: { type: boolean, enum: [true] }
|
|
|
|
|
|
'401': { description: 内部令牌无效 }
|
|
|
|
|
|
components:
|
|
|
|
|
|
securitySchemes:
|
|
|
|
|
|
internalServiceToken: { type: apiKey, in: header, name: X-Internal-Service-Token }
|
|
|
|
|
|
schemas:
|
2026-10-13 22:23:48 +08:00
|
|
|
|
RuntimeSnapshot:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
additionalProperties: false
|
|
|
|
|
|
required: [schemaVersion, service, observedAt, mode, environment, continuousEnabled, sourceAccountFingerprint, grantPresent, grantFingerprint, intervalMs, permitMaxSeconds, executionEnabled, pendingMaxSeconds, exclusiveHandoffVerified, priorEgressClosed, inflightDrained, transferredBudgetVerified]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
schemaVersion: { type: integer, enum: [1] }
|
|
|
|
|
|
service: { type: string, enum: [lastleg] }
|
|
|
|
|
|
observedAt: { type: string, format: date-time, description: UTC 观测时间 }
|
|
|
|
|
|
mode: { type: string, nullable: true, description: 原样返回实际配置,不将未知值转为 BUSINESS }
|
|
|
|
|
|
environment: { type: string, nullable: true }
|
|
|
|
|
|
continuousEnabled: { type: boolean }
|
|
|
|
|
|
sourceAccountFingerprint:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
nullable: true
|
|
|
|
|
|
pattern: '^[a-f0-9]{64}$'
|
|
|
|
|
|
description: 'SHA-256 UTF-8 输入为 selfop-runtime:v1:source-account 加一个 LF 再加原始值;不 trim;null 或 blank 返回显式 null。'
|
|
|
|
|
|
grantPresent: { type: boolean, description: grant 原值非 null 且非 blank }
|
|
|
|
|
|
grantFingerprint:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
nullable: true
|
|
|
|
|
|
pattern: '^[a-f0-9]{64}$'
|
|
|
|
|
|
description: 'SHA-256 UTF-8 输入为 selfop-runtime:v1:grant 加一个 LF 再加原始值;不 trim;null 或 blank 返回显式 null。'
|
|
|
|
|
|
intervalMs: { type: integer, format: int64, description: 实际配置的 BUSINESS 最小请求间隔毫秒数,未合并持久账户间隔 }
|
|
|
|
|
|
permitMaxSeconds: { type: integer, enum: [30] }
|
|
|
|
|
|
executionEnabled: { type: boolean }
|
|
|
|
|
|
pendingMaxSeconds: { type: integer, minimum: 1, maximum: 120 }
|
|
|
|
|
|
exclusiveHandoffVerified: { type: boolean }
|
|
|
|
|
|
priorEgressClosed: { type: boolean }
|
|
|
|
|
|
inflightDrained: { type: boolean }
|
|
|
|
|
|
transferredBudgetVerified: { type: boolean }
|
2026-10-12 20:27:35 +08:00
|
|
|
|
EgressContext:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [sourceAccount, executionPurpose, rootTaskId, sessionId, attemptId, exclusiveGrantId]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
sourceAccount: { type: string, maxLength: 64 }
|
|
|
|
|
|
executionPurpose: { type: string, enum: [VALIDATION, BUSINESS] }
|
|
|
|
|
|
rootTaskId: { type: string, pattern: '^[0-9]+$' }
|
|
|
|
|
|
sessionId: { type: string, maxLength: 128 }
|
|
|
|
|
|
attemptId: { type: string, maxLength: 128 }
|
|
|
|
|
|
exclusiveGrantId: { type: string, maxLength: 128 }
|
|
|
|
|
|
Operation:
|
|
|
|
|
|
type: string
|
2026-10-14 16:15:39 +08:00
|
|
|
|
enum: [WAREHOUSES, OUTBOUND_QUERY, OUTBOUND_LOCATE, OUTBOUND_DETAIL, PRODUCT_INVENTORY, CUSTOMERS, PRODUCTS]
|
2026-10-12 20:27:35 +08:00
|
|
|
|
AuthorizationRequest:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [context, operation, requestHash]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
context: { $ref: '#/components/schemas/EgressContext' }
|
|
|
|
|
|
operation: { $ref: '#/components/schemas/Operation' }
|
|
|
|
|
|
requestHash: { type: string, pattern: '^[a-f0-9]{64}$' }
|
|
|
|
|
|
AuthorizationDenial:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [errorCode, sampleDone, waitUntil]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
errorCode: { type: string, pattern: '^[A-Z][A-Z0-9_]{0,63}$' }
|
|
|
|
|
|
sampleDone: { type: boolean }
|
|
|
|
|
|
waitUntil: { type: string, format: date-time, nullable: true, description: UTC Instant 或显式 null }
|
|
|
|
|
|
AttemptPermit:
|
|
|
|
|
|
allOf:
|
|
|
|
|
|
- { $ref: '#/components/schemas/AuthorizationRequest' }
|
|
|
|
|
|
- type: object
|
|
|
|
|
|
required: [allowedAttempts, expiresAt, exclusiveConfirmed, budgetConsumed]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
allowedAttempts: { type: integer, enum: [1] }
|
|
|
|
|
|
expiresAt: { type: string, format: date-time }
|
|
|
|
|
|
exclusiveConfirmed: { type: boolean, enum: [true] }
|
|
|
|
|
|
budgetConsumed: { type: boolean, enum: [true] }
|
|
|
|
|
|
CompletionRequest:
|
|
|
|
|
|
allOf:
|
|
|
|
|
|
- { $ref: '#/components/schemas/AuthorizationRequest' }
|
|
|
|
|
|
- type: object
|
|
|
|
|
|
required: [actualHttpAttempts, accountPaused]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
actualHttpAttempts: { type: integer, minimum: 0, maximum: 1 }
|
|
|
|
|
|
errorCode: { type: string, nullable: true }
|
|
|
|
|
|
upstreamStatus: { type: integer, nullable: true }
|
|
|
|
|
|
sourceCode: { type: integer, nullable: true }
|
|
|
|
|
|
accountPaused: { type: boolean }
|
|
|
|
|
|
cooldownUntil: { type: string, format: date-time, nullable: true }
|