Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions doc/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## 2026-09-17 Integration 部署解析入口统一实施(RFC-0062)

- **统一共享部署准备流程(`PrepareDeploymentDocument`)**:
- 新增 `src/adapter/deployment_diagnostic.h` 轻量错误载体与 `src/adapter/deployment_preparation.h/.cpp` 共享部署准备实现(S1–S7)。
- 严格消除 `alg_pipeline_tool.cpp` 中的 `ResolveDeploymentBoundary` 及 `pipeline_authoring.cpp` 中的重复部署边界构造逻辑。
- 强制执行覆盖不可掩盖原始非法声明规则(T03/T04):原始模型路径缺失、类型非字符串、空串或结构错误在覆盖生效前由核心解析器(`ParsePipelineConfig`)识别并拒绝。
- **结构化诊断与来源精准投影**:
- 建立 `ProjectModelPathDiagnostics` 机制,将 Core 对有效模型的诊断精准映射回原始完整文档路径(有覆盖映射至 `/deployment/model_paths/<escaped_id>`,无覆盖映射至 `/models/<i>/model_path`)。
- `validate-io` 直接传递底层结构化诊断,不再从错误文案中脆弱提取 JSON Pointer。
- `PrepareDeploymentDocument` 实行原子化失败清理(T18),失败立即重置局部对象,保证无残留状态。
- **各调用面与工具链行为收敛**:
- `src/tools/pipeline_document_validation.h/.cpp` 统一 `validate`(`kValidate`)、`validate --explain`(`kExplain`)、`plan`(`kPlan`)及 Pipeline Authoring(`edit`、`fix-deps`)的文档校验与计划编排。
- 计划失败时保持规范包络(包含空层与拓扑顺序及完整诊断列表);`edit` 与 `fix-deps` 补齐部署校验,防止未知绑定或输出配置非法。

## 2026-09-17 部署配置归拢至 Pipeline 根文档(RFC-0061)

