fix(selfop): persist bounded next-root quantity diagnostics
This commit is contained in:
+489
-60
@@ -1,40 +1,168 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Internal bounded self-operated quantity diagnostics
|
||||
version: 1.0.0
|
||||
title: Internal next-root bounded quantity diagnostics
|
||||
version: 2.0.0
|
||||
description: Explicit arm only; no automatic rearm. Bind next normal automatic historical OUTBOUND_QUERY root
|
||||
within 6h. Sampling expires 4h after first LIST observedAt, never later than armedAt+10h. Lastleg session lock
|
||||
is receiver acceptance. Source root creation may predate arm. LOCATE, manual/validation roots, current excluded
|
||||
root and different bound root are rejected/ignored. At most 100 distinct LIST candidates total, 2000 retained
|
||||
SKU rows across both sides, 2MiB canonical evidence plus promotion overhead. Pending-to-final never refunds
|
||||
quota. First unknown or SKU mismatch persists INCONCLUSIVE and stops; any known inequality among list/forecast/actual
|
||||
persists QUANTITY_DIFFERENCE and stops. Original side timestamps/attempts are immutable; exact replay returns
|
||||
stored receipt even after termination, changed same-side evidence is rejected. Root termination drains only
|
||||
accepted candidates via an explicit Integration FIFO boundary without extending expiry. Fewer than 100 complete
|
||||
pairs is ROOT_ENDED_INSUFFICIENT; unpaired/lost tail is INCOMPLETE. Producer restart retains persisted candidates
|
||||
but marks delivery uncertain. Every request/response is stream-bounded to 262144 bytes. Never exposed through
|
||||
Gateway, never creates supplier requests or changes business facts.
|
||||
paths:
|
||||
/internal/lastleg/self-operated-analytics/quantity-diagnostics/orders:
|
||||
/internal/lastleg/self-operated-analytics/quantity-diagnostics/arm:
|
||||
post:
|
||||
operationId: receiveSelfopQuantityDiagnosticOrder
|
||||
description: >-
|
||||
Internal service-token only; never routed through Gateway. One complete paired order per request.
|
||||
Quantities are evidence only. Session scope and UTC time interval are frozen by server configuration.
|
||||
Maximum 100 orders, 2000 SKU rows and 2097152 canonical UTF8 bytes per session;
|
||||
200 SKU rows and 262144 wire/canonical bytes per request; at most 30 minutes.
|
||||
First known forecast/actual difference closes capture regardless of comparison classification.
|
||||
Same identity and canonical evidence is idempotent, including after closure; changed evidence is 409.
|
||||
operationId: quantityDiagnosticArm
|
||||
security:
|
||||
- internalServiceToken: []
|
||||
- internalServiceToken: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PairedOrder'
|
||||
$ref: '#/components/schemas/Arm'
|
||||
responses:
|
||||
'200':
|
||||
description: Diagnostic receipt in the standard ApiResponse wrapper; code is string 200.
|
||||
description: ApiResponse code 200. status data is null if no grant exists.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
code: { type: string, example: '200' }
|
||||
data: { $ref: '#/components/schemas/Receipt' }
|
||||
'400': { description: 'Invalid JSON, unknown fields, invalid scope/evidence, duplicate SKU or request limit.' }
|
||||
'401': { description: Invalid internal service token. }
|
||||
'409': { description: Session configuration changed or order identity reused with different evidence. }
|
||||
'503': { description: Internal service token unconfigured or diagnostic storage unavailable. }
|
||||
code:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
nullable: true
|
||||
data:
|
||||
$ref: '#/components/schemas/View'
|
||||
nullable: true
|
||||
required:
|
||||
- code
|
||||
- data
|
||||
'400':
|
||||
description: Invalid scope, changed frozen evidence, invalid observation or disabled feature.
|
||||
'401':
|
||||
description: Missing or invalid internal service token.
|
||||
'503':
|
||||
description: Diagnostic admission, dedicated connection or storage unavailable; collection is unaffected.
|
||||
/internal/lastleg/self-operated-analytics/quantity-diagnostics/status:
|
||||
post:
|
||||
operationId: quantityDiagnosticStatus
|
||||
security:
|
||||
- internalServiceToken: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Status'
|
||||
responses:
|
||||
'200':
|
||||
description: ApiResponse code 200. status data is null if no grant exists.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
nullable: true
|
||||
data:
|
||||
$ref: '#/components/schemas/View'
|
||||
nullable: true
|
||||
required:
|
||||
- code
|
||||
- data
|
||||
'400':
|
||||
description: Invalid scope, changed frozen evidence, invalid observation or disabled feature.
|
||||
'401':
|
||||
description: Missing or invalid internal service token.
|
||||
'503':
|
||||
description: Diagnostic admission, dedicated connection or storage unavailable; collection is unaffected.
|
||||
/internal/lastleg/self-operated-analytics/quantity-diagnostics/observe:
|
||||
post:
|
||||
operationId: quantityDiagnosticObserve
|
||||
security:
|
||||
- internalServiceToken: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Event'
|
||||
responses:
|
||||
'200':
|
||||
description: ApiResponse code 200. status data is null if no grant exists.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
nullable: true
|
||||
data:
|
||||
$ref: '#/components/schemas/Receipt'
|
||||
nullable: true
|
||||
required:
|
||||
- code
|
||||
- data
|
||||
'400':
|
||||
description: Invalid scope, changed frozen evidence, invalid observation or disabled feature.
|
||||
'401':
|
||||
description: Missing or invalid internal service token.
|
||||
'503':
|
||||
description: Diagnostic admission, dedicated connection or storage unavailable; collection is unaffected.
|
||||
/internal/lastleg/self-operated-analytics/quantity-diagnostics/stop:
|
||||
post:
|
||||
operationId: quantityDiagnosticStop
|
||||
security:
|
||||
- internalServiceToken: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Stop'
|
||||
responses:
|
||||
'200':
|
||||
description: ApiResponse code 200. status data is null if no grant exists.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
nullable: true
|
||||
data:
|
||||
$ref: '#/components/schemas/View'
|
||||
nullable: true
|
||||
required:
|
||||
- code
|
||||
- data
|
||||
'400':
|
||||
description: Invalid scope, changed frozen evidence, invalid observation or disabled feature.
|
||||
'401':
|
||||
description: Missing or invalid internal service token.
|
||||
'503':
|
||||
description: Diagnostic admission, dedicated connection or storage unavailable; collection is unaffected.
|
||||
components:
|
||||
securitySchemes:
|
||||
internalServiceToken:
|
||||
@@ -42,64 +170,365 @@ components:
|
||||
in: header
|
||||
name: X-Internal-Service-Token
|
||||
schemas:
|
||||
PairedOrder:
|
||||
Arm:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [diagnosticSessionId, collectionSessionId, rootTaskId, sourceAccount, warehouseCode, ownerCode, orderNo, listObservation, detailObservation, skus]
|
||||
properties:
|
||||
diagnosticSessionId: { type: string, maxLength: 128 }
|
||||
collectionSessionId: { type: string, maxLength: 128 }
|
||||
rootTaskId: { type: string, maxLength: 128 }
|
||||
sourceAccount: { type: string, maxLength: 64 }
|
||||
warehouseCode: { type: string, maxLength: 64 }
|
||||
ownerCode: { type: string, maxLength: 128 }
|
||||
orderNo: { type: string, maxLength: 128 }
|
||||
listObservation: { $ref: '#/components/schemas/Observation' }
|
||||
detailObservation: { $ref: '#/components/schemas/Observation' }
|
||||
skus:
|
||||
diagnosticSessionId:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
sourceAccount:
|
||||
type: string
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
warehouseCodes:
|
||||
type: array
|
||||
minItems: 1
|
||||
maxItems: 200
|
||||
description: Union of list/detail SKUs; duplicate source lines aggregated, unknown never zero-filled.
|
||||
items: { $ref: '#/components/schemas/Sku' }
|
||||
maxItems: 8
|
||||
uniqueItems: true
|
||||
items:
|
||||
type: string
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
ownerCode:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
description: Explicit owner code, or * for all allowed owners.
|
||||
excludedRootTaskId:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
nullable: true
|
||||
description: Must match the actual started, unfinished automatic root at arm; null only when none exists.
|
||||
required:
|
||||
- diagnosticSessionId
|
||||
- sourceAccount
|
||||
- warehouseCodes
|
||||
- ownerCode
|
||||
Status:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
sourceAccount:
|
||||
type: string
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
producerId:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
attach:
|
||||
type: boolean
|
||||
description: True only on Integration process attachment; replacement marks delivery uncertain and fences
|
||||
prior producer.
|
||||
drained:
|
||||
type: boolean
|
||||
description: Only after observing DRAINING, closing intake and draining previously queued callbacks.
|
||||
required:
|
||||
- sourceAccount
|
||||
- producerId
|
||||
- attach
|
||||
- drained
|
||||
Stop:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
diagnosticSessionId:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
sourceAccount:
|
||||
type: string
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
producerId:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
reason:
|
||||
type: string
|
||||
enum:
|
||||
- INCOMPLETE
|
||||
- QUEUE_FULL
|
||||
- OBSERVER_FAILED
|
||||
- SHUTDOWN
|
||||
required:
|
||||
- diagnosticSessionId
|
||||
- sourceAccount
|
||||
- producerId
|
||||
- reason
|
||||
Observation:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [observedAt, attemptId, skuSetComplete]
|
||||
properties:
|
||||
status: { type: string, nullable: true, maxLength: 32 }
|
||||
status:
|
||||
type: string
|
||||
maxLength: 32
|
||||
minLength: 1
|
||||
nullable: true
|
||||
outboundTime:
|
||||
type: string
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
nullable: true
|
||||
maxLength: 32
|
||||
description: Original Asia/Shanghai source text, null if absent; not reinterpreted as another timezone.
|
||||
observedAt: { type: string, format: date-time }
|
||||
attemptId: { type: string, maxLength: 128 }
|
||||
skuSetComplete: { type: boolean }
|
||||
observedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
attemptId:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
skuSetComplete:
|
||||
type: boolean
|
||||
required:
|
||||
- status
|
||||
- outboundTime
|
||||
- observedAt
|
||||
- attemptId
|
||||
- skuSetComplete
|
||||
Sku:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [sku, listPresent, detailPresent]
|
||||
properties:
|
||||
sku: { type: string, maxLength: 128 }
|
||||
listQty: { type: number, nullable: true, minimum: 0, multipleOf: 0.000001 }
|
||||
forecastQty: { type: number, nullable: true, minimum: 0, multipleOf: 0.000001 }
|
||||
actualQty: { type: number, nullable: true, minimum: 0, multipleOf: 0.000001 }
|
||||
listPresent: { type: boolean }
|
||||
detailPresent: { type: boolean }
|
||||
Receipt:
|
||||
type: object
|
||||
required: [outcome, acceptedOrders, acceptedSkuRows, acceptedBytes]
|
||||
properties:
|
||||
outcome: { type: string, enum: [ACCEPTED, DUPLICATE, STOPPED] }
|
||||
comparison:
|
||||
sku:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
listQty:
|
||||
type: number
|
||||
nullable: true
|
||||
enum: [SKU_SET_MISMATCH, QUANTITY_UNKNOWN, FORECAST_ACTUAL_DIFFERENCE, ALL_EQUAL, LIST_QUANTITY_DIFFERENCE]
|
||||
minimum: 0
|
||||
description: Up to 32 integer digits, at most 6 decimal places; unknown stays null.
|
||||
forecastQty:
|
||||
type: number
|
||||
nullable: true
|
||||
minimum: 0
|
||||
description: Up to 32 integer digits, at most 6 decimal places; unknown stays null.
|
||||
actualQty:
|
||||
type: number
|
||||
nullable: true
|
||||
minimum: 0
|
||||
description: Up to 32 integer digits, at most 6 decimal places; unknown stays null.
|
||||
required:
|
||||
- sku
|
||||
- listQty
|
||||
- forecastQty
|
||||
- actualQty
|
||||
Key:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
warehouseCode:
|
||||
type: string
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
ownerCode:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
orderNo:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
paired:
|
||||
type: boolean
|
||||
required:
|
||||
- warehouseCode
|
||||
- ownerCode
|
||||
- orderNo
|
||||
- paired
|
||||
Event:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
diagnosticSessionId:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
sourceAccount:
|
||||
type: string
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
producerId:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
rootTaskId:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
collectionSessionId:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
operation:
|
||||
type: string
|
||||
enum:
|
||||
- OUTBOUND_QUERY
|
||||
- OUTBOUND_DETAIL
|
||||
warehouseCode:
|
||||
type: string
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
ownerCode:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
orderNo:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
side:
|
||||
type: string
|
||||
enum:
|
||||
- LIST
|
||||
- DETAIL
|
||||
observation:
|
||||
$ref: '#/components/schemas/Observation'
|
||||
skus:
|
||||
type: array
|
||||
maxItems: 200
|
||||
items:
|
||||
$ref: '#/components/schemas/Sku'
|
||||
description: Empty allowed only when skuSetComplete=false; persists inconclusive evidence.
|
||||
required:
|
||||
- diagnosticSessionId
|
||||
- sourceAccount
|
||||
- producerId
|
||||
- rootTaskId
|
||||
- collectionSessionId
|
||||
- operation
|
||||
- warehouseCode
|
||||
- ownerCode
|
||||
- orderNo
|
||||
- side
|
||||
- observation
|
||||
- skus
|
||||
View:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
diagnosticSessionId:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
sourceAccount:
|
||||
type: string
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
rootTaskId:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
nullable: true
|
||||
collectionSessionId:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
nullable: true
|
||||
state:
|
||||
type: string
|
||||
enum:
|
||||
- ARMED
|
||||
- BOUND
|
||||
- DRAINING
|
||||
- TERMINAL
|
||||
stopReason:
|
||||
type: string
|
||||
nullable: true
|
||||
enum: [DISABLED, NOT_STARTED, EXPIRED, LIMIT_REACHED, FORECAST_ACTUAL_DIFFERENCE]
|
||||
acceptedOrders: { type: integer }
|
||||
acceptedSkuRows: { type: integer }
|
||||
acceptedBytes: { type: integer, format: int64, description: Canonical whitelist JSON UTF8 bytes persisted in this session. }
|
||||
startsAt:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
expiresAt:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
bindBefore:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
warehouseCodes:
|
||||
type: array
|
||||
maxItems: 8
|
||||
items:
|
||||
type: string
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
ownerCode:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
excludedRootTaskId:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
nullable: true
|
||||
candidates:
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
pairedOrders:
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
retainedRows:
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 2000
|
||||
retainedBytes:
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 2097152
|
||||
deliveryUncertain:
|
||||
type: boolean
|
||||
selected:
|
||||
type: array
|
||||
maxItems: 100
|
||||
items:
|
||||
$ref: '#/components/schemas/Key'
|
||||
required:
|
||||
- diagnosticSessionId
|
||||
- sourceAccount
|
||||
- rootTaskId
|
||||
- collectionSessionId
|
||||
- state
|
||||
- stopReason
|
||||
- startsAt
|
||||
- expiresAt
|
||||
- bindBefore
|
||||
- warehouseCodes
|
||||
- ownerCode
|
||||
- excludedRootTaskId
|
||||
- candidates
|
||||
- pairedOrders
|
||||
- retainedRows
|
||||
- retainedBytes
|
||||
- deliveryUncertain
|
||||
- selected
|
||||
Receipt:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
outcome:
|
||||
type: string
|
||||
enum:
|
||||
- ACCEPTED
|
||||
- IGNORED
|
||||
- DUPLICATE
|
||||
- STOPPED
|
||||
comparison:
|
||||
type: string
|
||||
nullable: true
|
||||
enum:
|
||||
- ALL_EQUAL
|
||||
- QUANTITY_DIFFERENCE
|
||||
- INCONCLUSIVE
|
||||
session:
|
||||
$ref: '#/components/schemas/View'
|
||||
required:
|
||||
- outcome
|
||||
- comparison
|
||||
- session
|
||||
|
||||
Reference in New Issue
Block a user