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
1 change: 1 addition & 0 deletions cmake_ext/node_core_contracts.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ pipeline_diagnostic.h
session_context.h
validated_node_plan.h
biz_definition.h
diagnostic_code.h
20 changes: 20 additions & 0 deletions doc/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## 2026-09-14 投产前诊断身份与 Node 注册状态收敛(RFC-0058)

- **统一诊断身份体系(B1)**:
- 新增轻量级 `DiagnosticCode` 枚举(44 个精确诊断码:40 个校验码 + 4 个运行时码)与唯一名称表,基于单清单 X-Macro 统一管理。
- 彻底废除 `PipelineErrorCode` 枚举及其与 `DiagnosticCode` 的多对一降级转换表(`ValidationCodeToPipelineCode`、`PipelineErrorCodeToDiagnosticCode`)。
- `Pipeline::BuildFromJson` 在预检校验失败时直接透传首条精确 `DiagnosticCode`、路径与错误消息,消除消息前缀反解析与有损类型折叠。
- 新增类型化 `RemediationCause` 枚举(12 种修复原因),`ValidationRemediation::cause` 全量迁移为强类型枚举,`ToJson` 边界保留既有字符串映射。
- `alg_pipeline_tool` 错误域边界显式划分为基于 `DiagnosticCode` 的方案错误与独立的 `ToolError`。
- **Node 单一事实源与原子注册(B2)**:
- `NodeRegistry` 存储结构收敛为单一不可变 `Entry`(同时持有 `NodeDefinition` 与 `CreatorFunc`),消除独立 `creators_` 映射与双阶段发布异常窗口。
- 彻底移除 `PipelineCatalog::RegisterNodeDefinition`、`RegisteredNodes()` 及独立节点写入口,`PipelineCatalog` 纯化为只读外观委托。
- 抽取私有结构校验与跨节点 Control 冲突检测,保证注册事务在单一互斥锁内原子提交;失败时单调锁存冲突标志,不污染既有已发布条目。
- `Create` 采用锁外复制执行模式,彻底消除构造期与作者回调重入查询可能引起的自锁。
- 移除公开的测试清理方法,改由 `tests/support/registry_test_access.h` 提供 friend 访问的 `RegistryTestAccess` 与 `ScopedNodeState`,实现严格测试作用域隔离与还原。
- **质量验证**:
- 覆盖 D1–D7 诊断精确性矩阵,通过所有表驱动负向用例及 JSON fixture 迁移。
- 覆盖 R1–R9 原子性、并发注册竞争、无死锁重入(含 fail-after-N 内存分配故障注入)及状态隔离。
- 通过全量 ThreadSanitizer(TSan)数据竞争检测(92/92 测试 100% 通过)。
- 通过本地预提交四层门禁 `./scripts/run_all_tests.sh`(97/97 测试 100% 通过)。

## 2026-09-14 Pipeline 编排与部署预检验收修复(RFC-0057)