- **部署配置归拢至 Pipeline 文档**:
Expand Down
603 changes: 603 additions & 0 deletions doc/rfcs/0062-unified-integration-deployment-preparation.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions doc/rfcs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ RFC-0054 是接续 RFC-0052 与已交付 RFC-0053、RFC-0055 的实施规格。`
| **RFC-0059** | 输入输出转换独立化与接入绑定架构 | `Completed` | 投产前 / ABI 6.0.0 | 接入适配层、流程编排层 / Tooling / Docs | [0059-independent-adapter-input-and-output.md](0059-independent-adapter-input-and-output.md)(宿主双入口部分被 RFC-0060 取代) |
| **RFC-0060** | 删除 C ABI,仅保留 C++ Operator API | `Completed` | `v11.0.0` / ABI 7.0.0 | 接入适配层、流程编排层 / Tooling / Docs | [0060-cpp-operator-only.md](0060-cpp-operator-only.md) |
| **RFC-0061** | Pipeline JSON 集中管理部署配置 | `Completed` | `v11.x` | 接入适配层、流程编排层 / Tooling / Docs | [0061-pipeline-owned-deployment-configuration.md](0061-pipeline-owned-deployment-configuration.md) |
| **RFC-0062** | Integration 部署解析入口统一实施设计 | `Completed` | 投产前 | 接入适配层、流程编排层 / Tooling | [0062-unified-integration-deployment-preparation.md](0062-unified-integration-deployment-preparation.md) |

## 专项验收与评审归档

Expand Down
1 change: 1 addition & 0 deletions src/adapter/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
target_sources(edgeflow_integration_objects PRIVATE
deployment_model_resolver.cpp
deployment_preparation.cpp
io_converter_registry.cpp
io_binding_registry.cpp
deployment_io_config.cpp
Expand Down
33 changes: 33 additions & 0 deletions src/adapter/deployment_diagnostic.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include <optional>
#include <string>

#include "core/diagnostic_code.h"
#include "core/pipeline_diagnostic.h"

namespace llm_edgeflow {

/**
* @brief 轻量级部署诊断载体 (RFC-0062)
*
* 用于在 Integration 内部及工具调用面跨函数传递结构化错误 (code, path, message,
* legacy_status).
*/
struct DeploymentDiagnostic {
std::string code; // Integration 错误码,或 DiagnosticCodeName 的结果
std::string path; // 原始完整文档的 RFC 6901 JSON Pointer
std::string message;
int legacy_status = -2;
std::optional<PipelineDiagnostic> pipeline_diagnostic;

void Clear() {
code.clear();
path.clear();
message.clear();
legacy_status = -2;
pipeline_diagnostic.reset();
}
};

} // namespace llm_edgeflow
119 changes: 83 additions & 36 deletions src/adapter/deployment_io_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,54 @@
#include <filesystem>
#include <fstream>

#include "adapter/pipeline_document.h"
#include "contracts/path_utils.h"

namespace llm_edgeflow {

namespace fs = std::filesystem;

static void SetConfigDiag(DeploymentDiagnostic* out_diag,
const std::string& code, const std::string& path,
const std::string& message) {
if (out_diag) {
out_diag->code = code;
out_diag->path = path;
out_diag->message = message;
out_diag->legacy_status = -2;
out_diag->pipeline_diagnostic.reset();
}
}

bool DeploymentIoConfig::ReadFromFile(const std::string& config_path,
const std::string& transport,
DeploymentIoConfig* out_config,
std::string* out_error) {
std::string* out_error,
DeploymentDiagnostic* out_diagnostic) {
if (out_diagnostic) out_diagnostic->Clear();

if (config_path.empty()) {
if (out_error) *out_error = "Empty config_path";
SetConfigDiag(out_diagnostic, "DEPLOYMENT_ERROR", "/", "Empty config_path");
return false;
}

std::ifstream ifs(config_path);
if (!ifs.is_open()) {
if (out_error) *out_error = "Failed to open config file: " + config_path;
std::string msg = "Failed to open config file: " + config_path;
if (out_error) *out_error = msg;
SetConfigDiag(out_diagnostic, "CONFIG_FILE_OPEN", "/", msg);
return false;
}

nlohmann::json root;
try {
ifs >> root;
} catch (const std::exception& e) {
if (out_error) {
*out_error = "JSON parse exception in " + config_path + ": " + e.what();
}
std::string msg =
"JSON parse exception in " + config_path + ": " + e.what();
if (out_error) *out_error = msg;
SetConfigDiag(out_diagnostic, "JSON_PARSE", "/", msg);
return false;
}

Expand All @@ -40,64 +60,90 @@ bool DeploymentIoConfig::ReadFromFile(const std::string& config_path,
}
cfg_dir = fs::absolute(cfg_dir);

return Parse(root, cfg_dir.string(), transport, out_config, out_error);
std::string parse_err;
DeploymentDiagnostic parse_diag;
bool ok = Parse(root, cfg_dir.string(), transport, out_config, &parse_err,
&parse_diag);
if (!ok) {
std::string prefix = "Error in config file " + config_path + ": ";
if (out_error) *out_error = prefix + parse_err;
if (out_diagnostic) {
*out_diagnostic = parse_diag;
out_diagnostic->message = prefix + out_diagnostic->message;
}
return false;
}
if (out_diagnostic) *out_diagnostic = parse_diag;
return true;
}

bool DeploymentIoConfig::Parse(const nlohmann::json& root,
const std::string& config_dir,
const std::string& transport,
DeploymentIoConfig* out_config,
std::string* out_error) {
std::string* out_error,
DeploymentDiagnostic* out_diagnostic) {
if (out_diagnostic) out_diagnostic->Clear();

if (!out_config) {
if (out_error) *out_error = "Null out_config pointer";
SetConfigDiag(out_diagnostic, "DEPLOYMENT_ERROR", "/",
"Null out_config pointer");
return false;
}

if (!root.is_object()) {
if (out_error) *out_error = "Root configuration must be a JSON object";
SetConfigDiag(out_diagnostic, "DEPLOYMENT_ERROR", "/",
"Root configuration must be a JSON object");
return false;
}

if (transport != "operator") {
if (out_error) {
*out_error = "Unsupported transport: '" + transport +
"' (only 'operator' is supported)";
}
std::string msg = "Unsupported transport: '" + transport +
"' (only 'operator' is supported)";
if (out_error) *out_error = msg;
SetConfigDiag(out_diagnostic, "UNSUPPORTED_TRANSPORT", "/", msg);
return false;
}

// 1. 检查并明确拒绝旧 Schema 1 字段及外部分散配置 (RFC-0061)
for (const char* deprecated_key :
{"data", "schema_version", "io_binding", "model_paths", "outputs"}) {
if (root.contains(deprecated_key)) {
if (out_error) {
*out_error =
std::string(
"Deprecated deployment configuration format (RFC-0061) at /") +
deprecated_key +
": '.conf' files must contain only 'pipe_path'. Deployment "
"configuration "
"(io_binding, output_allocations, model_paths) has moved to the "
"'deployment' section inside the Pipeline JSON.";
}
std::string escaped_key = EscapeJsonPointer(deprecated_key);
std::string msg =
std::string(
"Deprecated deployment configuration format (RFC-0061) at /") +
escaped_key +
": '.conf' files must contain only 'pipe_path'. Deployment "
"configuration "
"(io_binding, output_allocations, model_paths) has moved to the "
"'deployment' section inside the Pipeline JSON.";
if (out_error) *out_error = msg;
SetConfigDiag(out_diagnostic, "DEPLOYMENT_ERROR",
std::string("/") + escaped_key, msg);
return false;
}
}

// 2. 根字段白名单: 必须有且仅有 pipe_path
for (auto it = root.begin(); it != root.end(); ++it) {
if (it.key() != "pipe_path") {
if (out_error) {
*out_error = "Unknown field at /: '" + it.key() +
"' (only 'pipe_path' is allowed under RFC-0061)";
}
std::string escaped_key = EscapeJsonPointer(it.key());
std::string msg = "Unknown field at /: '" + it.key() +
"' (only 'pipe_path' is allowed under RFC-0061)";
if (out_error) *out_error = msg;
SetConfigDiag(out_diagnostic, "DEPLOYMENT_ERROR", "/" + escaped_key, msg);
return false;
}
}

if (!root.contains("pipe_path") || !root["pipe_path"].is_string() ||
root["pipe_path"].get<std::string>().empty()) {
if (out_error) *out_error = "Missing or empty 'pipe_path'";
SetConfigDiag(out_diagnostic, "DEPLOYMENT_ERROR", "/pipe_path",
"Missing or empty 'pipe_path'");
return false;
}

Expand All @@ -116,27 +162,28 @@ bool DeploymentIoConfig::Parse(const nlohmann::json& root,
fs::path canonical_pipe = fs::weakly_canonical(full_pipe, ec);

if (!IsPathWithinRoot(canonical_base, canonical_pipe)) {
if (out_error) {
*out_error =
"pipe_path escapes config directory: " + out_config->pipe_path;
}
std::string msg =
"pipe_path escapes config directory: " + out_config->pipe_path;
if (out_error) *out_error = msg;
SetConfigDiag(out_diagnostic, "DEPLOYMENT_ERROR", "/pipe_path", msg);
return false;
}

if (!fs::exists(canonical_pipe)) {
if (out_error) {
*out_error = "Pipeline file does not exist: " + canonical_pipe.string();
}
std::string msg =
"Pipeline file does not exist: " + canonical_pipe.string();
if (out_error) *out_error = msg;
SetConfigDiag(out_diagnostic, "DEPLOYMENT_ERROR", "/pipe_path", msg);
return false;
}

// 校验符号链接目标,防止符号链接逃出配置目录
fs::path real_pipe = fs::canonical(canonical_pipe, ec);
if (ec || !IsPathWithinRoot(canonical_base, real_pipe)) {
if (out_error) {
*out_error =
"pipe_path escapes config directory: " + out_config->pipe_path;
}
std::string msg =
"pipe_path escapes config directory: " + out_config->pipe_path;
if (out_error) *out_error = msg;
SetConfigDiag(out_diagnostic, "DEPLOYMENT_ERROR", "/pipe_path", msg);
return false;
}

Expand Down
8 changes: 6 additions & 2 deletions src/adapter/deployment_io_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <nlohmann/json.hpp>
#include <string>

#include "adapter/deployment_diagnostic.h"

namespace llm_edgeflow {

/**
Expand All @@ -15,12 +17,14 @@ struct DeploymentIoConfig {

static bool Parse(const nlohmann::json& root, const std::string& config_dir,
const std::string& transport,
DeploymentIoConfig* out_config, std::string* out_error);
DeploymentIoConfig* out_config, std::string* out_error,
DeploymentDiagnostic* out_diagnostic = nullptr);

static bool ReadFromFile(const std::string& config_path,
const std::string& transport,
DeploymentIoConfig* out_config,
std::string* out_error);
std::string* out_error,
DeploymentDiagnostic* out_diagnostic = nullptr);
};

} // namespace llm_edgeflow
Loading
Loading