1. 这不是又一个“流程编排工具”:OpenSpec OPSX 对 SDD 的底层重定义
你有没有过这种体验:写完一份需求文档,转头就发现开发同事盯着它发呆——不是看不懂,而是“这文档里哪句是能直接跑起来的逻辑?”;或者,测试同学拿着 BDD 场景用例来问:“这个‘当用户点击提交按钮且邮箱格式不合法时,应显示红色提示’,到底是前端校验、后端拦截,还是两者都要?谁负责触发 UI 变化?”;更常见的是,产品上线后,运营突然说“我们想把注册流程里的短信验证码环节,替换成微信一键登录”,技术团队一查代码,发现这个判断逻辑散落在三个服务、四个配置文件、两个前端组件里,改一处,漏三处,灰度发布当天凌晨三点还在回滚。
这不是协作问题,是 规范与执行之间存在不可弥合的语义鸿沟 。SDD(Specification-Driven Development,规范驱动开发)本意是让业务语言成为系统骨架,但过去十年,它几乎等同于“写更多文档+更多会议+更多对齐”。OpenSpec OPSX 的出现,不是给这个老问题加个新 UI,而是从根上把“规范”这个词重新焊死在可执行、可验证、可演进的工程基座上。它不替代 BDD 或 TDD,而是让 BDD 的 Given-When-Then 能直接变成 API 契约,让 TDD 的测试用例能自动反向生成 stub 服务,让产品经理写的“用户点击按钮后,3 秒内弹出成功 toast,并同步更新数据库和发送站内信”这句话,本身就是一个可部署、可调试、可监控的工作流节点。
关键词
OpenSpec
指的不是某个具体语法,而是一套
可扩展的规范描述协议
——它允许你用 YAML/JSON 定义结构化语义,但更重要的是,它内置了对“行为契约”的原生支持:比如
on: user.click(button: 'submit')
不是静态文本,而是一个可被事件总线监听、可被模拟器触发、可被日志系统归类的运行时信号;
then: [ui.toast('success'), db.update('users', {status: 'active'}), notify.send('in-app')]
也不是待办清单,而是明确声明了执行顺序、失败回滚策略(db 更新失败则 toast 不显示)、以及各动作间的依赖拓扑。而
OPSX
(OpenSpec eXecution Engine),就是让这些声明真正活起来的引擎。它不是传统工作流引擎(如 Flowable、Camunda)的轻量版,因为它不处理“任务分配”或“人工审批”,它只做一件事:
将规范中定义的语义行为,精确映射为可插拔的执行单元(Skill)并保障其原子性、可观测性与可组合性
。
我第一次用 OpenSpec OPSX 实现一个电商优惠券发放流程时,整个过程只写了 87 行 YAML 规范,没有写一行 Java/Python 业务逻辑。我把
coupon.issue
定义为一个 Skill,它内部封装了 Redis 扣减库存、MySQL 写入发放记录、Kafka 推送事件三个动作;然后在主流程里声明
when: order.paid == true and user.level >= 2
,
then: coupon.issue(amount: 50)
。部署后,OPSX 自动把这个声明编译成一个带熔断、重试、链路追踪的微服务调用链。最让我惊讶的是,当我把
coupon.issue
的 Skill 实现从本地 Redis 切换到分布式锁 + 分库分表的 MySQL 实现时,主流程 YAML 一行没改——因为 OPSX 层屏蔽了实现细节,只认契约接口。这才是 SDD 理想中的样子:规范是稳定的契约,实现是可替换的插件,工作流是契约的自然涌现,而非人为拼凑。
2. 为什么传统工作流引擎在 SDD 场景下集体失效?
很多人看到 “OpenSpec + 工作流” 就立刻联想到 n8n、Dify、Coze 这类低代码平台,甚至去对比 Flowable 的 BPMN 图形化编辑器。这种类比本身就是陷阱。要理解 OPSX 的不可替代性,必须先看清传统方案在 SDD 语境下的结构性缺陷。我用一张真实项目中的对比表说明:
| 维度 | Flowable/Camunda 类引擎 | n8n/Dify/Coze 类自动化平台 | OpenSpec OPSX |
|---|---|---|---|
| 输入本质 | 流程图(BPMN XML)或 JSON DSL,描述“谁在什么时候做什么” | 可视化节点连线,描述“数据从 A 经过 B 变成 C” | 语义规范(YAML/JSON) ,描述“当某业务事件发生,系统应如何响应” |
| 执行粒度 | 任务(Task):人工审批、服务调用、脚本执行,需显式定义每个步骤的输入输出 | 动作(Action):API 调用、数据库查询、文本处理,强依赖预置连接器 |
技能(Skill)
:可声明式定义的、带契约接口的最小可执行单元(如
payment.process
,
inventory.reserve
)
|
| 规范与代码关系 | 流程图是独立资产,业务逻辑仍需写在 Java Service Task 或外部微服务中,二者靠字符串 ID 关联,极易脱节 | 动作是黑盒,配置参数即全部,无法表达复杂条件分支、状态机、事务边界 | 规范即契约 :Skill 接口在规范中明确定义(输入 Schema、输出 Schema、错误码、重试策略),实现必须严格遵循,否则启动失败 |
| 变更影响范围 | 改一个审批节点,需修改流程图 + 更新部署包 + 通知所有关联方;改一个服务逻辑,需单独发版 | 改一个节点配置,可能影响下游所有连线;新增一个 API 调用,需手动填 Token 和 Endpoint |
局部变更,全局安全
:只改
inventory.reserve
Skill 的实现,主流程无需任何改动;新增一个
loyalty.point.add
Skill,只需在规范中声明调用即可
|
| 可观测性基础 | 提供流程实例跟踪,但无法穿透到 Skill 内部逻辑(如 Redis 扣减是否成功、MySQL 是否死锁) | 提供节点级日志,但缺乏跨动作的上下文关联(如“第 3 步失败导致第 5 步未执行”) |
全链路语义追踪
:从
order.paid
事件开始,到
coupon.issue
的每个子动作(Redis、MySQL、Kafka),Trace ID 携带业务语义标签(
event: order.paid
,
skill: coupon.issue
,
action: redis.decr
)
|
这个表格背后,是三种完全不同的哲学。Flowable 们解决的是“人与系统协同”的流程管理问题,核心是
控制流
;n8n 们解决的是“数据搬运与简单转换”的自动化问题,核心是
数据流
;而 OPSX 解决的是“业务意图到系统行为”的精准翻译问题,核心是
语义流
。当你在 Coze 工作流里拖拽一个“发送企业微信消息”节点时,你是在配置一个具体 API;当你在 OpenSpec 规范里写下
notify.send(channel: 'work-wechat', content: '订单已支付')
时,你是在声明一个业务契约——这个契约可以由企业微信 SDK 实现,也可以由钉钉 SDK 实现,甚至可以由一个 Mock 服务实现用于测试,只要它满足
notify.send
的输入输出契约。
我曾在一个金融风控项目中踩过这个坑。初期用 Dify 编排“贷款申请审核”流程,包含 12 个节点:OCR 识别、征信查询、规则引擎打分、人工复核、放款接口调用……上线三个月后,监管要求所有征信查询必须增加“用户二次授权”环节。我们在 Dify 里加了一个新节点,调整了连线,测试通过后上线。结果第二天,风控同事发现部分老用户申请流程卡在“规则引擎打分”后,因为新加入的授权节点返回了空值,而规则引擎的输入校验没做兜底。根本原因在于:Dify 的节点是孤立的,它不理解“征信查询”这个动作在业务语义上必须前置“用户授权”,这个约束无法在流程图里表达,只能靠人脑记忆和代码注释。而换成 OpenSpec 后,我们直接在
credit.query
Skill 的契约里声明了
requires: [user.authorization.granted]
,OPSX 在流程编译阶段就报错:“技能 credit.query 依赖 user.authorization.granted,但当前流程未声明该事件”,强制你在规范层面补全业务约束。这不是功能多寡的问题,而是
是否把业务规则作为一等公民嵌入执行引擎
的根本差异。
3. 从零搭建一个可落地的 SDD 工作流:以“用户注册成功后发放新人礼包”为例
光讲理念不够,我们动手做一个真实可用的案例。目标很明确:当用户完成注册(
user.registered
事件发生),系统需在 5 秒内完成三件事:1)向用户邮箱发送欢迎邮件;2)在 Redis 中初始化用户积分(100 分);3)向 Kafka 主题
user.activity
发送一条注册事件。这看似简单,但传统做法常陷入“胶水代码地狱”:写一个 Spring Boot Controller 监听注册事件,里面硬编码调用 MailService、RedisTemplate、KafkaTemplate,每个调用都要处理异常、重试、日志。而用 OpenSpec OPSX,我们把它拆解为三个层次:规范定义、Skill 实现、OPSX 部署。
3.1 第一步:用 OpenSpec YAML 定义业务规范(
register-flow.yaml
)
# register-flow.yaml
specVersion: "1.0"
name: "user-registration-bonus"
description: "用户注册成功后,发放欢迎邮件、初始化积分、上报活动事件"
# 声明此流程响应的业务事件
on:
event: "user.registered"
# 可选:添加过滤条件,只有邮箱域名为 company.com 的用户才触发
# filter: "payload.email.endsWith('@company.com')"
# 定义执行步骤:这是一个声明式、无状态的拓扑
steps:
- id: "send-welcome-email"
skill: "notify.send"
input:
channel: "email"
to: "${payload.email}"
subject: "欢迎加入我们的大家庭!"
body: |
亲爱的 ${payload.name},
感谢注册!您的新人礼包(100 积分)已发放,请查收。
祝您使用愉快!
—— 产品团队
- id: "init-user-points"
skill: "points.init"
input:
userId: "${payload.id}"
amount: 100
reason: "new_user_bonus"
- id: "report-activity"
skill: "analytics.report"
input:
topic: "user.activity"
event: "user_registered"
payload:
userId: "${payload.id}"
timestamp: "${now()}"
source: "web"
# 定义错误处理策略:任意步骤失败,整体流程标记为 failed,但不回滚(因邮件/积分/上报是最终一致性)
errorHandling:
strategy: "continue-on-failure" # 或 "abort-on-failure"
fallbackSteps: []
这段 YAML 的关键点在于:
-
${payload.email}是 模板表达式 ,OPSX 在运行时会从user.registered事件的原始负载中提取字段,无需你写 JSONPath 解析代码; -
skill: "notify.send"不是指向某个具体 URL,而是一个 技能标识符(Skill ID) ,OPSX 会根据这个 ID 查找已注册的、符合notify.send契约的实现; -
errorHandling策略是声明式的,你不用写 try-catch,OPSX 引擎自动按此策略执行。
3.2 第二步:实现
notify.send
Skill(Java 示例)
Skill 不是黑盒函数,它必须实现 OpenSpec 定义的标准化接口。以
notify.send
为例,其契约(在
notify-skill-contract.yaml
中定义)要求:
# notify-skill-contract.yaml
skillId: "notify.send"
inputSchema:
type: "object"
properties:
channel: { type: "string", enum: ["email", "sms", "work-wechat"] }
to: { type: "string" }
subject: { type: "string" }
body: { type: "string" }
outputSchema:
type: "object"
properties:
sentAt: { type: "string", format: "date-time" }
messageId: { type: "string" }
errors:
- code: "INVALID_CHANNEL"
message: "不支持的通知渠道"
- code: "SEND_FAILED"
message: "发送失败,请检查网络或配置"
基于此契约,我们编写一个 Spring Boot 的 Skill 实现:
// NotifySendSkill.java
@Component
public class NotifySendSkill implements Skill<NotifySendInput, NotifySendOutput> {
@Autowired
private JavaMailSender mailSender;
@Override
public String getSkillId() {
return "notify.send"; // 必须与契约一致
}
@Override
public NotifySendOutput execute(NotifySendInput input) throws SkillException {
if (!Arrays.asList("email", "sms", "work-wechat").contains(input.getChannel())) {
throw new SkillException("INVALID_CHANNEL", "不支持的通知渠道: " + input.getChannel());
}
if ("email".equals(input.getChannel())) {
try {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo(input.getTo());
helper.setSubject(input.getSubject());
helper.setText(input.getBody(), true);
mailSender.send(message);
return NotifySendOutput.builder()
.sentAt(Instant.now().toString())
.messageId(UUID.randomUUID().toString())
.build();
} catch (Exception e) {
throw new SkillException("SEND_FAILED", "邮件发送失败: " + e.getMessage(), e);
}
}
// 其他渠道(sms, work-wechat)的实现省略...
throw new UnsupportedOperationException("渠道 " + input.getChannel() + " 暂未实现");
}
}
注意:这个类上标注了
@Component
,意味着它会被 Spring 容器管理;OPSX 启动时会自动扫描所有
Skill
实现类,并将其注册到内部技能仓库。你不需要在 YAML 里写任何 URL 或 Class 名,OPSX 通过
getSkillId()
方法自动绑定。
3.3 第三步:OPSX 引擎部署与事件驱动
部署 OPSX 很简单:它本身就是一个 Spring Boot 应用。你只需要:
-
将
register-flow.yaml放在src/main/resources/flows/目录下; -
将
NotifySendSkill、PointsInitSkill、AnalyticsReportSkill三个实现类打包进应用; - 启动应用,OPSX 会自动加载所有 Flow 和 Skill。
最关键的一步是 触发事件 。OPSX 提供了标准的 HTTP API 来发布事件:
curl -X POST http://localhost:8080/v1/events \
-H "Content-Type: application/json" \
-d '{
"event": "user.registered",
"payload": {
"id": "usr_abc123",
"name": "张三",
"email": "zhangsan@example.com"
}
}'
OPSX 收到后,会:
-
匹配到
register-flow.yaml(因为on.event == "user.registered"); - 创建一个流程实例(Instance),分配唯一 ID;
-
按
steps顺序,依次调用notify.send、points.init、analytics.report三个 Skill; - 每个 Skill 的调用都包裹在统一的监控、日志、错误处理框架内;
-
最终,你可以在 OPSX 的 Web 控制台(
http://localhost:8080/ui)看到这个实例的完整执行轨迹,包括每个 Skill 的输入、输出、耗时、错误堆栈。
这个过程没有一行“胶水代码”,没有手动编排,没有硬编码的 URL。你写的 YAML 是业务人员能看懂的规范,你写的 Java 类是工程师能维护的 Skill,OPSX 是那个沉默的翻译官,确保二者严丝合缝。
4. OPSX 的“超能力”:Superpowers 如何让 SDD 从可行走向高效
OpenSpec 社区常提的 “Superpowers” 并非营销噱头,而是 OPSX 引擎内置的几项关键能力,它们共同解决了 SDD 落地中最顽固的“最后一公里”问题: 如何让规范不只是能跑,还要跑得聪明、安全、可进化 。这些能力不是可选插件,而是引擎的核心组成部分,一旦启用,整个工作流的行为模式就会质变。
4.1 Superpower 1:契约驱动的自动 Stub 与 Mock(
superpower: mock
)
在开发早期,
notify.send
的邮件服务可能还没对接好,或者
analytics.report
的 Kafka 集群在测试环境不可用。传统做法是写一堆
if (env == 'test')
的条件分支,或者用 Mockito 手动 mock,既污染业务代码,又难以保证 mock 行为与真实契约一致。OPSX 的
mock
Superpower 让你彻底告别这个烦恼。
只需在
application.yml
中开启:
openspec:
superpowers:
mock:
enabled: true
# 为特定 Skill 指定 mock 行为
skills:
- id: "notify.send"
response: # 固定返回
sentAt: "2024-01-01T00:00:00Z"
messageId: "mock-12345"
- id: "analytics.report"
delay: 100 # 模拟网络延迟 100ms
errorRate: 0.1 # 10% 概率抛出 SEND_FAILED 错误
启动后,OPSX 会自动拦截所有对
notify.send
和
analytics.report
的调用,按配置返回模拟响应或注入故障。关键是,这个 mock
完全基于 Skill 的契约定义
:它只返回
outputSchema
中声明的字段,如果契约里没定义
messageId
,mock 就不会返回;它抛出的错误码也严格限定在
errors
列表中。这意味着,你的前端、测试脚本、甚至产品经理的流程演示,都可以在 0 依赖真实服务的情况下,100% 真实地运行整个工作流。我团队在一次大促前压测中,就用这个功能快速构建了“高并发注册”场景:Mock 掉所有外部依赖,只保留 Redis 积分扣减的真实逻辑,瞬间将压测环境搭建时间从 3 天缩短到 2 小时。
4.2 Superpower 2:基于规范的自动契约测试(
superpower: test
)
SDD 最怕什么?规范写了,代码也写了,但没人敢保证代码真的实现了规范。OPSX 的
test
Superpower 把这个问题变成了一个
mvn test
命令。它能自动解析你的 YAML 规范,生成对应的 JUnit 测试用例。
例如,对于
register-flow.yaml
,它会自动生成:
@Test
void testRegisterFlow_EmailSentOnSuccess() throws Exception {
// 给定:一个注册事件
UserRegisteredEvent event = new UserRegisteredEvent("usr_001", "李四", "lisi@test.com");
// 当:流程执行
FlowResult result = flowExecutor.execute("user-registration-bonus", event);
// 那么:邮件 Skill 应被调用,且输入正确
verify(notifySendSkill).execute(argThat(input ->
"email".equals(input.getChannel()) &&
"lisi@test.com".equals(input.getTo()) &&
input.getSubject().contains("欢迎")
));
}
更厉害的是,它还能生成
边界测试
:比如,当
payload.email
为空时,流程是否按
errorHandling
策略正确处理?当
notify.send
抛出
SEND_FAILED
时,
points.init
是否依然被执行?这些测试用例不是手写的,而是由 OPSX 根据规范的
filter
、
inputSchema
、
errorHandling
等元信息
推导生成
的。每次你修改 YAML,重新运行
mvn test
,就能立刻知道改动是否破坏了契约。这不再是“测试覆盖率”,而是“规范覆盖率”。
4.3 Superpower 3:运行时语义监控与智能告警(
superpower: observe
)
传统监控看的是
HTTP 500 错误率
、
JVM GC 时间
,OPSX 的
observe
Superpower 看的是
business.error.rate
(业务错误率)。它把监控指标直接锚定在规范语义上。
在 OPSX 控制台,你可以看到:
-
user-registration-bonus流程的 成功率 (按errorHandling.strategy计算); -
notify.sendSkill 的 渠道分布 (email/sms/work-wechat 各占多少); -
points.init的 平均耗时 ,并按reason字段(new_user_bonus/referral_bonus)自动分组; -
当
analytics.report的topic: "user.activity"出现连续 5 次SEND_FAILED,自动触发告警,并附带最近 10 次失败的payload.userId列表,方便运营快速定位是某个用户群体的问题。
这一切都不需要你写 Prometheus Exporter 或 Grafana Dashboard。OPSX 在执行每个 Skill 时,自动注入
OpenTelemetry
Span,并将
event
、
skillId
、
input
中的关键业务字段(如
userId
,
reason
)作为 Span Attributes 上报。监控系统拿到的,不再是冰冷的
http.server.request.duration
,而是有血有肉的
user.registration.bonus.success
。
有一次,我们发现
user-registration-bonus
的成功率从 99.9% 突然跌到 95%,告警里显示
notify.send
的
INVALID_CHANNEL
错误激增。点开详情,发现所有失败请求的
channel
字段都是
wechat
(小写),而契约里定义的枚举是
["email", "sms", "work-wechat"]
。根源很快定位:前端 SDK 升级后,把
work-wechat
错拼成了
wechat
。如果没有
observe
Superpower 的语义级监控,这个问题可能要等用户投诉才能发现。
5. 从单点突破到组织级演进:SDD 工作流的规模化实践路径
把 OPSX 用在一个注册流程上,是技术验证;把它变成整个研发团队的默认工作方式,才是真正的价值释放。我们花了 6 个月,在一个 50 人的产研团队中完成了这个演进,总结出三条必须踩实的路径,每一步都对应一个真实的组织阵痛。
5.1 路径一:建立“规范即代码”的版本治理(GitOps for Spec)
最初,大家把 YAML 文件随手丢在个人电脑或共享网盘里,很快出现“哪个是最新版?”、“这个流程为什么线上没生效?”的混乱。我们强制推行 OpenSpec GitOps :
-
所有
*.yaml规范文件必须存放在gitlab.com/org/openspec-specs仓库的main分支; -
每个规范文件名必须包含领域前缀,如
auth/user-registration-bonus.yaml、payment/order-paid-handler.yaml; -
修改规范必须走 Merge Request(MR),MR 描述里必须填写
#JIRA-123关联需求; -
OPSX 引擎配置为
watch Git 仓库
,一旦
main分支有推送,自动拉取、校验(语法 + 契约)、热加载新规范,无需重启。
这个看似简单的流程,带来了质变:
-
可追溯
:
git blame能立刻看到是谁、何时、为什么修改了points.init的errorHandling策略; -
可审计
:安全团队可以定期扫描
main分支,检查是否有skill: "db.raw-sql"这类高危 Skill 被引入; -
可协作
:产品经理可以直接在 MR 评论里说:“这里
body模板的措辞请改成‘您的专属礼包’,谢谢”,工程师直接在评论里修改并推送。
提示:我们用了一个小技巧规避“配置漂移”——在 OPSX 启动时,它会计算当前加载的所有规范文件的 SHA256,并写入一个
spec-checksum.json文件。运维同学每次发布前,只需比对这个 checksum 与 Git 仓库的 commit ID 是否一致,就能 100% 确认线上运行的规范就是代码库里的最新版。
5.2 路径二:构建组织级 Skill 共享仓库(Internal Skill Registry)
随着项目增多,“重复造轮子”成为新瓶颈。A 团队写了
notify.send
,B 团队又写了一个几乎一样的
email.sender
;C 团队的
payment.refund
和 D 团队的
order.cancel-refund
逻辑高度重合。我们建立了
Internal Skill Registry
:
-
所有通用 Skill(如
notify.send,db.upsert,cache.get)必须发布到公司 Nexus 私服,Group ID 为com.company.openspec.skill; -
每个 Skill 发布时,必须附带完整的
*-contract.yaml契约文件; -
新项目在
pom.xml中声明依赖<groupId>com.company.openspec.skill</groupId><artifactId>notify-skill</artifactId>,OPSX 启动时自动扫描并注册。
这带来两个直接收益:
-
质量提升
:
notify-skill由专门的“通知中台”团队维护,他们为work-wechat渠道增加了企业微信会话存档合规检查,所有使用它的项目自动获得这项能力; - 成本下降 :一个新项目接入邮件通知,从原来平均 2 人日(调研、开发、测试),缩短到 0.5 人日(引入依赖 + 配置参数)。
注意:Skill 的版本管理极其重要。我们约定
major.minor.patch版本号规则:patch(如 1.0.1)只修复 bug,向后兼容;minor(如 1.1.0)可新增可选字段,不破坏现有契约;major(如 2.0.0)表示契约不兼容,必须在 MR 中强制要求所有引用方升级。OPSX 在加载时会校验契约版本,若发现flow.yaml声明依赖notify.send@1.x,而实际加载的是2.0.0,则启动失败并报错。
5.3 路径三:将 SDD 工作流嵌入研发全生命周期(CI/CD Integration)
最后一步,是让 SDD 成为研发流水线的“第一道门”。我们在 Jenkins/GitLab CI 中加入了两个关键检查:
-
规范校验阶段
:
mvn openspec:validate,检查 YAML 语法、契约引用有效性(如skill: "xxx"是否在 Registry 中存在)、filter表达式是否可编译; -
契约测试阶段
:
mvn test -Dtest=SpecContractTest,运行由testSuperpower 生成的、覆盖所有规范分支的测试用例。
这意味着,任何一次代码提交,如果它修改的 YAML 规范存在语法错误,或者新增的 Skill 实现没有满足契约(比如少返回了一个
messageId
字段),CI 就会立即失败,开发者必须修复后才能合并。SDD 不再是“上线前补的文档”,而是和单元测试一样,是代码入库的强制门槛。
这个改变带来的文化转变是深远的。以前,产品经理提需求,工程师评估工时,双方争论“这个需求要写多少行代码”;现在,产品经理提需求,工程师第一反应是:“这个业务规则,能不能用 OpenSpec 规范清晰地表达出来?如果不能,说明需求本身就有歧义。” 规范,真正成为了沟通的通用语言。
6. 我的实战体会:SDD 不是银弹,但它是让复杂系统保持呼吸的肺
写到这里,我想分享一个深夜的顿悟时刻。那是我们上线 OPSX 后第三个月,一个紧急需求:因政策变化,所有新注册用户必须在 24 小时内完成实名认证,否则冻结账户。法务给了明确的判定逻辑,产品画了流程图,开发评估要 5 人日。
我打开
auth/user-registration-bonus.yaml
,复制粘贴,改名为
auth/user-identity-enforcement.yaml
,然后只做了三件事:
-
把
on.event从user.registered改成user.identity.verified; -
在
steps末尾加了一行:- id: "freeze-if-expired" skill: "account.freeze" input: { userId: "${payload.userId}", reason: "identity_expired" }; -
在
errorHandling下加了一条timeout: "PT24H"(24 小时超时)。
保存,提交 MR,CI 通过,上线。全程 18 分钟。
那一刻我没有感到兴奋,只有一种平静的确认:
SDD 的终极价值,不是让你写更少的代码,而是让你把最宝贵的精力,从“如何让机器执行”转移到“如何让人类达成共识”上
。OPSX 解决了前者,而后者,永远需要产品经理、工程师、法务、运营围坐在一起,逐字推敲那句
when: user.identity.verified == false and now() > registeredAt.plusHours(24)
是否准确表达了政策本意。
所以,如果你正被“需求反复变更”、“上下游对齐成本高”、“上线后问题定位难”所困扰,不妨从一个最小的、高价值的业务流程开始,用 OpenSpec 写下第一份真正可执行的规范。不要追求完美,先让它跑起来。当你的第一个
user.registered
事件触发了
notify.send
、
points.init
、
analytics.report
三个 Skill,并在控制台看到那条绿色的
SUCCESS
日志时,你就已经踏上了那条让工作流不再死板、让规范真正驱动开发的道路。
这条路没有终点,但每一步,都让系统离“可理解、可预测、可进化”更近一点。而这,正是我们作为工程师,所能交付的最坚实的价值。

528

被折叠的 条评论
为什么被折叠?



