第一章:从日志到知识图谱的Go文本提取全景概览
现代可观测性系统每天生成海量结构化与半结构化日志,其中蕴含着服务调用链路、异常模式、依赖关系等隐性知识。将原始日志转化为可推理、可查询的知识图谱,需跨越文本解析、实体识别、关系抽取与图谱建模四层技术栈——而Go语言凭借其高并发处理能力、静态编译特性和丰富的标准库,正成为构建高性能日志语义提取管道的理想选择。
日志解析的核心挑战
- 格式异构性:Nginx访问日志、JSON结构化日志、Kubernetes容器日志共存,需统一抽象为
LogEntry结构体; - 时序敏感性:同一事务的日志散落在不同Pod/进程,需基于traceID或时间窗口聚合;
- 语义稀疏性:错误日志中“connection refused”需映射为
ServiceUnavailable → dependsOn → DatabaseService关系。
Go文本提取关键组件
使用golang.org/x/exp/utf8string高效切分长日志行;通过正则预编译(regexp.MustCompile)匹配常见模式(如(?P<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}));借助github.com/goccy/go-json替代标准库encoding/json提升JSON日志解析吞吐量30%以上。
构建可扩展提取流水线
// 定义日志处理器链式接口
type LogProcessor interface {
Process(*LogEntry) (*LogEntry, error)
}
// 示例:提取HTTP状态码并标注为节点属性
func NewStatusAnnotator() LogProcessor {
return &statusAnnotator{}
}
func (s *statusAnnotator) Process(entry *LogEntry) (*LogEntry, error) {
if matches := statusRE.FindStringSubmatch(entry.Raw); len(matches) > 0 {
entry.Attributes["http_status"] = string(matches[0]) // 注入语义属性
entry.KGNodes = append(entry.KGNodes, KGNode{
ID: "http_status_" + string(matches[0]),
Type: "HTTPStatusCode",
Props: map[string]string{"code": string(matches[0])},
})
}
return entry, nil
}
该设计支持动态注册处理器(如IPGeolocator、ErrorClassifier),输出带语义标签的中间表示,为后续图谱构建提供标准化输入。
第二章:Go语言文本预处理与结构化解析核心实践
2.1 基于正则与Unicode规范的日志行解析器设计与性能优化
日志解析需兼顾多语言兼容性与毫秒级吞吐,核心挑战在于混合编码边界识别与回溯抑制。
Unicode感知的行切分策略
采用 \R(Unicode换行符)替代 \n,覆盖 U+2028(LINE SEPARATOR)、U+2029(PARAGRAPH SEPARATOR)等:
^(?<timestamp>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)\s+(?<level>[A-Z]+)\s+(?<msg>[\p{L}\p{N}\p{P}\p{Zs}&&[^\\]]+)$
\p{L}匹配任意语言字母(含中文、阿拉伯文)\p{Zs}安全包含全角空格(U+3000),避免截断&&[^\\]排除转义序列干扰,防止误匹配
性能关键路径优化
| 优化项 | 未优化耗时 | 优化后耗时 | 提升幅度 |
|---|---|---|---|
| 回溯正则 | 12.7 ms | — | — |
RegexOptions.Compiled + 预编译 |
— | 3.2 ms | 75% |
| Unicode属性缓存 | — | 2.8 ms | +12% |
graph TD
A[原始日志流] --> B{Unicode换行切分}
B --> C[预编译正则匹配]
C --> D[属性缓存加速\p{L}]
D --> E[结构化LogEntry]
2.2 多编码、流式分块与内存映射(mmap)驱动的大日志文件高效读取
处理TB级日志时,传统readline()易因编码不一致崩溃,且逐行加载导致OOM。需协同解决三重挑战:
编码自适应检测
使用chardet预检+codecs.iterdecode流式解码:
import chardet, codecs
with open("app.log", "rb") as f:
raw = f.read(10000) # 采样头部
enc = chardet.detect(raw)["encoding"] or "utf-8"
f.seek(0)
decoder = codecs.getincrementaldecoder(enc)(errors="replace")
→ 逻辑:仅采样不加载全量;errors="replace"保障解码鲁棒性;getincrementaldecoder支持分块解码。
mmap + 分块迭代
import mmap
with open("app.log", "r+b") as f:
mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
for i in range(0, len(mm), 8192): # 8KB分块
chunk = mm[i:i+8192]
lines = chunk.split(b"\n")
→ 参数:ACCESS_READ避免写入开销;固定块大小平衡IO与内存碎片。
| 方案 | 内存峰值 | 支持编码切换 | 随机跳转 |
|---|---|---|---|
open().readlines() |
高 | 否 | 否 |
mmap + split() |
极低 | 是(配合解码器) | 是 |
graph TD
A[原始二进制流] --> B{编码检测}
B --> C[增量解码器]
C --> D[流式UTF-8行迭代]
A --> E[mmap分块]
E --> F[按需页加载]
D & F --> G[零拷贝日志解析]
2.3 文本归一化管道:标点标准化、空格压缩与时间戳统一格式化
文本归一化是语音转写后处理的关键环节,直接影响下游NLU与检索效果。
标点标准化
将全角标点(,。!?;:“”)统一映射为半角(,.!?;:””),并修复孤立引号配对:
import re
def normalize_punctuation(text):
# 全角→半角映射(简化版)
text = re.sub(r',', ',', text)
text = re.sub(r'。', '.', text)
text = re.sub(r'“(.+?)”', r'"\1"', text) # 智能引号闭合
return text
逻辑说明:正则按优先级逐层替换;r'“(.+?)”'使用非贪婪捕获确保最短匹配,避免跨句误吞;引号重写保障JSON兼容性。
空格压缩与时间戳格式化
- 合并连续空白符为单个空格
- 将
HH:MM:SS,mmm/HH:MM:SS.mmm统一为 ISO 8601 扩展格式HH:MM:SS.fff
| 输入格式 | 归一化输出 | 说明 |
|---|---|---|
01:02:03,456 |
01:02:03.456 |
逗号→英文句点 |
1:2:3.78 |
01:02:03.078 |
补零+毫秒三位对齐 |
graph TD
A[原始文本] --> B[标点标准化]
B --> C[空格压缩]
C --> D[时间戳解析与重格式化]
D --> E[归一化文本]
2.4 上下文感知的段落切分与会话边界识别(基于滑动窗口与启发式规则)
传统按标点或长度切分易破坏语义连贯性。本方案融合时序上下文与对话行为特征,动态判定会话断点。
核心策略
- 滑动窗口扫描连续文本流(默认窗口大小
w=5句) - 在窗口内聚合三类信号:停顿时长、说话人切换、话题关键词突变
- 应用启发式规则优先级判定边界(如
speaker_change ∧ pause > 1.2s → 强切分)
规则引擎示例
def is_session_break(prev, curr, pause_sec):
# prev/curr: dict with 'speaker', 'topic_vec', 'timestamp'
speaker_shift = prev["speaker"] != curr["speaker"]
long_pause = pause_sec > 1.2
topic_drift = cosine_dist(prev["topic_vec"], curr["topic_vec"]) > 0.65
return speaker_shift and (long_pause or topic_drift) # 启发式组合逻辑
该函数以说话人切换为必要条件,叠加暂停或语义漂移任一充分条件,避免误切;cosine_dist 衡量主题向量余弦距离,阈值 0.65 经验证在客服对话数据集上F1达0.89。
决策权重配置表
| 信号类型 | 权重 | 触发条件 |
|---|---|---|
| 说话人变更 | 0.45 | prev.speaker ≠ curr.speaker |
| 静音 ≥1.2s | 0.30 | pause_sec ≥ 1.2 |
| 主题向量距离 | 0.25 | cos_dist > 0.65 |
graph TD
A[输入连续话语流] --> B[滑动窗口提取5句上下文]
B --> C{规则引擎并行评估}
C --> D[说话人切换?]
C --> E[静音>1.2s?]
C --> F[主题漂移>0.65?]
D & E & F --> G[加权投票判定边界]
2.5 日志模板自动抽取:基于频繁模式挖掘(PrefixSpan变体)的结构模式归纳
传统正则手工定义模板难以覆盖动态服务日志的多样性。我们改进PrefixSpan算法,聚焦日志事件序列的前缀约束挖掘,跳过全局频繁项集生成,直接在token化日志流上执行带长度阈值与支持度下限的前缀投影。
核心优化点
- 引入时间窗口滑动机制,保障时序局部性
- 对
<timestamp><level><service><msg>四元组做语义分层编码(如将[ERROR]→L2) - 投影阶段剪枝低频分支,保留top-k高置信前缀路径
示例:日志序列预处理
def tokenize_log(line):
# 输入: "2024-03-15T08:22:14Z ERROR auth-service failed to validate token: invalid sig"
parts = re.split(r'\s+', line.strip(), maxsplit=3) # 仅切分前3空格,保留msg完整
return [parts[0], level_map.get(parts[1], 'L0'), parts[2].replace('-', '_'), parts[3]]
# 输出: ['2024-03-15T08:22:14Z', 'L2', 'auth_service', 'failed to validate token: invalid sig']
逻辑分析:maxsplit=3确保消息体不被误切;level_map统一日志级别编码,提升跨系统模式泛化能力;replace('-', '_')消除服务名中连字符对序列对齐的干扰。
| 参数 | 值 | 说明 |
|---|---|---|
min_support |
0.05 | 全局日志中出现频率 ≥5% |
max_prefix_len |
4 | 防止过长模板导致泛化失效 |
window_sec |
60 | 滑动时间窗,保障上下文一致性 |
graph TD
A[原始日志流] --> B[Token化 & 语义编码]
B --> C{滑动时间窗聚合}
C --> D[PrefixSpan变体挖掘]
D --> E[高频前缀 → 模板候选]
E --> F[正则化泛化:\\d+ → <num>]
第三章:面向知识图谱构建的NER标注对齐工程体系
3.1 Go原生CRF++/BERT轻量封装:支持ONNX Runtime的命名实体识别推理层
为兼顾性能与部署灵活性,本层采用Go语言构建统一推理接口,底层桥接CRF++(传统特征工程)与BERT-based ONNX模型(语义建模),通过gorgonia与onnxruntime-go双后端调度。
架构概览
graph TD
A[Go API] --> B{Model Router}
B --> C[CRF++ C API via CGO]
B --> D[ONNX Runtime Session]
D --> E[Tokenizer → Tensor]
核心封装特性
- ✅ 零拷贝内存共享:
unsafe.Slice复用输入字节切片至ONNX输入张量 - ✅ 动态模型加载:支持
.onnx与.crf双格式热切换 - ✅ 批处理对齐:自动pad/truncate至BERT最大长度(默认128)
推理调用示例
// 初始化ONNX会话(自动绑定CUDA/CPU)
sess, _ := ort.NewSession("ner-bert.onnx", ort.SessionOptions{})
input := ort.NewTensor(ort.Float32, []int64{1, 128}, tokens) // tokens: int32 slice
output, _ := sess.Run(ort.NewValueMap().Set("input_ids", input))
// output["logits"] shape: [1,128,9] → CRF解码得实体标签
tokens需经WordPiece分词并映射为ID;logits维度[batch, seq_len, num_labels]直接馈入CRF解码器,跳过Python层开销。
3.2 标注-原始文本双向对齐算法:基于字符偏移映射与编辑距离约束的鲁棒匹配
核心思想
将标注片段(如实体边界)与原始文本建立可逆字符级映射,同时容忍 OCR 错误、空格归一化、标点增删等常见扰动。
算法流程
def align_span(annotated_span: str, raw_text: str, max_edit_dist: int = 3) -> Optional[Tuple[int, int]]:
# 在 raw_text 中搜索编辑距离 ≤ max_edit_dist 的最左最优子串
from Levenshtein import distance
best_match = None
min_dist = float('inf')
for i in range(len(raw_text)):
for j in range(i + len(annotated_span), min(i + len(annotated_span) + 4, len(raw_text) + 1)):
candidate = raw_text[i:j].strip()
dist = distance(annotated_span, candidate)
if dist <= max_edit_dist and dist < min_dist:
min_dist = dist
best_match = (i, j)
return best_match # 返回原始文本中起止字符偏移(含)
逻辑说明:遍历所有可能子串,以编辑距离为软约束筛选候选;
max_edit_dist=3覆盖单字错别、漏字、多空格等典型噪声;返回(start, end)为 Unicode 字符偏移,支持 UTF-8 安全双向映射。
对齐质量评估指标
| 指标 | 含义 | 合格阈值 |
|---|---|---|
| 字符偏移误差 | 对齐起始位置绝对偏差 | ≤ 2 |
| 编辑距离归一化比 | dist / max(len(a),len(b)) |
≤ 0.15 |
| 映射可逆性 | 反向还原标注是否一致 | 100% |
graph TD
A[标注文本 Span] --> B{Levenshtein 搜索}
B --> C[候选子串集合]
C --> D[按距离/位置排序]
D --> E[取首个满足约束的区间]
E --> F[返回 raw_text 字符偏移]
3.3 领域自适应NER微调框架:LoRA注入+动态负采样在Go训练调度器中的实现
为适配金融公告、医疗报告等垂直领域文本,我们在Go编写的轻量级训练调度器中融合LoRA低秩适配与动态负采样机制。
LoRA层注入策略
采用秩r=8、α=16的LoRA模块,在BERT-base的query和value投影层插入可训练A/B矩阵(A∈ℝ^{d×r}, B∈ℝ^{r×d}),冻结原始权重:
// lora_layer.go: 在Transformer层前向中注入
func (l *LoRALayer) Forward(x tensor.Tensor) tensor.Tensor {
original := l.Linear(x) // 原始线性变换 Wx
delta := mat.MatMul(l.B, mat.MatMul(l.A, x)) // ΔW = BAx
return tensor.Add(original, tensor.Scale(l.Alpha/float64(l.Rank), delta))
}
Alpha/Rank控制增量更新幅度,避免破坏预训练语义;Scale项确保梯度稳定。
动态负采样调度
每批次依据当前实体边界F1动态调整负样本比例(0.3→0.7),提升难例学习效率:
| Epoch | F1@50 | Neg Ratio | Rationale |
|---|---|---|---|
| 1 | 0.42 | 0.3 | 初期聚焦正例召回 |
| 15 | 0.68 | 0.65 | 强化边界判别能力 |
graph TD
A[Batch Input] --> B{F1 Score < 0.6?}
B -->|Yes| C[Sample 30% negative spans]
B -->|No| D[Sample 65% negative spans]
C & D --> E[NER Loss + Span Contrastive Term]
第四章:Schema自动推导与图谱本体生成流水线
4.1 实体共现统计与语义角色标注(SRL)驱动的关系候选挖掘
关系抽取的起点并非原始句子,而是经过深层语言结构解析的语义骨架。实体共现仅提供粗粒度线索,需融合SRL识别的谓词-论元结构,精准锚定潜在关系路径。
核心流程示意
graph TD
A[原始句子] --> B[SRL解析器<br/>(如AllenNLP SRL)]
B --> C[谓词中心化三元组<br/>(PRED, ARG0, ARG1)]
C --> D[跨句实体共现矩阵]
D --> E[关系候选过滤<br/>(共现频次 ≥3 ∧ ARG0/ARG1为命名实体)]
关键实现片段
from allennlp.predictors import Predictor
predictor = Predictor.from_path("https://storage.googleapis.com/allennlp-public-models/structured-prediction-srl-bert.2020.12.15.tar.gz")
# 输入:含目标谓词的句子片段
result = predictor.predict(sentence="Apple acquired Beats in 2014.")
# 输出含 ARG0='Apple', ARG1='Beats', PRED='acquired'
逻辑说明:
predictor.predict()返回JSON结构,verbs字段含SRL标注结果;ARG0常为主语施事者,ARG1为受事宾语,二者与谓词共同构成高置信度关系候选三元组。阈值min_cooccur=3用于过滤低频噪声共现。
候选关系质量评估维度
| 维度 | 说明 |
|---|---|
| 谓词语义强度 | 如“acquire” > “mention” |
| 论元实体类型 | ARG0/ARG1均为ORG或PER更可靠 |
| 句法距离 | ARG0与ARG1间依存路径长度 ≤5 |
4.2 基于类型约束传播(Type Constraint Propagation)的属性类型自动推断
类型约束传播通过分析赋值、函数调用与继承关系,将已知类型信息沿数据流和控制流逐步扩散,从而在无显式标注处推导出精确的属性类型。
核心传播路径
- 赋值语句:
obj.x = 42→ 约束x: number - 函数返回:
fn()返回string→ 调用点obj.y = fn()→ 推导y: string - 类型交集:多路径汇入时取交集(如
number & finite→number)
示例:约束图构建与求解
class User {
id = Math.random(); // 初始约束: id: number
name = "Alice"; // 初始约束: name: string
init() {
this.id = this.id * 2; // 强化约束: id remains number
this.name = this.getName(); // getName(): string → name stays string
}
getName() { return "Bob"; }
}
逻辑分析:id 的初始值触发 number 约束;乘法运算不改变类型域,故传播后仍为 number。getName() 的返回类型被静态解析为 string,经赋值传播至 name,强化其不可变性。
约束传播状态表
| 变量 | 初始约束 | 传播路径 | 最终类型 |
|---|---|---|---|
| id | number |
id * 2 |
number |
| name | string |
getName(): string |
string |
graph TD
A[init()] --> B[id = id * 2]
A --> C[name = getName()]
B --> D[id: number]
C --> E[name: string]
4.3 图模式归纳(Graph Pattern Mining):从实体-关系三元组中提取可复用Schema片段
图模式归纳旨在从海量 RDF 三元组中识别高频、语义一致的子图结构,形成可复用的 Schema 片段(如 :Person → :hasEmail → :Email → :hasDomain → :Domain)。
核心目标
- 发现具有统计显著性的连接子图(support ≥ min_sup)
- 保证模式可泛化(非孤立路径,含至少2个关系边)
- 输出带语义约束的模板(如
?x :hasAge ?a . FILTER(?a > 18))
典型挖掘流程
from rdflib import Graph
from rdflib.namespace import RDFS
g = Graph().parse("data.ttl", format="turtle")
# 提取所有长度为2的路径模式(主体→谓词1→客体→谓词2→客体2)
patterns = g.query("""
SELECT ?p1 ?p2 (COUNT(*) AS ?cnt)
WHERE { ?s ?p1 ?o1 . ?o1 ?p2 ?o2 }
GROUP BY ?p1 ?p2
HAVING (?cnt > 50)
""")
逻辑分析:该 SPARQL 查询扫描所有两跳路径,按谓词对
(p1, p2)分组计数;HAVING (?cnt > 50)实现最小支持度剪枝。参数min_sup=50防止噪声模式干扰 Schema 抽象。
常见模式类型对比
| 模式类型 | 示例结构 | 可复用性 | 语义明确性 |
|---|---|---|---|
| 星型模式 | :Person → [:name, :age, :city] |
高 | 中 |
| 链式模式 | :Order → :hasItem → :Product |
高 | 高 |
| 循环模式 | :A → :partOf → :B → :hasPart → :A |
低 | 易歧义 |
graph TD
A[原始三元组集] --> B[频繁子图挖掘]
B --> C{模式过滤}
C -->|支持度/闭包性| D[Schema 片段库]
C -->|语义一致性验证| E[OWL 约束增强]
4.4 Schema版本管理与兼容性校验:基于Delta Diff与OWL 2 RL规则的Go验证引擎
核心设计思想
将Schema变更建模为语义差分(Delta Diff),结合OWL 2 RL规则引擎进行前向/后向兼容性推理,避免运行时结构冲突。
验证流程概览
graph TD
A[加载v1/v2 Schema JSON-LD] --> B[提取OWL 2 RL三元组]
B --> C[执行Delta Diff计算]
C --> D[触发RL规则集:subClassOf、equivalentClass、propertyDomain]
D --> E[生成兼容性断言报告]
Go核心验证器片段
func ValidateCompatibility(v1, v2 *Schema) (Report, error) {
diff := ComputeDelta(v1, v2) // 返回added/removed/modified属性集
rules := LoadOWL2RLRules() // 加载预编译的RL规则二进制包
return InferCompat(diff, rules), nil
}
ComputeDelta 输出结构化差异(含语义类型标记如 owl:DatatypeProperty→rdfs:subPropertyOf);InferCompat 调用嵌入式RDFox推理器执行规则链推导。
兼容性判定矩阵
| 变更类型 | 向后兼容 | 向前兼容 | 触发规则 |
|---|---|---|---|
| 新增可选字段 | ✅ | ✅ | optPropSubsumption |
| 删除必填字段 | ❌ | ❌ | reqPropRemovalBlock |
| 类型放宽(int→number) | ✅ | ❌ | domainWideningCheck |
第五章:端到端流水线集成、压测与生产部署总结
流水线全链路串联实践
在真实电商大促项目中,我们将 GitLab CI 与 Argo CD 深度集成,构建了从代码提交 → 单元测试 → 镜像构建(Kaniko)→ Helm Chart 自动化版本化 → 多环境灰度发布(dev/staging/prod)的完整流水线。关键节点通过 Webhook 触发下游动作,例如当 main 分支合并后,自动触发 staging 环境部署,并同步向企业微信机器人推送含 commit hash、镜像 digest 和部署时间戳的结构化消息。
压测策略与可观测性闭环
采用 k6 + Prometheus + Grafana 实现压测即代码(Code-as-Load)。以下为某次核心下单接口压测脚本关键片段:
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 200,
duration: '5m',
thresholds: {
'http_req_duration{scenario:order_submit}': ['p(95)<800'],
}
};
export default function () {
const res = http.post('https://api.prod.example.com/v1/orders', JSON.stringify({
sku_id: 'SKU-2024-7890',
quantity: 1
}), {
headers: { 'Content-Type': 'application/json', 'X-Trace-ID': __ENV.TRACE_ID || 'test-' + Date.now() }
});
check(res, { 'status was 201': (r) => r.status === 201 });
sleep(1);
}
压测期间实时监控 CPU 负载、JVM GC 频率、数据库连接池等待数及 OpenTelemetry 上报的 Span 错误率,发现 DB 连接泄漏问题后,通过修改 HikariCP 的 leak-detection-threshold=60000 并结合 pprof 内存快照定位到未关闭的 ResultSet。
生产环境蓝绿切换实操
使用 Nginx Ingress Controller 的 canary annotation 实现无感流量切换。下表为某次 v2.3.0 版本上线的关键参数配置对比:
| 参数 | v2.2.0(旧版) | v2.3.0(新版) | 切换方式 |
|---|---|---|---|
| 最大并发连接数 | 1024 | 2048 | 动态重载 |
| JWT 解析超时 | 3s | 800ms(启用缓存) | ConfigMap 挂载热更新 |
| 日志采样率 | 100% | 5%(ERROR 全量,INFO 采样) | Envoy Filter 动态下发 |
故障注入验证韧性
在预发布环境执行 Chaos Mesh 故障注入实验:持续 3 分钟模拟 etcd 网络延迟(99% 请求增加 2s RTT),观察订单服务是否自动降级至本地缓存兜底。结果表明,熔断器在第 47 秒触发(错误率突破 50% 阈值),下游 Redis Cluster 在 12 秒内完成主从切换,整体 P99 延迟稳定在 1.4s 内,未引发雪崩。
安全合规落地要点
所有生产镜像均通过 Trivy 扫描并嵌入 SBOM(Software Bill of Materials),CI 流程强制拦截 CVSS ≥ 7.0 的高危漏洞;Kubernetes Pod 启用 Seccomp profile 限制 syscalls,禁用 CAP_NET_RAW;Secrets 通过 HashiCorp Vault Agent 注入,避免硬编码或环境变量泄露。
监控告警分级响应机制
建立三级告警体系:L1(自动修复)如 Node DiskPressure 触发自动清理 /tmp;L2(人工介入)如支付成功率突降 5% 持续 2 分钟,自动创建 Jira 工单并 @SRE on-call;L3(跨团队协同)如第三方短信网关 SLA 不达标,触发与供应商的 SLA escalation 流程,日志中自动附带最近 100 条失败请求 traceID。
回滚决策支持系统
每次部署生成唯一 Deployment ID(如 dep-20240522-142833-a7f9b2),关联 Git SHA、Helm Release Revision、Prometheus 数据快照(含 QPS、错误率、延迟分位图)。当检测到 5 分钟内 HTTP 5xx 错误率 > 0.8%,系统自动比对前一健康版本指标基线,若差异显著(p-value helm rollback order-service 127 –namespace prod。
graph LR
A[Git Push to main] --> B[CI Pipeline Trigger]
B --> C{Unit Test Pass?}
C -->|Yes| D[Build & Scan Image]
C -->|No| E[Fail & Notify]
D --> F[Helm Chart Version Bump]
F --> G[Deploy to Staging]
G --> H[Automated Smoke Test]
H -->|Pass| I[Trigger k6 Load Test]
H -->|Fail| E
I --> J{P95 Latency < 800ms?}
J -->|Yes| K[Promote to Prod via Argo CD Sync]
J -->|No| L[Block & Auto-Analyze Logs] 