- 增加八类原生作者操作、128 动作/4 MiB 有界请求和 `fix-deps` 预览/原地修复;写入失败保留原文件,普通扇出保持已有默认 key,歧义目标和非法请求返回结构化错误。
Expand Down
526 changes: 526 additions & 0 deletions doc/rfcs/0058-diagnostic-and-node-registry-convergence.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 @@ -83,6 +83,7 @@ RFC-0054 是接续 RFC-0052 与已交付 RFC-0053、RFC-0055 的实施规格。`
| **RFC-0053** | 业务 Adapter 函数式作者接口与载体机制收敛 | `Completed` | 投产前 | 接入适配层 / Tooling / Docs | [0053-function-oriented-adapter-authoring.md](0053-function-oriented-adapter-authoring.md) |
| **RFC-0055** | 批次关联、分组、选择回填与拆分公共工具 | `Completed` | 投产前 / Catalog v3 | 能力节点层 / Tooling / Docs | [0055-traceable-batch-operations.md](0055-traceable-batch-operations.md) |
| **RFC-0056** | 投产前框架兼容入口与配置收口(第一批瘦身落地) | `Completed` | 投产前 | 四层 / Build / Tooling / Docs | [0056-batch-1-framework-slimming.md](0056-batch-1-framework-slimming.md) |
| **RFC-0058** | 投产前诊断身份与 Node 注册状态收敛 | `Completed` | 投产前 | 流程编排层、能力节点层、接入适配层 / Tooling | [0058-diagnostic-and-node-registry-convergence.md](0058-diagnostic-and-node-registry-convergence.md) |

## 专项验收与评审归档

Expand Down
59 changes: 59 additions & 0 deletions include/core/diagnostic_code.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#pragma once

namespace llm_edgeflow {

#define LLM_EDGEFLOW_DIAGNOSTIC_CODES(X) \
X(kOk, "OK") \
X(kJsonParse, "JSON_PARSE") \
X(kConfigFileOpen, "CONFIG_FILE_OPEN") \
X(kRootType, "ROOT_TYPE") \
X(kUnknownField, "UNKNOWN_FIELD") \
X(kMissingField, "MISSING_FIELD") \
X(kFieldType, "FIELD_TYPE") \
X(kFieldRange, "FIELD_RANGE") \
X(kInvalidCombination, "INVALID_COMBINATION") \
X(kDuplicateModelId, "DUPLICATE_MODEL_ID") \
X(kDuplicateNodeId, "DUPLICATE_NODE_ID") \
X(kUnknownBiz, "UNKNOWN_BIZ") \
X(kUnknownNodeType, "UNKNOWN_NODE_TYPE") \
X(kUnknownModelType, "UNKNOWN_MODEL_TYPE") \
X(kUnknownBackend, "UNKNOWN_BACKEND") \
X(kModelCapabilityMismatch, "MODEL_CAPABILITY_MISMATCH") \
X(kBackendProtocolMismatch, "BACKEND_PROTOCOL_MISMATCH") \
X(kUnknownModelConfigField, "UNKNOWN_MODEL_CONFIG_FIELD") \
X(kUnknownBackendConfigField, "UNKNOWN_BACKEND_CONFIG_FIELD") \
X(kInvalidDependency, "INVALID_DEPENDENCY") \
X(kDuplicateDependency, "DUPLICATE_DEPENDENCY") \
X(kDagCycle, "DAG_CYCLE") \
X(kRegistryConflict, "REGISTRY_CONFLICT") \
X(kUnknownConfigField, "UNKNOWN_CONFIG_FIELD") \
X(kMissingConfigField, "MISSING_CONFIG_FIELD") \
X(kConfigFieldType, "CONFIG_FIELD_TYPE") \
X(kConfigFieldRange, "CONFIG_FIELD_RANGE") \
X(kConfigFieldEnum, "CONFIG_FIELD_ENUM") \
X(kUnknownModelReference, "UNKNOWN_MODEL_REFERENCE") \
X(kNodeBizMismatch, "NODE_BIZ_MISMATCH") \
X(kMissingInputProducer, "MISSING_INPUT_PRODUCER") \
X(kDuplicatePortProducer, "DUPLICATE_PORT_PRODUCER") \
X(kMissingBizOutput, "MISSING_BIZ_OUTPUT") \
X(kNodeNotParallelSafe, "NODE_NOT_PARALLEL_SAFE") \
X(kParallelWriteConflict, "PARALLEL_WRITE_CONFLICT") \
X(kSerializedModelConcurrency, "SERIALIZED_MODEL_CONCURRENCY") \
X(kPortCardinalityMismatch, "PORT_CARDINALITY_MISMATCH") \
X(kPortProvenanceMismatch, "PORT_PROVENANCE_MISMATCH") \
X(kPortLifetimeMismatch, "PORT_LIFETIME_MISMATCH") \
X(kInternalException, "INTERNAL_EXCEPTION") \
X(kModelMaterializationFailed, "MODEL_MATERIALIZATION_FAILED") \
X(kNodeCreateFailed, "NODE_CREATE_FAILED") \
X(kNodeInitFailed, "NODE_INIT_FAILED") \
X(kInvalidBuildState, "INVALID_BUILD_STATE")

enum class DiagnosticCode {
#define LLM_EDGEFLOW_DEF_ENUM(name, str) name,
LLM_EDGEFLOW_DIAGNOSTIC_CODES(LLM_EDGEFLOW_DEF_ENUM)
#undef LLM_EDGEFLOW_DEF_ENUM
};

const char* DiagnosticCodeName(DiagnosticCode code) noexcept;

} // namespace llm_edgeflow
106 changes: 35 additions & 71 deletions include/core/node_registry.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#pragma once

#include <algorithm>
#include <atomic>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
Expand All @@ -15,6 +17,16 @@

namespace llm_edgeflow {

namespace test_support {
class RegistryTestAccess;
}

struct NodeRegistrySnapshot {
std::vector<NodeDefinition> definitions; // node_type 升序,独立值副本
bool has_conflict = false;
std::vector<std::string> conflict_errors;
};

class NodeRegistry {
public:
using CreatorFunc = std::function<std::unique_ptr<INode>()>;
Expand Down Expand Up @@ -52,87 +64,39 @@ class NodeRegistry {
}
}

std::unique_ptr<INode> Create(const std::string& node_type) const {
CreatorFunc creator;
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = creators_.find(node_type);
if (it == creators_.end()) return nullptr;
creator = it->second;
}
// R1-ACC-004: 锁外执行外部 creator,避免嵌套查询或构造导致自锁
return creator();
}
std::unique_ptr<INode> Create(const std::string& node_type) const;
bool Has(const std::string& node_type) const;
std::vector<std::string> ListTypes() const;
bool HasConflict() const;
std::vector<std::string> GetConflictErrors() const;

bool Has(const std::string& node_type) const {
std::lock_guard<std::mutex> lock(mutex_);
return creators_.find(node_type) != creators_.end();
}

std::vector<std::string> ListTypes() const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::string> result;
result.reserve(creators_.size());
for (const auto& item : creators_) result.push_back(item.first);
std::sort(result.begin(), result.end());
return result;
}

bool HasConflict() const {
std::lock_guard<std::mutex> lock(mutex_);
return has_conflict_;
}

std::vector<std::string> GetConflictErrors() const {
std::lock_guard<std::mutex> lock(mutex_);
return conflict_errors_;
}
NodeRegistrySnapshot Snapshot() const;
std::optional<NodeDefinition> Find(const std::string& node_type) const;
std::vector<NodeDefinition> ListDefinitions() const;

void ClearForTesting() {
std::lock_guard<std::mutex> lock(mutex_);
creators_.clear();
has_conflict_ = false;
conflict_errors_.clear();
}
private:
friend class test_support::RegistryTestAccess;

void ClearConflictForTesting() noexcept {
std::lock_guard<std::mutex> lock(mutex_);
has_conflict_ = false;
conflict_errors_.clear();
}
struct Entry {
NodeDefinition definition;
CreatorFunc creator;
};
using EntryHandle = std::shared_ptr<const Entry>;

private:
void RecordRegistrationFailure(std::string_view message) noexcept {
try {
std::lock_guard<std::mutex> lock(mutex_);
has_conflict_ = true;
conflict_errors_.emplace_back(message);
} catch (...) {
// Registration remains failed even if its diagnostic cannot be stored.
}
}
static bool CheckCrossNodeControlConflict(
const NodeDefinition& definition,
const std::unordered_map<std::string, EntryHandle>& entries,
std::string* error);

void RecordRegistrationFailure(std::string_view message) noexcept;
void RecordRegistrationFailure(std::string_view prefix,
std::string_view separator,
std::string_view message) noexcept {
try {
std::lock_guard<std::mutex> lock(mutex_);
has_conflict_ = true;
std::string full;
full.reserve(prefix.size() + separator.size() + message.size());
full.append(prefix);
full.append(separator);
full.append(message);
conflict_errors_.push_back(std::move(full));
} catch (...) {
// Registration remains failed even if its diagnostic cannot be stored.
}
}
std::string_view message) noexcept;

NodeRegistry() = default;
mutable std::mutex mutex_;
std::unordered_map<std::string, CreatorFunc> creators_;
bool has_conflict_ = false;
std::unordered_map<std::string, EntryHandle> entries_;
std::atomic<bool> has_conflict_{false};
std::vector<std::string> conflict_errors_;
};

Expand Down
16 changes: 12 additions & 4 deletions include/core/pipeline_catalog.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,22 @@

namespace llm_edgeflow {

namespace test_support {
class RegistryTestAccess;
}

struct PipelineCatalogSnapshot {
std::vector<NodeDefinition> nodes;
std::vector<BizDefinition> bizs;
bool node_registry_has_conflict = false;
std::vector<std::string> node_registry_errors;

const NodeDefinition* FindNode(const std::string& node_type) const;
const BizDefinition* FindBiz(const std::string& biz_name) const;
};

class PipelineCatalog {
public:
static bool RegisterNodeDefinition(const NodeDefinition& definition,
std::string* error = nullptr);
static bool RegisterBizDefinition(const BizDefinition& definition);
static bool RegisterBizDefinitions(
const std::vector<BizDefinition>& definitions);
Expand All @@ -43,12 +47,16 @@ class PipelineCatalog {
const std::string& backend_type);
static std::optional<BizDefinition> FindBiz(const std::string& biz_name);

static void ClearForTesting();

static nlohmann::json ToJson(const PipelineCatalogSnapshot& snapshot,
const std::string& biz_filter = std::string());
static nlohmann::json ToJson(const std::string& biz_filter = std::string());
static nlohmann::json NodeToJson(const NodeDefinition& definition);
static nlohmann::json ModelToJson(const ModelDefinition& definition);
static nlohmann::json BackendToJson(const BackendDefinition& definition);

private:
friend class test_support::RegistryTestAccess;
static void ResetBizsForTesting();
};

} // namespace llm_edgeflow
36 changes: 5 additions & 31 deletions include/core/pipeline_diagnostic.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,22 @@

#include <string>

namespace llm_edgeflow {
#include "core/diagnostic_code.h"

/**
* @brief 结构化错误诊断码 (PipelineErrorCode)
*/
enum class PipelineErrorCode {
kOk = 0,
kJsonParse,
kConfigFileOpen,
kRootType,
kUnknownField,
kMissingField,
kFieldType,
kFieldRange,
kInvalidCombination,
kDuplicateModelId,
kDuplicateNodeId,
kUnknownNodeType,
kUnknownModelType,
kUnknownBackend,
kInvalidDependency,
kDagCycle,
kRegistryConflict,
kModelMaterializationFailed,
kNodeCreateFailed,
kNodeInitFailed,
kInternalException,
kInvalidBuildState,
};
namespace llm_edgeflow {

/**
* @brief 轻量级结构化诊断信息 (PipelineDiagnostic)
*/
struct PipelineDiagnostic {
PipelineErrorCode code = PipelineErrorCode::kOk;
DiagnosticCode code = DiagnosticCode::kOk;
std::string path;
std::string message;

bool IsOk() const { return code == PipelineErrorCode::kOk; }
bool IsOk() const { return code == DiagnosticCode::kOk; }

void Clear() {
code = PipelineErrorCode::kOk;
code = DiagnosticCode::kOk;
path.clear();
message.clear();
}
Expand Down
Loading
Loading