Skip to content

Commit 7f65fa7

Browse files
committed
fix: repair map action CI assertions
1 parent 3277291 commit 7f65fa7

3 files changed

Lines changed: 236 additions & 2 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ Gin 内置了一个“允许来源列表 + 可选 null origin”逻辑:
173173
## 8. Agent/协作行为准则(CRITICAL)
174174

175175
- **Git 写操作**:除非用户明确要求,否则严禁自动执行 `git add` / `git commit` / `git push` 等。
176+
- **推送后流水线监控(强制)**:一旦按用户明确要求执行 `git push`,必须立即监控该 push 触发的所有 GitHub Actions。使用 `gh run list`/`gh run view --log-failed` 定位失败 job,修复并重新推送,直到该 commit 或修复 commit 对应的相关流水线全部成功。不得在未确认流水线状态的情况下报告“完成”。
176177
- **Plan Mode 限制**:严禁使用 `ExitPlanMode` 工具;按用户指令直接执行。
177178
- **重置/回滚限制(重要)**:任何 `reset` / `checkout` / `restore` / “还原文件”等操作,只允许回滚 **我本次会话里明确修改过的文件**;涉及到非我修改的文件,除非用户明确点名要求,否则一律禁止重置。
178179

backend/scripts/backend_test.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ if [ -n "$JWT_TOKEN" ]; then
291291
MAP_ACTIONS_STATUS=$(echo "$MAP_ACTIONS_RESPONSE" | tail -n 1)
292292

293293
if [ "$MAP_ACTIONS_STATUS" -eq 200 ]; then
294-
if echo "$MAP_ACTIONS_BODY" | jq -e '.results | length == 4 and .content.markers | length == 2' > /dev/null; then
294+
if echo "$MAP_ACTIONS_BODY" | jq -e '(.results | length == 4) and (.content.markers | length == 2)' > /dev/null; then
295295
print_pass "Test 10: Map actions applied successfully."
296296
else
297297
print_fail "Test 10: Map actions response content incorrect. Body: $MAP_ACTIONS_BODY"
@@ -309,7 +309,7 @@ if [ -n "$JWT_TOKEN" ]; then
309309
MAP_VERIFY_STATUS=$(echo "$MAP_VERIFY_RESPONSE" | tail -n 1)
310310

