refactor: delete contracts
This commit is contained in:
@@ -1,15 +0,0 @@
|
||||
# Contracts
|
||||
|
||||
模块接口契约公共目录:
|
||||
|
||||
```text
|
||||
openapi/ HTTP OpenAPI 3 契约
|
||||
events/ MySQL Outbox 内部事件 JSON Schema
|
||||
runtime/ Runtime Adapter 契约
|
||||
locks/ Notebook 编辑锁契约
|
||||
```
|
||||
|
||||
各服务实现必须以本目录中的版本化契约为准。
|
||||
|
||||
快速 Demo 的跨模块边界、状态枚举和事件路由统一见
|
||||
`demo-core-v1.md`。
|
||||
@@ -1 +0,0 @@
|
||||
"""Executable cross-service contracts."""
|
||||
@@ -1,56 +0,0 @@
|
||||
# 快速 Demo 核心契约 V1
|
||||
|
||||
冻结日期:2026-07-27
|
||||
状态:`frozen-target`
|
||||
|
||||
## 1. 契约边界
|
||||
|
||||
Demo 公共 HTTP API 由两部分共同组成:
|
||||
|
||||
1. `openapi/platform-api-v1.yaml`:已经运行的脚本、稳定版本详情、数据资源和文件编辑锁接口。
|
||||
2. `openapi/demo-core-extension-v1.yaml`:第 15 小步冻结、后续小步实现的上下文、Jupyter 票据、稳定版本列表、调度和运行查询接口。
|
||||
|
||||
内部 Storage 与 Runtime 接口继续分别以
|
||||
`openapi/storage-api-internal-v1.yaml` 和
|
||||
`openapi/runtime-api-internal-v1.yaml` 为准。
|
||||
|
||||
## 2. 不可变约束
|
||||
|
||||
- 主标识使用 ULID;调度节点只引用不可变的 `versions_id`。
|
||||
- 当前认证上下文由 `X-User-ID`、`X-Workspace-ID` 提供;以后换成 JWT 时不得改变业务 DTO。
|
||||
- 修改调度方案必须提交 `workflow_version`,冲突返回 `412`。
|
||||
- 创建调度、立即运行、启停和取消使用 `Idempotency-Key`。
|
||||
- `schedule_runs.schedule_snapshot` 固化本次执行 DAG,后续编辑不影响已创建的运行。
|
||||
- 浏览器不能拿到 Jupyter 内部 Token,只能获得短期访问 Cookie。
|
||||
|
||||
## 3. 状态值
|
||||
|
||||
- 调度运行:`queued / running / succeeded / failed / cancelled / timed_out`
|
||||
- 节点运行:`queued / running / succeeded / failed / skipped / cancelled / timed_out`
|
||||
- 失败策略:`stop / continue`
|
||||
- 触发类型:`manual / cron / api / retry`
|
||||
|
||||
## 4. MySQL Outbox 与 HTTP 推送
|
||||
|
||||
| 事件 | 写入方 | 处理方 |
|
||||
|---|---|---|
|
||||
| `schedule.run.requested` | Backend / Cron Dispatcher | Schedule Executor |
|
||||
| `job.node.execute` | Schedule Executor | Schedule Executor Worker |
|
||||
| `job.node.finished` | Schedule Executor Worker | Schedule Executor |
|
||||
|
||||
业务事务先写 `outbox_events`。Backend 对“立即运行”执行一次内部 HTTP 推送,
|
||||
Executor 同时轮询 MySQL 作为兜底;`consumer_inbox` 防止同一事件重复执行。
|
||||
|
||||
## 5. 模块所有权
|
||||
|
||||
| 模块 | 拥有的数据与职责 |
|
||||
|---|---|
|
||||
| Platform API | 脚本、稳定版本、调度定义、运行查询、Outbox 写入 |
|
||||
| Runtime Manager | Workspace/Jupyter 生命周期与内部运行态 |
|
||||
| Nginx | 统一入口、Jupyter HTTP/WebSocket 代理 |
|
||||
| Schedule Orchestrator | 消费调度请求、解析 DAG、推进节点状态 |
|
||||
| Job Worker | 读取稳定版本、执行节点、保存日志/结果、报告终态 |
|
||||
| Storage Service | RustFS 对象元数据与预签名访问 |
|
||||
|
||||
模块间不得复制定义 DTO、状态枚举、错误结构或事件字段;契约变更必须先
|
||||
升级本目录中的版本化文件。
|
||||
@@ -1,11 +0,0 @@
|
||||
# Database Event Schemas
|
||||
|
||||
内部事件以 JSON Schema Draft 2020-12 定义:
|
||||
|
||||
- `event-envelope-v1.json`:公共事件信封;
|
||||
- `schedule-run-requested-v1.json`:请求启动一次调度运行;
|
||||
- `job-node-execute-v1.json`:请求执行一个稳定版本节点;
|
||||
- `job-node-finished-v1.json`:节点终态。
|
||||
|
||||
事件先随业务事务写入 MySQL `outbox_events`,Schedule Executor 直接轮询处理;
|
||||
`consumer_inbox` 提供幂等保护。字段和状态值不得在生产者或消费者中另行定义。
|
||||
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "event-envelope-v1.json",
|
||||
"title": "Model Platform Event Envelope V1",
|
||||
"description": "MySQL Outbox 中所有内部业务事件共用的不可变信封。",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"event_id",
|
||||
"event_type",
|
||||
"schema_version",
|
||||
"occurred_at",
|
||||
"producer",
|
||||
"trace_id",
|
||||
"aggregate_type",
|
||||
"aggregate_id",
|
||||
"idempotency_key",
|
||||
"payload"
|
||||
],
|
||||
"properties": {
|
||||
"event_id": {
|
||||
"$ref": "#/$defs/ulid"
|
||||
},
|
||||
"event_type": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z][a-z0-9]*(\\.[a-z][a-z0-9_]*)+$"
|
||||
},
|
||||
"schema_version": {
|
||||
"const": 1
|
||||
},
|
||||
"occurred_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"producer": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 64
|
||||
},
|
||||
"trace_id": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 64
|
||||
},
|
||||
"aggregate_type": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 64
|
||||
},
|
||||
"aggregate_id": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128
|
||||
},
|
||||
"idempotency_key": {
|
||||
"type": "string",
|
||||
"minLength": 8,
|
||||
"maxLength": 128
|
||||
},
|
||||
"payload": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"ulid": {
|
||||
"type": "string",
|
||||
"minLength": 26,
|
||||
"maxLength": 26,
|
||||
"pattern": "^[0-9A-HJKMNP-TV-Z]{26}$"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "job-node-execute-v1.json",
|
||||
"title": "Job Node Execute V1",
|
||||
"description": "调度编排器请求 Job Worker 执行一个稳定版本节点。",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "event-envelope-v1.json"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"event_type": {
|
||||
"const": "job.node.execute"
|
||||
},
|
||||
"aggregate_type": {
|
||||
"const": "schedule_node_run"
|
||||
},
|
||||
"payload": {
|
||||
"$ref": "job-node-execute-v1.json#/$defs/payload"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"$defs": {
|
||||
"ulid": {
|
||||
"type": "string",
|
||||
"minLength": 26,
|
||||
"maxLength": 26,
|
||||
"pattern": "^[0-9A-HJKMNP-TV-Z]{26}$"
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"workspace_id",
|
||||
"run_id",
|
||||
"node_run_id",
|
||||
"node_id",
|
||||
"versions_id",
|
||||
"attempt_no",
|
||||
"script_type",
|
||||
"artifact_object_id",
|
||||
"artifact_path",
|
||||
"timeout_seconds",
|
||||
"arguments"
|
||||
],
|
||||
"properties": {
|
||||
"workspace_id": {
|
||||
"$ref": "job-node-execute-v1.json#/$defs/ulid"
|
||||
},
|
||||
"run_id": {
|
||||
"$ref": "job-node-execute-v1.json#/$defs/ulid"
|
||||
},
|
||||
"node_run_id": {
|
||||
"$ref": "job-node-execute-v1.json#/$defs/ulid"
|
||||
},
|
||||
"node_id": {
|
||||
"$ref": "job-node-execute-v1.json#/$defs/ulid"
|
||||
},
|
||||
"versions_id": {
|
||||
"$ref": "job-node-execute-v1.json#/$defs/ulid"
|
||||
},
|
||||
"attempt_no": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 11
|
||||
},
|
||||
"script_type": {
|
||||
"enum": [
|
||||
"python",
|
||||
"notebook"
|
||||
]
|
||||
},
|
||||
"artifact_object_id": {
|
||||
"$ref": "job-node-execute-v1.json#/$defs/ulid"
|
||||
},
|
||||
"artifact_path": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 1024
|
||||
},
|
||||
"timeout_seconds": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 86400
|
||||
},
|
||||
"arguments": {
|
||||
"type": "array",
|
||||
"maxItems": 100,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"maxLength": 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"event_id": "01K123456789ABCDEFGHJKMNPZ",
|
||||
"event_type": "job.node.execute",
|
||||
"schema_version": 1,
|
||||
"occurred_at": "2026-07-27T08:00:01Z",
|
||||
"producer": "schedule-orchestrator",
|
||||
"trace_id": "req-demo-0001",
|
||||
"aggregate_type": "schedule_node_run",
|
||||
"aggregate_id": "01K123456789ABCDEFGHJKMNQ0",
|
||||
"idempotency_key": "01K123456789ABCDEFGHJKMNQ0:1",
|
||||
"payload": {
|
||||
"workspace_id": "01K123456789ABCDEFGHJKMNPS",
|
||||
"run_id": "01K123456789ABCDEFGHJKMNPR",
|
||||
"node_run_id": "01K123456789ABCDEFGHJKMNQ0",
|
||||
"node_id": "01K123456789ABCDEFGHJKMNPW",
|
||||
"versions_id": "01K123456789ABCDEFGHJKMNPX",
|
||||
"attempt_no": 1,
|
||||
"script_type": "python",
|
||||
"artifact_object_id": "01K123456789ABCDEFGHJKMNPY",
|
||||
"artifact_path": "versions/prepare/v1.py",
|
||||
"timeout_seconds": 600,
|
||||
"arguments": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "job-node-finished-v1.json",
|
||||
"title": "Job Node Finished V1",
|
||||
"description": "Job Worker 报告一次节点执行的终态,调度编排器据此推进 DAG。",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "event-envelope-v1.json"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"event_type": {
|
||||
"const": "job.node.finished"
|
||||
},
|
||||
"aggregate_type": {
|
||||
"const": "schedule_node_run"
|
||||
},
|
||||
"payload": {
|
||||
"$ref": "job-node-finished-v1.json#/$defs/payload"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"$defs": {
|
||||
"ulid": {
|
||||
"type": "string",
|
||||
"minLength": 26,
|
||||
"maxLength": 26,
|
||||
"pattern": "^[0-9A-HJKMNP-TV-Z]{26}$"
|
||||
},
|
||||
"nullableUlid": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "job-node-finished-v1.json#/$defs/ulid"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"workspace_id",
|
||||
"run_id",
|
||||
"node_run_id",
|
||||
"node_id",
|
||||
"versions_id",
|
||||
"attempt_no",
|
||||
"node_status",
|
||||
"exit_code",
|
||||
"started_at",
|
||||
"finished_at",
|
||||
"duration_ms",
|
||||
"logs_object_id",
|
||||
"result_object_id",
|
||||
"error_code",
|
||||
"error_message"
|
||||
],
|
||||
"properties": {
|
||||
"workspace_id": {
|
||||
"$ref": "job-node-finished-v1.json#/$defs/ulid"
|
||||
},
|
||||
"run_id": {
|
||||
"$ref": "job-node-finished-v1.json#/$defs/ulid"
|
||||
},
|
||||
"node_run_id": {
|
||||
"$ref": "job-node-finished-v1.json#/$defs/ulid"
|
||||
},
|
||||
"node_id": {
|
||||
"$ref": "job-node-finished-v1.json#/$defs/ulid"
|
||||
},
|
||||
"versions_id": {
|
||||
"$ref": "job-node-finished-v1.json#/$defs/ulid"
|
||||
},
|
||||
"attempt_no": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 11
|
||||
},
|
||||
"node_status": {
|
||||
"enum": [
|
||||
"succeeded",
|
||||
"failed",
|
||||
"cancelled",
|
||||
"timed_out"
|
||||
]
|
||||
},
|
||||
"exit_code": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"started_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"finished_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"duration_ms": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"logs_object_id": {
|
||||
"$ref": "job-node-finished-v1.json#/$defs/nullableUlid"
|
||||
},
|
||||
"result_object_id": {
|
||||
"$ref": "job-node-finished-v1.json#/$defs/nullableUlid"
|
||||
},
|
||||
"error_code": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"maxLength": 64
|
||||
},
|
||||
"error_message": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"maxLength": 2000
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"event_id": "01K123456789ABCDEFGHJKMNQ1",
|
||||
"event_type": "job.node.finished",
|
||||
"schema_version": 1,
|
||||
"occurred_at": "2026-07-27T08:00:03Z",
|
||||
"producer": "job-worker",
|
||||
"trace_id": "req-demo-0001",
|
||||
"aggregate_type": "schedule_node_run",
|
||||
"aggregate_id": "01K123456789ABCDEFGHJKMNQ0",
|
||||
"idempotency_key": "01K123456789ABCDEFGHJKMNQ0:1:finished",
|
||||
"payload": {
|
||||
"workspace_id": "01K123456789ABCDEFGHJKMNPS",
|
||||
"run_id": "01K123456789ABCDEFGHJKMNPR",
|
||||
"node_run_id": "01K123456789ABCDEFGHJKMNQ0",
|
||||
"node_id": "01K123456789ABCDEFGHJKMNPW",
|
||||
"versions_id": "01K123456789ABCDEFGHJKMNPX",
|
||||
"attempt_no": 1,
|
||||
"node_status": "succeeded",
|
||||
"exit_code": 0,
|
||||
"started_at": "2026-07-27T08:00:01Z",
|
||||
"finished_at": "2026-07-27T08:00:03Z",
|
||||
"duration_ms": 2000,
|
||||
"logs_object_id": null,
|
||||
"result_object_id": null,
|
||||
"error_code": null,
|
||||
"error_message": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schedule-run-requested-v1.json",
|
||||
"title": "Schedule Run Requested V1",
|
||||
"description": "Platform API 或 Cron Dispatcher 请求调度编排器启动一次固化 DAG。",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "event-envelope-v1.json"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"event_type": {
|
||||
"const": "schedule.run.requested"
|
||||
},
|
||||
"aggregate_type": {
|
||||
"const": "schedule_run"
|
||||
},
|
||||
"payload": {
|
||||
"$ref": "schedule-run-requested-v1.json#/$defs/payload"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"$defs": {
|
||||
"ulid": {
|
||||
"type": "string",
|
||||
"minLength": 26,
|
||||
"maxLength": 26,
|
||||
"pattern": "^[0-9A-HJKMNP-TV-Z]{26}$"
|
||||
},
|
||||
"node": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"node_id",
|
||||
"node_key",
|
||||
"versions_id",
|
||||
"script_type",
|
||||
"artifact_object_id",
|
||||
"artifact_path",
|
||||
"timeout_seconds",
|
||||
"retry_count",
|
||||
"retry_interval_sec",
|
||||
"arguments"
|
||||
],
|
||||
"properties": {
|
||||
"node_id": {
|
||||
"$ref": "schedule-run-requested-v1.json#/$defs/ulid"
|
||||
},
|
||||
"node_key": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 64
|
||||
},
|
||||
"versions_id": {
|
||||
"$ref": "schedule-run-requested-v1.json#/$defs/ulid"
|
||||
},
|
||||
"script_type": {
|
||||
"enum": [
|
||||
"python",
|
||||
"notebook"
|
||||
]
|
||||
},
|
||||
"artifact_object_id": {
|
||||
"$ref": "schedule-run-requested-v1.json#/$defs/ulid"
|
||||
},
|
||||
"artifact_path": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 1024
|
||||
},
|
||||
"timeout_seconds": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 86400
|
||||
},
|
||||
"retry_count": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 10
|
||||
},
|
||||
"retry_interval_sec": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 3600
|
||||
},
|
||||
"arguments": {
|
||||
"type": "array",
|
||||
"maxItems": 100,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"maxLength": 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edge": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"source_node_id",
|
||||
"target_node_id"
|
||||
],
|
||||
"properties": {
|
||||
"source_node_id": {
|
||||
"$ref": "schedule-run-requested-v1.json#/$defs/ulid"
|
||||
},
|
||||
"target_node_id": {
|
||||
"$ref": "schedule-run-requested-v1.json#/$defs/ulid"
|
||||
}
|
||||
}
|
||||
},
|
||||
"snapshot": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schedule_name",
|
||||
"workflow_version",
|
||||
"max_concurrency",
|
||||
"failure_policy",
|
||||
"nodes",
|
||||
"edges"
|
||||
],
|
||||
"properties": {
|
||||
"schedule_name": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 255
|
||||
},
|
||||
"workflow_version": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"max_concurrency": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 100
|
||||
},
|
||||
"failure_policy": {
|
||||
"enum": [
|
||||
"stop",
|
||||
"continue"
|
||||
]
|
||||
},
|
||||
"nodes": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 100,
|
||||
"items": {
|
||||
"$ref": "schedule-run-requested-v1.json#/$defs/node"
|
||||
}
|
||||
},
|
||||
"edges": {
|
||||
"type": "array",
|
||||
"maxItems": 500,
|
||||
"items": {
|
||||
"$ref": "schedule-run-requested-v1.json#/$defs/edge"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"workspace_id",
|
||||
"schedule_id",
|
||||
"run_id",
|
||||
"workflow_version",
|
||||
"trigger_type",
|
||||
"triggered_by",
|
||||
"schedule_snapshot"
|
||||
],
|
||||
"properties": {
|
||||
"workspace_id": {
|
||||
"$ref": "schedule-run-requested-v1.json#/$defs/ulid"
|
||||
},
|
||||
"schedule_id": {
|
||||
"$ref": "schedule-run-requested-v1.json#/$defs/ulid"
|
||||
},
|
||||
"run_id": {
|
||||
"$ref": "schedule-run-requested-v1.json#/$defs/ulid"
|
||||
},
|
||||
"workflow_version": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"trigger_type": {
|
||||
"enum": [
|
||||
"manual",
|
||||
"cron",
|
||||
"api",
|
||||
"retry"
|
||||
]
|
||||
},
|
||||
"triggered_by": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "schedule-run-requested-v1.json#/$defs/ulid"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"schedule_snapshot": {
|
||||
"$ref": "schedule-run-requested-v1.json#/$defs/snapshot"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"event_id": "01K123456789ABCDEFGHJKMNPQ",
|
||||
"event_type": "schedule.run.requested",
|
||||
"schema_version": 1,
|
||||
"occurred_at": "2026-07-27T08:00:00Z",
|
||||
"producer": "platform-api",
|
||||
"trace_id": "req-demo-0001",
|
||||
"aggregate_type": "schedule_run",
|
||||
"aggregate_id": "01K123456789ABCDEFGHJKMNPR",
|
||||
"idempotency_key": "run-demo-0001",
|
||||
"payload": {
|
||||
"workspace_id": "01K123456789ABCDEFGHJKMNPS",
|
||||
"schedule_id": "01K123456789ABCDEFGHJKMNPT",
|
||||
"run_id": "01K123456789ABCDEFGHJKMNPR",
|
||||
"workflow_version": 1,
|
||||
"trigger_type": "manual",
|
||||
"triggered_by": "01K123456789ABCDEFGHJKMNPV",
|
||||
"schedule_snapshot": {
|
||||
"schedule_name": "每日模型演示",
|
||||
"workflow_version": 1,
|
||||
"max_concurrency": 1,
|
||||
"failure_policy": "stop",
|
||||
"nodes": [
|
||||
{
|
||||
"node_id": "01K123456789ABCDEFGHJKMNPW",
|
||||
"node_key": "prepare",
|
||||
"versions_id": "01K123456789ABCDEFGHJKMNPX",
|
||||
"script_type": "python",
|
||||
"artifact_object_id": "01K123456789ABCDEFGHJKMNPY",
|
||||
"artifact_path": "versions/prepare/v1.py",
|
||||
"timeout_seconds": 600,
|
||||
"retry_count": 0,
|
||||
"retry_interval_sec": 5,
|
||||
"arguments": []
|
||||
}
|
||||
],
|
||||
"edges": []
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# File Edit Lock
|
||||
|
||||
当前版本见 [file-edit-lock-v1.md](file-edit-lock-v1.md)。MySQL 租约是实时锁权威。
|
||||
@@ -1,38 +0,0 @@
|
||||
# 文件编辑锁契约 v1
|
||||
|
||||
## 公共接口
|
||||
|
||||
```text
|
||||
POST /api/v1/files/{storage_object_id}/lock
|
||||
POST /api/v1/file-locks/{edit_session_id}/heartbeat
|
||||
DELETE /api/v1/file-locks/{edit_session_id}
|
||||
```
|
||||
|
||||
三个接口均要求 `X-User-ID` 和 `X-Workspace-ID`。加锁成功返回一次性原始
|
||||
`lock_token`;心跳和释放请求体均为:
|
||||
|
||||
```json
|
||||
{"lock_token": "raw-token-returned-by-acquire"}
|
||||
```
|
||||
|
||||
原始 token 只由客户端持有,数据库只保存 SHA-256 摘要。
|
||||
|
||||
## MySQL 租约
|
||||
|
||||
`edit_sessions` 是实时锁权威:
|
||||
|
||||
- `session_status=active` 且 `expires_at > now()` 表示锁有效;
|
||||
- 同一 `storage_object_id` 同时只能存在一个有效编辑会话;
|
||||
- 心跳更新 `last_heartbeat_at` 与 `expires_at`;
|
||||
- 主动释放将状态改为 `closed`;
|
||||
- 后台清理将超时租约改为 `expired`。
|
||||
|
||||
加锁、心跳和释放都在数据库事务中校验 `edit_session_id + lock_token_hash`。
|
||||
部署时 Runtime 保持单副本;若扩展到多副本,应为加锁查询增加数据库行锁或唯一
|
||||
租约表约束。
|
||||
|
||||
## 状态与错误
|
||||
|
||||
- 冲突返回 HTTP 409、错误码 `FILE_LOCK_CONFLICT`,并包含当前编辑者和租约到期时间;
|
||||
- 错误 token 返回 HTTP 403,且不得续期或释放现有锁;
|
||||
- 浏览器建议每 15 秒心跳,默认租约为 45 秒。
|
||||
@@ -1,11 +0,0 @@
|
||||
# OpenAPI 契约
|
||||
|
||||
- `platform-api-v1.yaml`:浏览器访问的公共业务接口。
|
||||
- `demo-core-extension-v1.yaml`:第 15 小步冻结的 Demo 目标扩展接口;
|
||||
与 `platform-api-v1.yaml` 合并后构成完整公共 API v1。
|
||||
- `storage-api-internal-v1.yaml`:Storage Service 内部接口。
|
||||
- `runtime-api-internal-v1.yaml`:Runtime Manager 内部接口。
|
||||
|
||||
现有服务契约由对应 FastAPI 应用生成;目标扩展契约先冻结、后实现,不能
|
||||
标记为已上线。所有文件均纳入结构校验。公共接口统一使用 `/api/v1`,
|
||||
内部服务接口统一使用 `/internal/v1`。
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,699 +0,0 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: runtime-manager service
|
||||
version: 0.1.0
|
||||
paths:
|
||||
/:
|
||||
get:
|
||||
summary: Root
|
||||
operationId: root__get
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
title: Response Root Get
|
||||
/health/live:
|
||||
get:
|
||||
summary: Live
|
||||
operationId: live_health_live_get
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
title: Response Live Health Live Get
|
||||
/api/v1/health:
|
||||
get:
|
||||
summary: Public Health
|
||||
operationId: public_health_api_v1_health_get
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
title: Response Public Health Api V1 Health Get
|
||||
/health/ready:
|
||||
get:
|
||||
summary: Ready
|
||||
operationId: ready_health_ready_get
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
title: Response Ready Health Ready Get
|
||||
/internal/v1/file-locks/acquire:
|
||||
post:
|
||||
summary: Acquire File Lock
|
||||
operationId: acquire_file_lock_internal_v1_file_locks_acquire_post
|
||||
parameters:
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AcquireFileLockRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Acquire File Lock Internal V1 File Locks Acquire Post
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/v1/file-locks/{edit_session_id}/heartbeat:
|
||||
post:
|
||||
summary: Heartbeat File Lock
|
||||
operationId: heartbeat_file_lock_internal_v1_file_locks__edit_session_id__heartbeat_post
|
||||
parameters:
|
||||
- name: edit_session_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Edit Session Id
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FileLockTokenRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Heartbeat File Lock Internal V1 File Locks Edit Session Id Heartbeat
|
||||
Post
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/v1/file-locks/{edit_session_id}:
|
||||
delete:
|
||||
summary: Release File Lock
|
||||
operationId: release_file_lock_internal_v1_file_locks__edit_session_id__delete
|
||||
parameters:
|
||||
- name: edit_session_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Edit Session Id
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FileLockTokenRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Release File Lock Internal V1 File Locks Edit Session Id Delete
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/v1/jupyter/access-tickets/{edit_session_id}:
|
||||
post:
|
||||
summary: Create Jupyter Access Ticket
|
||||
operationId: create_jupyter_access_ticket_internal_v1_jupyter_access_tickets__edit_session_id__post
|
||||
parameters:
|
||||
- name: edit_session_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Edit Session Id
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FileLockTokenRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Create Jupyter Access Ticket Internal V1 Jupyter Access Tickets Edit
|
||||
Session Id Post
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/v1/jupyter/authorize:
|
||||
get:
|
||||
summary: Authorize Jupyter Proxy
|
||||
operationId: authorize_jupyter_proxy_internal_v1_jupyter_authorize_get
|
||||
parameters:
|
||||
- name: X-Original-URI
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Original-Uri
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
- name: jupyter_access
|
||||
in: cookie
|
||||
required: false
|
||||
schema:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: 'null'
|
||||
title: Jupyter Access
|
||||
responses:
|
||||
'204':
|
||||
description: Successful Response
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/v1/runtimes/ensure:
|
||||
post:
|
||||
summary: Ensure Runtime
|
||||
operationId: ensure_runtime_internal_v1_runtimes_ensure_post
|
||||
parameters:
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/EnsureRuntimeApiRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Ensure Runtime Internal V1 Runtimes Ensure Post
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/v1/runtimes/{runtime_id}:
|
||||
get:
|
||||
summary: Get Runtime
|
||||
operationId: get_runtime_internal_v1_runtimes__runtime_id__get
|
||||
parameters:
|
||||
- name: runtime_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Runtime Id
|
||||
- name: workspace_id
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
minLength: 26
|
||||
maxLength: 26
|
||||
title: Workspace Id
|
||||
- name: user_id
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
minLength: 26
|
||||
maxLength: 26
|
||||
title: User Id
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Get Runtime Internal V1 Runtimes Runtime Id Get
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
delete:
|
||||
summary: Stop Runtime
|
||||
operationId: stop_runtime_internal_v1_runtimes__runtime_id__delete
|
||||
parameters:
|
||||
- name: runtime_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Runtime Id
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/StopRuntimeApiRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Stop Runtime Internal V1 Runtimes Runtime Id Delete
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/v1/runtimes/{runtime_id}/restart:
|
||||
post:
|
||||
summary: Restart Runtime
|
||||
operationId: restart_runtime_internal_v1_runtimes__runtime_id__restart_post
|
||||
parameters:
|
||||
- name: runtime_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Runtime Id
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RuntimeIdentityRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Restart Runtime Internal V1 Runtimes Runtime Id Restart Post
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/v1/runtimes/{runtime_id}/sessions:
|
||||
post:
|
||||
summary: Create Runtime Session
|
||||
operationId: create_runtime_session_internal_v1_runtimes__runtime_id__sessions_post
|
||||
parameters:
|
||||
- name: runtime_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Runtime Id
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateRuntimeSessionApiRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Create Runtime Session Internal V1 Runtimes Runtime Id Sessions Post
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/v1/runtimes/{runtime_id}/sessions/{session_id}:
|
||||
delete:
|
||||
summary: Terminate Runtime Session
|
||||
operationId: terminate_runtime_session_internal_v1_runtimes__runtime_id__sessions__session_id__delete
|
||||
parameters:
|
||||
- name: runtime_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Runtime Id
|
||||
- name: session_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Session Id
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RuntimeIdentityRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Terminate Runtime Session Internal V1 Runtimes Runtime Id Sessions Session
|
||||
Id Delete
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/health/runtime:
|
||||
get:
|
||||
summary: Internal Health
|
||||
operationId: internal_health_internal_health_runtime_get
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
title: Response Internal Health Internal Health Runtime Get
|
||||
components:
|
||||
schemas:
|
||||
AcquireFileLockRequest:
|
||||
properties:
|
||||
workspace_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: Workspace Id
|
||||
storage_object_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: Storage Object Id
|
||||
user_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: User Id
|
||||
request_id:
|
||||
anyOf:
|
||||
- type: string
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
- type: 'null'
|
||||
title: Request Id
|
||||
additionalProperties: false
|
||||
type: object
|
||||
required:
|
||||
- workspace_id
|
||||
- storage_object_id
|
||||
- user_id
|
||||
title: AcquireFileLockRequest
|
||||
CreateRuntimeSessionApiRequest:
|
||||
properties:
|
||||
workspace_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: Workspace Id
|
||||
user_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: User Id
|
||||
request_id:
|
||||
anyOf:
|
||||
- type: string
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
- type: 'null'
|
||||
title: Request Id
|
||||
storage_object_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: Storage Object Id
|
||||
relative_path:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
title: Relative Path
|
||||
additionalProperties: false
|
||||
type: object
|
||||
required:
|
||||
- workspace_id
|
||||
- user_id
|
||||
- storage_object_id
|
||||
- relative_path
|
||||
title: CreateRuntimeSessionApiRequest
|
||||
EnsureRuntimeApiRequest:
|
||||
properties:
|
||||
workspace_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: Workspace Id
|
||||
user_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: User Id
|
||||
request_id:
|
||||
anyOf:
|
||||
- type: string
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
- type: 'null'
|
||||
title: Request Id
|
||||
additionalProperties: false
|
||||
type: object
|
||||
required:
|
||||
- workspace_id
|
||||
- user_id
|
||||
title: EnsureRuntimeApiRequest
|
||||
FileLockTokenRequest:
|
||||
properties:
|
||||
workspace_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: Workspace Id
|
||||
user_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: User Id
|
||||
lock_token:
|
||||
type: string
|
||||
maxLength: 256
|
||||
minLength: 32
|
||||
title: Lock Token
|
||||
additionalProperties: false
|
||||
type: object
|
||||
required:
|
||||
- workspace_id
|
||||
- user_id
|
||||
- lock_token
|
||||
title: FileLockTokenRequest
|
||||
HTTPValidationError:
|
||||
properties:
|
||||
detail:
|
||||
items:
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
type: array
|
||||
title: Detail
|
||||
type: object
|
||||
title: HTTPValidationError
|
||||
RuntimeIdentityRequest:
|
||||
properties:
|
||||
workspace_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: Workspace Id
|
||||
user_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: User Id
|
||||
request_id:
|
||||
anyOf:
|
||||
- type: string
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
- type: 'null'
|
||||
title: Request Id
|
||||
additionalProperties: false
|
||||
type: object
|
||||
required:
|
||||
- workspace_id
|
||||
- user_id
|
||||
title: RuntimeIdentityRequest
|
||||
StopRuntimeApiRequest:
|
||||
properties:
|
||||
workspace_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: Workspace Id
|
||||
user_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: User Id
|
||||
request_id:
|
||||
anyOf:
|
||||
- type: string
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
- type: 'null'
|
||||
title: Request Id
|
||||
reason:
|
||||
type: string
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
title: Reason
|
||||
default: client_request
|
||||
additionalProperties: false
|
||||
type: object
|
||||
required:
|
||||
- workspace_id
|
||||
- user_id
|
||||
title: StopRuntimeApiRequest
|
||||
ValidationError:
|
||||
properties:
|
||||
loc:
|
||||
items:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: integer
|
||||
type: array
|
||||
title: Location
|
||||
msg:
|
||||
type: string
|
||||
title: Message
|
||||
type:
|
||||
type: string
|
||||
title: Error Type
|
||||
type: object
|
||||
required:
|
||||
- loc
|
||||
- msg
|
||||
- type
|
||||
title: ValidationError
|
||||
@@ -1,550 +0,0 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: storage-api service
|
||||
version: 0.1.0
|
||||
paths:
|
||||
/:
|
||||
get:
|
||||
summary: Root
|
||||
operationId: root__get
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
title: Response Root Get
|
||||
/health/live:
|
||||
get:
|
||||
summary: Live
|
||||
operationId: live_health_live_get
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
title: Response Live Health Live Get
|
||||
/api/v1/health:
|
||||
get:
|
||||
summary: Public Health
|
||||
operationId: public_health_api_v1_health_get
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
title: Response Public Health Api V1 Health Get
|
||||
/health/ready:
|
||||
get:
|
||||
summary: Ready
|
||||
operationId: ready_health_ready_get
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
title: Response Ready Health Ready Get
|
||||
/internal/v1/uploads:
|
||||
post:
|
||||
summary: Create Upload
|
||||
operationId: create_upload_internal_v1_uploads_post
|
||||
parameters:
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateUploadRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Create Upload Internal V1 Uploads Post
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/v1/uploads/{upload_id}/complete:
|
||||
post:
|
||||
summary: Complete Upload
|
||||
operationId: complete_upload_internal_v1_uploads__upload_id__complete_post
|
||||
parameters:
|
||||
- name: upload_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Upload Id
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CompleteUploadRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Complete Upload Internal V1 Uploads Upload Id Complete Post
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/v1/uploads/{upload_id}/abort:
|
||||
post:
|
||||
summary: Abort Upload
|
||||
operationId: abort_upload_internal_v1_uploads__upload_id__abort_post
|
||||
parameters:
|
||||
- name: upload_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Upload Id
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Abort Upload Internal V1 Uploads Upload Id Abort Post
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/v1/objects:
|
||||
post:
|
||||
summary: Create Server Object
|
||||
operationId: create_server_object_internal_v1_objects_post
|
||||
parameters:
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ServerObjectRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Create Server Object Internal V1 Objects Post
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/v1/workspace-objects:
|
||||
post:
|
||||
summary: Register Workspace Object
|
||||
operationId: register_workspace_object_internal_v1_workspace_objects_post
|
||||
parameters:
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RegisterWorkspaceObjectRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Register Workspace Object Internal V1 Workspace Objects Post
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/v1/objects/{storage_object_id}/download-url:
|
||||
post:
|
||||
summary: Create Download Url
|
||||
operationId: create_download_url_internal_v1_objects__storage_object_id__download_url_post
|
||||
parameters:
|
||||
- name: storage_object_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Storage Object Id
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DownloadUrlRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Create Download Url Internal V1 Objects Storage Object Id Download Url
|
||||
Post
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/v1/objects/{storage_object_id}:
|
||||
delete:
|
||||
summary: Delete Object
|
||||
operationId: delete_object_internal_v1_objects__storage_object_id__delete
|
||||
parameters:
|
||||
- name: storage_object_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Storage Object Id
|
||||
- name: X-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: X-Service-Token
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
title: Response Delete Object Internal V1 Objects Storage Object Id Delete
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/internal/health/storage:
|
||||
get:
|
||||
summary: Internal Health
|
||||
operationId: internal_health_internal_health_storage_get
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
title: Response Internal Health Internal Health Storage Get
|
||||
components:
|
||||
schemas:
|
||||
CompleteUploadRequest:
|
||||
properties:
|
||||
usage_type:
|
||||
type: string
|
||||
enum:
|
||||
- data_resource
|
||||
- version_artifact
|
||||
- snapshot
|
||||
- run_log
|
||||
- run_result
|
||||
title: Usage Type
|
||||
visibility:
|
||||
type: string
|
||||
enum:
|
||||
- private
|
||||
- workspace
|
||||
- public
|
||||
title: Visibility
|
||||
default: private
|
||||
is_immutable:
|
||||
type: boolean
|
||||
title: Is Immutable
|
||||
default: false
|
||||
additionalProperties: false
|
||||
type: object
|
||||
required:
|
||||
- usage_type
|
||||
title: CompleteUploadRequest
|
||||
CreateUploadRequest:
|
||||
properties:
|
||||
workspace_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: Workspace Id
|
||||
user_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: User Id
|
||||
usage_type:
|
||||
type: string
|
||||
enum:
|
||||
- data_resource
|
||||
- version_artifact
|
||||
- snapshot
|
||||
- run_log
|
||||
- run_result
|
||||
title: Usage Type
|
||||
file_name:
|
||||
type: string
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
title: File Name
|
||||
content_type:
|
||||
type: string
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
title: Content Type
|
||||
expected_size_bytes:
|
||||
type: integer
|
||||
maximum: 104857600.0
|
||||
minimum: 0.0
|
||||
title: Expected Size Bytes
|
||||
expected_hash:
|
||||
anyOf:
|
||||
- type: string
|
||||
maxLength: 64
|
||||
minLength: 64
|
||||
- type: 'null'
|
||||
title: Expected Hash
|
||||
idempotency_key:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 8
|
||||
title: Idempotency Key
|
||||
url_scope:
|
||||
type: string
|
||||
enum:
|
||||
- public
|
||||
- internal
|
||||
title: Url Scope
|
||||
default: public
|
||||
additionalProperties: false
|
||||
type: object
|
||||
required:
|
||||
- workspace_id
|
||||
- user_id
|
||||
- usage_type
|
||||
- file_name
|
||||
- content_type
|
||||
- expected_size_bytes
|
||||
- idempotency_key
|
||||
title: CreateUploadRequest
|
||||
DownloadUrlRequest:
|
||||
properties:
|
||||
expires_seconds:
|
||||
type: integer
|
||||
maximum: 3600.0
|
||||
minimum: 30.0
|
||||
title: Expires Seconds
|
||||
default: 300
|
||||
additionalProperties: false
|
||||
type: object
|
||||
title: DownloadUrlRequest
|
||||
HTTPValidationError:
|
||||
properties:
|
||||
detail:
|
||||
items:
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
type: array
|
||||
title: Detail
|
||||
type: object
|
||||
title: HTTPValidationError
|
||||
RegisterWorkspaceObjectRequest:
|
||||
properties:
|
||||
workspace_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: Workspace Id
|
||||
user_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: User Id
|
||||
relative_path:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
title: Relative Path
|
||||
usage_type:
|
||||
type: string
|
||||
enum:
|
||||
- working_copy
|
||||
- public_script
|
||||
title: Usage Type
|
||||
visibility:
|
||||
type: string
|
||||
enum:
|
||||
- private
|
||||
- workspace
|
||||
- public
|
||||
title: Visibility
|
||||
default: private
|
||||
additionalProperties: false
|
||||
type: object
|
||||
required:
|
||||
- workspace_id
|
||||
- user_id
|
||||
- relative_path
|
||||
- usage_type
|
||||
title: RegisterWorkspaceObjectRequest
|
||||
ServerObjectRequest:
|
||||
properties:
|
||||
workspace_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: Workspace Id
|
||||
user_id:
|
||||
type: string
|
||||
maxLength: 26
|
||||
minLength: 26
|
||||
title: User Id
|
||||
usage_type:
|
||||
type: string
|
||||
enum:
|
||||
- data_resource
|
||||
- version_artifact
|
||||
- snapshot
|
||||
- run_log
|
||||
- run_result
|
||||
title: Usage Type
|
||||
file_name:
|
||||
type: string
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
title: File Name
|
||||
content_type:
|
||||
type: string
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
title: Content Type
|
||||
content_base64:
|
||||
type: string
|
||||
minLength: 1
|
||||
title: Content Base64
|
||||
visibility:
|
||||
type: string
|
||||
enum:
|
||||
- private
|
||||
- workspace
|
||||
- public
|
||||
title: Visibility
|
||||
default: private
|
||||
is_immutable:
|
||||
type: boolean
|
||||
title: Is Immutable
|
||||
default: false
|
||||
idempotency_key:
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 8
|
||||
title: Idempotency Key
|
||||
additionalProperties: false
|
||||
type: object
|
||||
required:
|
||||
- workspace_id
|
||||
- user_id
|
||||
- usage_type
|
||||
- file_name
|
||||
- content_type
|
||||
- content_base64
|
||||
- idempotency_key
|
||||
title: ServerObjectRequest
|
||||
ValidationError:
|
||||
properties:
|
||||
loc:
|
||||
items:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: integer
|
||||
type: array
|
||||
title: Location
|
||||
msg:
|
||||
type: string
|
||||
title: Message
|
||||
type:
|
||||
type: string
|
||||
title: Error Type
|
||||
type: object
|
||||
required:
|
||||
- loc
|
||||
- msg
|
||||
- type
|
||||
title: ValidationError
|
||||
@@ -1,26 +0,0 @@
|
||||
# Runtime Adapter 契约
|
||||
|
||||
`runtime_adapter.py` 是 Runtime Manager 与具体运行环境之间的可执行契约。
|
||||
|
||||
当前 Compose 环境使用 `SharedJupyterAdapter`。它把共享 Jupyter Server 作为
|
||||
首期 Provider,并在 MySQL 中为每个 `Workspace` 建立并复用一个逻辑
|
||||
Runtime。每个打开的 Notebook 在该 Runtime 中建立独立 Jupyter Session,
|
||||
每个 Session 绑定自己的 Kernel。
|
||||
后续 Docker/Kubernetes Provider 必须实现同一 Protocol,公共锁接口和
|
||||
`runtime_instances` 状态模型保持不变。
|
||||
|
||||
核心约束:
|
||||
|
||||
- `ensure_running` 对同一作用域必须幂等;
|
||||
- Runtime 的唯一复用作用域是 Workspace,不能按用户重复创建;
|
||||
- Runtime 的期望状态、实际状态和租约写入 MySQL;
|
||||
- Notebook 对应独立 Jupyter Session/Kernel,其创建和终止只能由 Runtime
|
||||
Manager 调用;
|
||||
- Provider 不能依赖 Platform API 的进程内状态;
|
||||
- `proxy_base_path` 与 Jupyter 内部 `base_url` 必须一致。
|
||||
- Provider 调用必须携带内部服务凭据,不能通过关闭 XSRF 检查规避认证;
|
||||
- Workspace 的目录、文件权限必须同时满足 Platform 原子写入和 Jupyter
|
||||
读写,当前 Compose 环境使用共享 Unix 组完成。
|
||||
|
||||
Jupyter 浏览器代理、访问票据和 WebSocket 约束见
|
||||
`jupyter-proxy-v1.md`。
|
||||
@@ -1 +0,0 @@
|
||||
"""Runtime adapter contract package."""
|
||||
@@ -1,65 +0,0 @@
|
||||
# Jupyter 代理与访问票据契约 V1
|
||||
|
||||
## 1. 浏览器访问流程
|
||||
|
||||
1. 前端携带用户、Workspace、`edit_session_id` 和 `lock_token` 调用
|
||||
`POST /api/v1/jupyter/access-tickets`。
|
||||
2. Platform API 校验编辑锁、Runtime 与 Jupyter Session 的归属及状态。
|
||||
3. 成功后返回 `/jupyter/...` 地址,并设置短期
|
||||
`jupyter_access` Cookie。
|
||||
4. 前端在当前平台页面的编辑区内嵌同源 `/jupyter/...` 页面,不新开浏览器
|
||||
标签页。
|
||||
5. 内嵌页面经 Nginx 访问 Jupyter HTTP/WebSocket;Nginx 先执行内部鉴权
|
||||
子请求,再把内部 Token 注入上游请求。
|
||||
|
||||
一个 Workspace 只建立或复用一个 Jupyter Runtime/Server;同一 Server
|
||||
中的每个 Notebook 使用独立 Jupyter Session 和独立 Kernel。
|
||||
|
||||
浏览器响应、URL、JavaScript 和日志中都不得出现 Jupyter 内部 Token。
|
||||
|
||||
## 2. 访问票据
|
||||
|
||||
- 票据绑定:`user_id`、`workspace_id`、`edit_session_id`、
|
||||
`runtime_id`、`jupyter_session_id`。
|
||||
- 默认有效期:60 秒;最长不超过 5 分钟。
|
||||
- Cookie:`HttpOnly; SameSite=Lax; Path=/jupyter/`,生产环境必须增加
|
||||
`Secure`。
|
||||
- 票据只授权当前 Workspace 的 Jupyter 路径,不能跨 Workspace 使用。
|
||||
- Jupyter 页面必须与平台使用同一站点入口,并允许同源 iframe 嵌入。
|
||||
|
||||
## 3. Nginx 内部鉴权接口
|
||||
|
||||
```http
|
||||
GET /internal/v1/jupyter/authorize
|
||||
Cookie: jupyter_access=<opaque-ticket>
|
||||
X-Original-URI: /jupyter/...
|
||||
X-Request-ID: <request-id>
|
||||
```
|
||||
|
||||
该接口只允许 Nginx 在内部网络调用:
|
||||
|
||||
- `204`:票据有效,响应头提供上游地址和内部认证信息。
|
||||
- `401`:票据缺失、伪造或过期。
|
||||
- `403`:用户、Workspace、Session 不匹配,或编辑锁/Runtime 已失效。
|
||||
|
||||
成功响应头:
|
||||
|
||||
```http
|
||||
X-Jupyter-Upstream: http://jupyter:8888
|
||||
X-Jupyter-Authorization: token <internal-token>
|
||||
X-Workspace-ID: <workspace-id>
|
||||
```
|
||||
|
||||
Nginx 必须删除客户端传入的 `Authorization`,使用内部鉴权结果重建上游
|
||||
认证头。
|
||||
|
||||
## 4. WebSocket 约束
|
||||
|
||||
- `/jupyter/api/kernels/*/channels` 与其他 WebSocket 路径使用 HTTP/1.1。
|
||||
- 转发 `Upgrade`、`Connection`、`Host`、`Origin` 和请求 ID。
|
||||
- 关闭代理缓冲,读超时不小于 3600 秒。
|
||||
- HTTP 和 WebSocket 使用同一票据校验规则。
|
||||
- 连接关闭只断开交互连接,不自动停止 Workspace Runtime。
|
||||
- Compose Demo 使用最长 5 分钟票据;前端必须继续发送编辑锁心跳,并在
|
||||
票据到期前重新签发。
|
||||
- Jupyter Lab HTML 中出现的内部 Token 必须由 Nginx 在返回浏览器前清除。
|
||||
@@ -1,70 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class EnsureRuntimeRequest:
|
||||
runtime_id: str
|
||||
workspace_id: str
|
||||
workspace_code: str
|
||||
owner_user_id: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RuntimeEndpoint:
|
||||
runtime_id: str
|
||||
runtime_type: str
|
||||
provider: str
|
||||
runtime_ref: str
|
||||
internal_url: str
|
||||
proxy_base_path: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RuntimeHealth:
|
||||
runtime_id: str
|
||||
healthy: bool
|
||||
checked_at: datetime
|
||||
detail: str | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CreateSessionRequest:
|
||||
runtime_id: str
|
||||
workspace_code: str
|
||||
relative_path: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RuntimeSession:
|
||||
runtime_id: str
|
||||
session_id: str
|
||||
jupyter_url: str
|
||||
reused: bool
|
||||
|
||||
|
||||
class RuntimeAdapter(Protocol):
|
||||
async def ensure_running(
|
||||
self,
|
||||
request: EnsureRuntimeRequest,
|
||||
) -> RuntimeEndpoint: ...
|
||||
|
||||
async def stop(self, runtime_id: str, reason: str) -> None: ...
|
||||
|
||||
async def restart(self, runtime_id: str) -> RuntimeEndpoint: ...
|
||||
|
||||
async def health(self, runtime_id: str) -> RuntimeHealth: ...
|
||||
|
||||
async def create_session(
|
||||
self,
|
||||
request: CreateSessionRequest,
|
||||
) -> RuntimeSession: ...
|
||||
|
||||
async def terminate_session(
|
||||
self,
|
||||
runtime_id: str,
|
||||
session_id: str,
|
||||
) -> None: ...
|
||||
@@ -1,122 +0,0 @@
|
||||
# 调度定义 API 契约 V1
|
||||
|
||||
冻结日期:2026-07-28
|
||||
状态:`implemented`
|
||||
|
||||
## 1. 范围
|
||||
|
||||
本契约只覆盖调度定义,不触发任务执行:
|
||||
|
||||
- 调度方案增删改查;
|
||||
- 稳定版本制品列表;
|
||||
- 节点和连线增删改;
|
||||
- 五段 Cron 校验与未来时间预览;
|
||||
- DAG 完整性和有向无环校验。
|
||||
|
||||
立即运行、Cron 自动触发和 Executor 执行已经由独立 Schedule 服务承接。
|
||||
|
||||
## 2. 请求上下文
|
||||
|
||||
所有接口都要求:
|
||||
|
||||
| Header | 含义 |
|
||||
|---|---|
|
||||
| `X-User-ID` | 当前用户 |
|
||||
| `X-Workspace-ID` | 当前 Workspace |
|
||||
| `X-Request-ID` | 请求追踪 ID;可省略,由服务端生成 |
|
||||
|
||||
服务端只返回当前 Workspace 的调度。节点引用的 `versions_id` 必须属于当前
|
||||
Workspace,且当前用户有权读取。
|
||||
|
||||
## 3. HTTP 接口
|
||||
|
||||
| 方法 | 路径 | 作用 |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/v1/schedule-artifacts` | 查询可加入调度的稳定版本 |
|
||||
| `POST` | `/api/v1/cron/preview` | 校验 Cron 并预览未来时间 |
|
||||
| `GET` | `/api/v1/schedules` | 查询调度列表 |
|
||||
| `POST` | `/api/v1/schedules` | 新建调度 |
|
||||
| `GET` | `/api/v1/schedules/{schedule_id}` | 查询调度及完整 DAG |
|
||||
| `PUT/PATCH` | `/api/v1/schedules/{schedule_id}` | 修改调度基本信息 |
|
||||
| `DELETE` | `/api/v1/schedules/{schedule_id}` | 软删除调度 |
|
||||
| `POST` | `/api/v1/schedules/{schedule_id}/nodes` | 新建节点 |
|
||||
| `PUT` | `/api/v1/schedules/{schedule_id}/nodes/{node_id}` | 修改节点 |
|
||||
| `DELETE` | `/api/v1/schedules/{schedule_id}/nodes/{node_id}` | 删除节点及关联连线 |
|
||||
| `POST` | `/api/v1/schedules/{schedule_id}/edges` | 新建有向连线 |
|
||||
| `PUT` | `/api/v1/schedules/{schedule_id}/edges/{edge_id}` | 修改连线条件 |
|
||||
| `DELETE` | `/api/v1/schedules/{schedule_id}/edges/{edge_id}` | 删除连线 |
|
||||
| `POST` | `/api/v1/schedules/{schedule_id}/validate` | 校验当前 DAG |
|
||||
|
||||
成功响应统一为:
|
||||
|
||||
```json
|
||||
{
|
||||
"request_id": "01...",
|
||||
"data": {},
|
||||
"meta": {}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 并发修改契约
|
||||
|
||||
新建调度时 `workflow_version=1`。每次修改调度、节点或连线都必须在请求体中
|
||||
提交当前 `workflow_version`,成功后版本号加一。
|
||||
|
||||
版本不一致时返回 `412 Precondition Failed`:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": {
|
||||
"code": "WORKFLOW_VERSION_CONFLICT",
|
||||
"message": "schedule was modified by another request",
|
||||
"expected": 3,
|
||||
"current": 4
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
前端收到 `412` 后必须重新读取调度,不得用旧画布直接覆盖。
|
||||
|
||||
## 5. 调度与 Cron 约束
|
||||
|
||||
- `trigger_type`:`manual / cron / api`;
|
||||
- `failure_policy`:`stop / continue`;
|
||||
- Cron 固定为五段:`minute hour day month weekday`;
|
||||
- 时区使用 IANA 名称,例如 `Asia/Shanghai`;
|
||||
- `cron` 类型必须提供 `cron_expression`,其他类型不得提供;
|
||||
- 新建的空调度不能直接启用;
|
||||
- 只有 DAG 校验通过的调度才能设为 `enabled=true`;
|
||||
- `next_run_at` 在数据库和接口中按 UTC 保存和返回。
|
||||
|
||||
Cron 预览请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"cron_expression": "*/5 * * * *",
|
||||
"timezone": "Asia/Shanghai",
|
||||
"count": 5,
|
||||
"base_time": "2026-07-28T08:00:00+08:00"
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 节点与 DAG 约束
|
||||
|
||||
- 节点必须引用不可变稳定版本 `versions_id`,不能引用工作副本;
|
||||
- 同一个调度内 `node_key` 唯一;
|
||||
- 节点保存超时、重试、位置、参数和环境引用;
|
||||
- 连线的起点、终点必须属于同一调度;
|
||||
- 不允许自环、重复连线和有向环;
|
||||
- 删除节点会同时删除与该节点相连的边;
|
||||
- 已有运行历史的节点不能物理删除;
|
||||
- 校验结果返回根节点、叶节点、拓扑顺序和错误列表。
|
||||
|
||||
## 7. 典型状态码
|
||||
|
||||
| 状态码 | 场景 |
|
||||
|---|---|
|
||||
| `201` | 调度、节点或连线创建成功 |
|
||||
| `404` | 调度、稳定版本、节点或连线不存在/不可见 |
|
||||
| `409` | 名称冲突、重复连线、DAG 成环或无效 DAG 启用 |
|
||||
| `412` | `workflow_version` 已过期 |
|
||||
| `422` | 请求字段、Cron、时区或节点归属不合法 |
|
||||
|
||||
Reference in New Issue
Block a user