本地开发依赖就绪合同:从端口已开到能力可用
本地服务启动成功,不代表数据库迁移、鉴权范围和消息主题已经可用。本文用版本化能力探针、稳定窗口与失败分类,把“等一会再刷新”改造成可验证且可回退的开发启动门。
本文要点
- 主题聚焦:TypeScript、DevOps、开源
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
开发脚本看到 5432 端口可连就启动 Web 应用,首页却因迁移未完成而报错;Mock 身份服务返回 200,但缺少项目要求的 scope;对象存储进程已经监听,初始化 bucket 仍不存在。这些并非“电脑慢”,而是团队没有说明依赖何时才算可用。CI/CD 流水线设计解决提交后的自动化阶段,本篇只约束开发者从一份仓库快照启动本地工作区之前的能力条件。
Docker Compose 的 depends_on 可以按 service_healthy 等待健康检查,也能用 service_completed_successfully 等待一次性任务;但健康命令仍由项目定义。只写 curl /health,无法证明当前数据库 schema、测试租户或鉴权策略与代码匹配。依赖镜像和 lockfile 的升级风险继续交给依赖升级与锁文件兼容门,就绪合同判断的是本次已解析环境能否安全开始开发。
冻结 readinessContract.v1
仓库保存 dev.readinessContract.v1,并对规范化内容求摘要。合同列出仓库 revision、lockfile 摘要、支持的平台,以及每个依赖的稳定 dependencyId、前置边、连接方式、期望协议、版本范围、必需能力、探针超时和失败策略。运行时产生独立 readinessRunId,记录实际镜像 digest、端点、探针版本及结果;不能把本机可变路径写回合同。
schema: dev.readinessContract.v1
dependencyId: postgres.primary
requires: [network.compose]
probe: db-schema.v2
accepts: { engine: ">=16 <18", migration: "2026071903" }
stabilityWindow: 10s
freshSamples: 3
deadline: 90s
onUnavailable: block
状态只允许 BLOCKED -> PROBING -> STABILIZING -> READY。STABILIZING 必须取得三次间隔采样,首末样本覆盖完整 stabilityWindow;每次都重新连接并带采样时间、endpoint identity 与当前 readinessRunId。任一次失败或超过 fresh-age 就清零并回到 PROBING。总 deadline 覆盖探测和完整稳定窗口,旧 run 的迟到样本不能贡献计数。超时或能力不符进入 FAILED;曾经 READY 后失败进入 STALE。可选邮件预览器可声明 degrade,数据库不能临时改成可选。不认识必填探针返回 UNSUPPORTED_PROBE,不能退化成 TCP 检查。
探针验证能力而不改坏现场
探针默认只读:PostgreSQL 读取 server version、迁移表和只读查询,HTTP 校验 machine-readable 版本、scope 与关键字段。确实必须证明写能力时,合同另列 effectProbe.v1:使用只授予本次 run 的受限身份,以及无 trigger、sequence、CDC 或业务消费者的专用 probe 表/队列/对象前缀,绝不写共享业务资源。每个 effect 保存 effectId、资源名与 PLANNED -> ACKED -> OBSERVED -> CLEANED 状态;调用成功但 ack 丢失进入 UNKNOWN_EFFECT,必须按 effectId 对账并清理,在确认前阻断 READY。清理也要显式 ack,失败进入 CLEANUP_FAILED,不能因 TTL 存在就假报完成。秘密只通过凭证引用注入。
调度器按依赖图并发执行无关探针,前置失败时下游标记 BLOCKED_BY,而不是制造重复报错。重试有上限;结果绑定合同摘要和 run ID,旧进程迟到的 READY 不得覆盖新 run。应用启动后保留轻量状态通道:依赖再次失效时,UI 明确显示能力不可用,不能靠热重载掩盖陈旧连接。SLO 与错误预算适合生产目标,本地门禁使用固定夹具和开发等待预算。
用反例证明门不是装饰
测试环境主动注入:端口监听但说错协议;数据库迁移落后一版;HTTP 200 却缺 scope;一次成功后稳定窗口内失败;旧 run 延迟样本;写 effect 已接受但 ack 丢失;清理已发出但失败;依赖 READY 后重启。期望分别得到 PROTOCOL_MISMATCH、SCHEMA_BEHIND、CAPABILITY_MISSING、重新 STABILIZING、STALE_RUN、UNKNOWN_EFFECT、CLEANUP_FAILED 和 STALE,禁止统一显示“服务未启动”。
阻断门要求:支持矩阵中的干净启动 30/30 成功;八类故障检出率 100%;假 READY、旧样本计入和共享资源写入均为 0;effect/cleanup 对账覆盖率 100%;必需依赖 P95 判定不超过 90 秒;停止命令后 20 秒内隔离产物清零。还要从空缓存机器运行,避免现有卷虚假通过。
发布先以 observe 模式记录旧脚本与新合同的差异,再提示不阻断,最后只对稳定依赖启用 block。每阶段比较启动成功率、手工重试次数、P95 等待时间和降级使用率。若误阻断超过 1%、等待时间恶化 25% 或 reader 崩溃,就切回上一合同摘要并恢复旧启动入口;已生成的 run report 保留用于定位,不改写成成功。离线工作则使用显式、带到期时间的 profile,界面持续标注缺失能力,不能靠环境变量永久绕过。基础设施即代码可保证共享资源声明,本合同补齐开发者真正可以开始编码的最后一公里。
Related