311311
if [ "$MAP_VERIFY_STATUS" -eq 200 ]; then
312-
if echo "$MAP_VERIFY_BODY" | jq -e '.plan.content.markers | length == 2 and .plan.content.connections | length == 1 and .plan.content.dateNotes["2026-10-01"].notes == "Morning route."' > /dev/null; then
312+
if echo "$MAP_VERIFY_BODY" | jq -e '(.plan.content.markers | length == 2) and (.plan.content.connections | length == 1) and (.plan.content.dateNotes["2026-10-01"].notes == "Morning route.")' > /dev/null; then
313313
print_pass "Test 10.1: Saved map content verified."
314314
else
315315
print_fail "Test 10.1: Saved map content mismatch. Body: $MAP_VERIFY_BODY"
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# 2026-08-03 Map Actions CI Failure Postmortem
2+
3+
## Summary
4+
5+
This postmortem covers the failed delivery of the server-side Roadbook map action API and the accompanying `roadbook-map-editor` skill. The change introduced `POST /api/v1/plans/:id/map/actions` for Go and Cloudflare Worker backends, tests for server-side map edits, and a TRAE CLI skill that explains how an agent should create and edit Roadbook plans through HTTP APIs.
6+
7+
The initial push was premature. The reviewer loop had passed for the code shape available at that time, but I did not monitor the GitHub Actions runs after pushing. Two CI workflows failed:
8+
9+
- `CI Backend`
10+
- `CI Cloudflare Worker`
11+
12+
Both failed for the same reason: the new integration test assertions in `backend/scripts/backend_test.sh` used an incorrect `jq` expression. The service response was valid, but the test expression piped into an array and then attempted to index `.content` or `.plan` from that array.
13+
14+
The immediate fix was to add parentheses around each `jq` predicate:
15+
16+
```bash
17+
(.results | length == 4) and (.content.markers | length == 2)
18+
```
19+
20+
and:
21+
22+
```bash
23+
(.plan.content.markers | length == 2) and (.plan.content.connections | length == 1) and (.plan.content.dateNotes["2026-10-01"].notes == "Morning route.")
24+
```
25+
26+
That fixed the direct CI failure. During the same cleanup, reviewer feedback also drove several correctness fixes:
27+
28+
- Go map actions now run under a repository write lock through `Repository.ApplyMapActions`, rather than splitting `FindByID` and `Save` in the HTTP handler.
29+
- `connect_markers` now stores the matched marker's actual `id` type in `startId` and `endId`, avoiding frontend strict-equality reload failures when an action supplies `"101"` for marker id `101`.
30+
- Cloudflare Worker coordinate validation now rejects `null`, `undefined`, empty strings, booleans, arrays, and objects instead of letting JavaScript `Number(...)` coerce invalid values to `0` or another accidental number.
31+
- The skill was simplified back to a pure HTTP API guide. Earlier local helper scripts were removed because they were unnecessary for a skill whose job is to tell an agent which HTTP endpoints exist and how to call them.
32+
- `README.md`, `docs/api.md`, `cloudflare/README.md`, and `AGENTS.md` were updated to document the new endpoint and the operational rule that pushes must be monitored until CI succeeds.
33+
34+
## Impact
35+
36+
The pushed commit `3277291 feat: add server-side roadbook map actions` left the `master` branch with failing CI until a fix commit could be prepared. The runtime service was not deployed from this repository during the incident, but the repository state was still bad: anyone looking at the branch saw red workflows. That undermines confidence in the change and creates avoidable noise for anyone else working in the repository.
37+
38+
The failed workflows were not caused by production traffic, flaky infrastructure, or an upstream API. They were caused by a test bug introduced in the same change. The bug was preventable with better local validation or, at minimum, by watching CI immediately after push.
39+
40+
## Timeline
41+
42+
1. A request came in to support local-agent editing of Roadbook maps without requiring browser UI interaction.
43+
2. I inspected the existing backend and confirmed that plans were stored as whole JSON `content` documents.
44+
3. I added a map action API in Go and Cloudflare Worker.
45+
4. I added tests and docs.
46+
5. I created a skill folder, initially with unnecessary `.mjs` helper scripts.
47+
6. The user correctly pushed back that the skill should explain HTTP endpoints directly.
48+
7. I removed the helper scripts and converted the skill into a pure HTTP API guide.
49+
8. Reviewer found multiple issues, including lost-update risk, mismatched request-shape docs, Worker coordinate coercion, and README gaps.
50+
9. I fixed those issues and requested re-review.
51+
10. Reviewer found two more medium issues: connection endpoint id type mismatch and remaining Worker number coercion.
52+
11. I fixed those and requested another review.
53+
12. Reviewer reported unresolved blocker/high/medium = 0.
54+
13. The user asked me to commit and push.
55+
14. I pulled, committed, and pushed.
56+
15. I did not monitor CI after pushing.
57+
16. The user reported that the pipeline failed.
58+
17. I inspected GitHub Actions and found `CI Backend` and `CI Cloudflare Worker` failures.
59+
18. The failed logs showed the same `jq` precedence error in both workflows.
60+
19. I corrected the `jq` expressions locally.
61+
20. I requested reviewer re-check of the fix.
62+
21. Reviewer confirmed no blocker/high/medium findings on the fix.
63+
22. I added a mandatory push-monitoring rule to `AGENTS.md`.
64+
65+
## Root Cause
66+
67+
The root cause was not a complex backend problem. It was a simple CI assertion mistake:
68+
69+
```bash
70+
jq -e '.results | length == 4 and .content.markers | length == 2'
71+
```
72+
73+
This expression does not mean "check `.results` length and `.content.markers` length on the original object" in the way it was intended. The pipeline operator changes what the right side sees. The failed CI log showed:
74+
75+
```text
76+
jq: error (at <stdin>:1): Cannot index array with string "content"
77+
```
78+
79+
That message was precise. The expression had already moved into `.results`, which is an array, and then attempted to read `.content` from that array.
80+
81+
The correct expression groups each predicate:
82+
83+
```bash
84+
jq -e '(.results | length == 4) and (.content.markers | length == 2)'
85+
```
86+
87+
The same issue existed in the follow-up verification assertion:
88+
89+
```bash
90+
jq -e '.plan.content.markers | length == 2 and .plan.content.connections | length == 1 ...'
91+
```
92+
93+
The fix is:
94+
95+
```bash
96+
jq -e '(.plan.content.markers | length == 2) and (.plan.content.connections | length == 1) and (...)'
97+
```
98+
99+
## Contributing Causes
100+
101+
### 1. I did not monitor CI after push
102+
103+
The most important process failure was not watching GitHub Actions after pushing. In this repository, a push is not done when `git push` exits successfully. It is done when the relevant workflows finish successfully.
104+
105+
The new rule in `AGENTS.md` now states that after any user-approved push, the agent must monitor all triggered GitHub Actions with `gh run list` and `gh run view --log-failed`, fix failures, and continue until all relevant workflows succeed.
106+
107+
### 2. Local environment did not match CI
108+
109+
The local machine did not have `jq`, so the exact shell integration script could not be run locally as-is. I noticed that limitation earlier, but I accepted partial local validation rather than making sure the CI-only `jq` expression itself was correct.
110+
111+
When a test script depends on a tool unavailable locally, the safe response is not to assume the script is fine. At minimum, the expression should be tested in an environment that has the tool, or the logic should be simple enough and reviewed carefully.
112+
113+
### 3. I overbuilt the skill before correcting its scope
114+
115+
The skill initially included `.mjs` helper scripts. That was a design mistake. The user's actual need was for an installable skill that tells an agent how to call existing HTTP APIs. The extra scripts added surface area and distracted from the straightforward contract:
116+
117+
- login
118+
- create plan
119+
- search coordinates
120+
- submit map actions
121+
- verify plan content
122+
123+
The skill has since been corrected to pure HTTP guidance.
124+
125+
### 4. I did not fully simulate the shared CI path
126+
127+
`backend/scripts/backend_test.sh` is shared by both Go backend integration tests and Cloudflare Worker integration tests. A mistake in that script breaks both workflows. That makes it a high-leverage file and demands more care than a one-off local test helper.
128+
129+
### 5. The change had cross-runtime semantics
130+
131+
The API needed to behave consistently across Go and Worker. The reviewer found that Worker number coercion did not match Go behavior. This is exactly the kind of cross-runtime mismatch that can hide behind apparently simple code. The fix now makes Worker reject non-scalar coordinates just like Go.
132+
133+
## What Went Well
134+
135+
The review loop did catch important behavioral issues before the final fix:
136+
137+
- The Go handler initially performed `FindByID` and `Save` separately. This could lose updates under concurrent requests. Moving the operation into `Repository.ApplyMapActions` fixed the Go file backend by keeping read-modify-write under one write lock.
138+
- The skill initially documented a raw JSON action array even though both servers require an object with an `actions` array. That was corrected.
139+
- The Worker initially accepted some invalid coordinate values due to JavaScript coercion. That was corrected.
140+
- `connect_markers` initially saved raw action ids, which could produce a frontend reload mismatch. That was corrected.
141+
142+
The final reviewer pass reported:
143+
144+
```text
145+
unresolved blocker/high/medium = 0
146+
```
147+
148+
That is the correct gate before pushing a non-trivial backend/API change.
149+
150+
## What Went Wrong
151+
152+
The failure after push was avoidable. I treated passing local checks and review as sufficient and did not follow through on the delivery obligation. A successful push only means Git accepted objects. It says nothing about whether the branch is healthy.
153+
154+
The exact CI break was also avoidable. The `jq` expression was small and could have been reasoned about more carefully. The error was not subtle after seeing the logs. It was a simple precedence/pipeline mistake.
155+
156+
The skill detour was also avoidable. The user asked for a skill so an agent could edit maps. That does not imply local helper scripts. A skill is often best as concise operational knowledge. In this case, the operational knowledge is the HTTP API contract.
157+
158+
## Corrective Actions Already Taken
159+
160+
### Code fixes
161+
162+
- Added `Repository.ApplyMapActions` to perform Go file-backend map edits under a single write lock.
163+
- Updated `PlanHandler.ApplyMapActionsHandler` to use the repository-level atomic method.
164+
- Updated Worker map action implementation to queue same-plan updates in the same isolate.
165+
- Updated Worker coordinate validation to reject invalid non-scalar input.
166+
- Updated `connect_markers` to persist matched marker ids rather than raw action ids.
167+
- Fixed `backend/scripts/backend_test.sh` `jq` expressions.
168+
169+
### Test fixes
170+
171+
- Added Go unit tests for action application.
172+
- Added tests for failure atomicity at the action batch level.
173+
- Added tests for marker removal and connection cleanup.
174+
- Added tests for preserving marker id type when action ids are supplied as strings.
175+
- Added integration-script coverage for `POST /api/v1/plans/:id/map/actions`.
176+
177+
### Documentation fixes
178+
179+
- Updated `docs/api.md` with the map action endpoint.
180+
- Updated `README.md` formal API list.
181+
- Updated `cloudflare/README.md`.
182+
- Updated `AGENTS.md` API alignment guidance.
183+
- Added `skills/roadbook-map-editor/SKILL.md` as a pure HTTP API guide.
184+
- Added the mandatory push-monitoring rule to `AGENTS.md`.
185+
186+
## Preventive Rules Going Forward
187+
188+
### Push monitoring is mandatory
189+
190+
After any push:
191+
192+
1. Run `gh run list --limit 10`.
193+
2. Identify every workflow triggered by the pushed commit.
194+
3. Wait for completion.
195+
4. If any fail, run `gh run view <run-id> --log-failed`.
196+
5. Fix the root cause.
197+
6. Commit and push the fix.
198+
7. Repeat until relevant workflows are green.
199+
8. Only then report completion.
200+
201+
### Shared scripts deserve focused validation
202+
203+
If a script is shared by multiple workflows, treat it as production code. For `backend/scripts/backend_test.sh`, a broken assertion breaks both backend and Worker CI. Future edits to shared scripts should include:
204+
205+
- `bash -n`
206+
- local execution when dependencies are available
207+
- focused review of tool-specific syntax such as `jq`
208+
- monitoring of every workflow that consumes the script
209+
210+
### Skills should stay minimal
211+
212+
A skill should contain the minimum operational knowledge needed by an agent. For Roadbook map editing, that is the HTTP API contract. Helper scripts are only justified when direct API calls are too fragile or repetitive. Here they were not justified.
213+
214+
### Cross-runtime contracts must be explicit
215+
216+
Whenever Go and Worker implement the same endpoint, the request shape, validation rules, error shape, and response shape must match. JavaScript coercion is especially risky; validation should be explicit and conservative.
217+
218+
## Current State
219+
220+
The current working tree includes:
221+
222+
- `POST /api/v1/plans/:id/map/actions` in Go backend.
223+
- Matching map actions endpoint in Cloudflare Worker.
224+
- `roadbook-map-editor` skill as pure HTTP API guidance.
225+
- Updated backend integration script.
226+
- Updated docs.
227+
- Added mandatory CI monitoring rule.
228+
229+
The reviewer has confirmed no unresolved blocker/high/medium findings after the most recent code and test-script fixes.
230+
231+
## Final Notes
232+
233+
The central lesson is straightforward: a pushed change is not complete until the automation that protects the branch has succeeded. The actual failure was small, but the process gap was not. The fix is now encoded in `AGENTS.md` so future work does not stop at `git push`.

0 commit comments

Comments
 (0)