saiprakash774/workflow-inbox
GitHub: saiprakash774/workflow-inbox
一款基于 TypeScript 的多区域运维工作流收件箱工具,通过服务端状态机和幂等 REST API 统一处理部署审查、基础设施配置与事件响应三类审批任务。
Stars: 0 | Forks: 0
# Workflow 收件箱
一款内部运维工具:提供统一的收件箱,用于审查并处理三种不同工作流类型的待办任务——部署审查、基础设施配置请求以及事件响应——且每种类型均跨越多个 AWS 风格的区域。
本项目是在严格的 60 分钟时间限制下完成的技术测试。任务要求明确指定使用 TypeScript,并明确指出不要添加 auth、Docker、CI/CD 或 Kubernetes——项目中完全没有包含这些内容,这是有意为之,并非疏漏。
## 快速开始
```
npm install
npm start
```
然后打开 **http://localhost:3000**。
`npm start` 会运行一个 `prestart` 步骤,在启动后端之前编译前端 TypeScript(`frontend/app.ts` → `public/frontend/app.js`,不使用 bundler),后端使用 `tsx` 启动(直接执行 TypeScript,无需单独的编译步骤)。要求 Node 18+;在 Node 24 上进行开发和测试。
其他脚本:
| 命令 | 作用 |
|---|---|
| `npm run dev` | 与 `start` 相同,但后端会在保存时热重载(`tsx watch`) |
| `npm run typecheck` | 使用 `tsc --noEmit` 对后端及共享 contract 进行类型检查 |
| `npm run build:frontend` | 从 `frontend/*.ts` 重新构建 `public/frontend/*.js` |
**已知缺陷:** `npm run dev` 只会在启动时构建一次前端——对 `frontend/app.ts` 进行修改后,需要手动运行 `npm run build:frontend`(或重启 `dev`)才能生效。请参阅下文的“改进设想”。
## 包含内容
- **三种工作流类型**,共有 15 个预置模拟项目,涵盖了每种类型生命周期中的所有状态:
- **部署审查 (Deployment Review)** —— 批准/拒绝将服务部署到 staging/production 环境,然后将其标记为已部署。
- **基础设施配置 (Infra Provisioning)** —— 批准/拒绝云资源请求(RDS、EKS 节点、VPC peering 等),然后在整个配置过程中进行追踪,直至完成或失败(支持重试)。
- **事件响应 (Incident Response)** —— 确认、升级并解决事件;其中一个预置的事件带有一个模拟的 `aiTriageSuggestion` 字段,以此作为对 LLM 辅助分诊的小小致敬。
- 一个带有**服务端状态机**的 REST API —— 针对给定工作流的一组合法操作由服务端计算并交由前端处理;客户端永远不会重新实现“在这里哪些按钮是合法的”逻辑。
- 在唯一的 mutation endpoint(`POST /:id/actions`)上实现了**严格的幂等性**,具备全局错误处理、带有 request ID 的结构化日志,以及基础的内存级指标。
- 一个**无框架、可访问的前端** —— 采用语义化 HTML、原生表单控件、全程支持键盘操作,并已在真实的无头浏览器中验证通过(绝非仅凭肉眼检查)。
## 前提假设
- **无 auth。** “代表行事”是一个纯文本字段,用于代替已验证的身份——根据任务说明,这属于范围之外的内容。
- **内存级数据**,每次重启都会重置。没有持久化层,因为明确指出这只是一个供团队成员继续开发的初始版本。
- **“工作流”是具有一个 status 字段和 N 个有效转换的单一资源**,而不是多节点执行图。真正的工作流编排器(参见“未来系统集成”)运行的是由异构节点组成的图;这里进行了范围缩减以适应时间限制。
- **区域只是标签,而非基础设施。** 每个工作流都会列出其涉及的区域,仅用于过滤——实际上并不会发生跨区域复制或路由。
## 工程选择与权衡
- **全面采用 TypeScript,不使用 bundler。** 后端运行在 `tsx` 上(零配置 TS 执行);前端是直接通过普通的 `tsc` 编译为静态 JS 的 TypeScript(不使用 React/Vite/webpack)。这使得整个工具链仅需 `npm install && npm start`,从而能将时间预算投入到 API/状态机/幂等性逻辑中,而非构建工具上。权衡:与组件框架相比,`frontend/app.ts` 中需要编写更多的手动 DOM 代码——为了缓解这一问题,所有动态渲染都封装在一个小巧的 `el()` 辅助函数背后,该函数只设置 `textContent`,绝不设置 `innerHTML`,因此它也不会成为引发 XSS 的捷径。
- **共享 contract,而非共享逻辑。** `shared/types.ts` 定义了 `Workflow` 联合类型以及 API 请求/响应的数据结构,由前后端共同引入——这是“契约优先”API 设计的一个具体缩影。状态机的*规则*本身**并不**共享——只有后端(`server/services/transitions.ts`)拥有它们,前端通过请求 `GET /api/workflows/meta` 来获取当前合法的操作。在客户端重复业务规则正是持久化工作流 runtime 旨在防范的那种“一致性漂移风险”;这个应用在更小的规模上也做出了同样的选择。
- **内存级存储,而非 Postgres。** 避免了在 Docker/DB 设置上绕道;代价是重启后会丢失数据(参见“前提假设”)。
- **内存级幂等存储,而非 Redis。** 在单进程内是正确的;但在多个实例间或重启后无法保持。这一点在 `server/middleware/idempotency.ts` 中已明确指出。
- **手写验证,而非 zod/joi。** 在仅有三种固定请求结构的情况下是合理的;如果 endpoint 数量超过少许,就不可避免地会面临与 `shared/types.ts` 发生实际漂移的风险。
## API 设计与幂等性
| 方法与路径 | 用途 |
|---|---|
| `GET /api/health` | 存活状态、运行时间、内存级指标快照 |
| `GET /api/workflows?type=&status=®ion=&q=` | 列出/过滤工作流 |
| `GET /api/workflows/:id` | 单个工作流,包含完整历史记录 |
| `GET /api/workflows/meta` | 类型、区域,以及完整的 `status → 允许的操作` 表 |
| `POST /api/workflows/:id/actions` | 执行操作——请求体为 `{ action, actor, comment? }`,**需要 `Idempotency-Key` 请求头** |
错误返回格式始终为 `{ error: { code, message, requestId, details? } }`,并带有经过精心挑选的 status code(400 表示验证/格式错误的请求体,404 表示未找到,409 表示冲突,500 表示意外错误——绝不泄露内部细节)。
每个响应都带有 `X-Request-Id`(自动生成,或者如果上游传入了 `X-Request-Id` 请求头则对其进行转发,正如真正的 gateway 会做的那样),这样用户报告的错误就可以直接通过检索定位到其服务端日志行。
**幂等性**,仿照 Stripe 风格的 `Idempotency-Key` 请求头约定:
- `POST /:id/actions` 不带 key → **400**。
- 新的 key → 操作运行,并且其响应该 key 进行缓存。
- 相同的 key,相同的 payload,在第一次调用完成*后*重播 → **原样返回原始响应**(带有 `Idempotent-Replay: true` 响应头)——处理程序不会再次运行,因此重试“批准”操作绝不会导致重复应用。
- 相同的 key,相同的 payload,但第一次调用*仍在进行中* → **409**(防止双击或重复请求竞争);30 秒的过期检查可防止真正卡住的请求永久阻塞该 key。
- 相同的 key,**不同**的 payload → **409**(在不同的逻辑操作中重用 key 是客户端的 bug,不应被静默允许)。
- **5xx 响应永远不会被缓存**——发生临时故障时,可以使用相同的 key 安全地进行重试。
这是在 HTTP 层面对持久化执行引擎(见下文)免费提供保证的一种手工近似实现。
## 可访问性
全程使用原生的 `
标签:MITM代理, TypeScript, 任务看板, 内部工具, 安全插件, 工作流系统, 自动化攻击, 运维管理