From cf9e79cd8412409be22a09a406b5dae4d322bc05 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Fri, 18 Sep 2026 06:58:28 +0200 Subject: [PATCH 1/4] test: cover server bootstrap and extension commands --- .agents/pm/history/pm-web-ulgy.jsonl | 3 + .agents/pm/tasks/pm-web-ulgy.toon | 41 +++++-- package.json | 4 +- src/server.ts | 13 ++- test/extension.test.ts | 2 + test/smoke.test.ts | 161 ++++++++++++++++++++++++++- 6 files changed, 212 insertions(+), 12 deletions(-) diff --git a/.agents/pm/history/pm-web-ulgy.jsonl b/.agents/pm/history/pm-web-ulgy.jsonl index 93ade75b..9aafa4c7 100644 --- a/.agents/pm/history/pm-web-ulgy.jsonl +++ b/.agents/pm/history/pm-web-ulgy.jsonl @@ -12,3 +12,6 @@ {"hash_algorithm":"sha256","ts":"2026-09-18T03:39:21.296Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"gpt-5.6-sol","agent_model_source":"environment","agent_instance":"3f43cca22c8538dbf0bf9483","agent_provenance":{"model":{"value":"gpt-5.6-sol","source":"environment"},"effort":null,"role":{"value":"implementer","source":"argv"},"topic":null},"op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-18T03:39:21.296Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-09-18T03:39:21.265Z"},{"op":"add","path":"/metadata/completed_at","value":"2026-09-18T03:39:21.265Z"},{"op":"add","path":"/metadata/resolution","value":"Added real HTTP/PostgreSQL/pm-workspace collaboration tests for cross-client SSE delivery, concurrent no-loss writes with valid history, and broad routes/pm.ts lifecycle behavior; ratcheted coverage floors without weakening the branch gate."},{"op":"add","path":"/metadata/expected_result","value":"The collaboration guarantees and pm route command surface are behaviorally covered, npm run release:check passes, and measured coverage floors only move upward."},{"op":"add","path":"/metadata/actual_result","value":"npm run release:check exits 0 with 398/398 tests passing and aggregate src coverage L/B/F 76.82/80.32/73.00; floors are now 76/80/73. Remaining uncovered files and exact lines are recorded in the preceding item comment; public/src remains intentionally unmeasured at 14,884 TypeScript lines."},{"op":"add","path":"/metadata/close_reason","value":"Behavioral coverage tranche delivered and all local release gates pass; remaining 100% work is explicitly inventoried for the next tranche."}],"before_hash":"71ef9c1b5c62341f81e420c85871de0baf2106c1430a18899c1dc4fbcaad00bd","after_hash":"e2fc273e9bf0b64081ffe2145aac1e10a547a1a4a0a772d60d2a314e50c4eb66","item_hash_version":3,"event_class":"substantive","record_hash_version":1,"record_hash":"74dfb60e1b2f8b6f445da8e561644b9607e87460c3a3178b9379e93404d33853"} {"hash_algorithm":"sha256","ts":"2026-09-18T03:43:37.356Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"gpt-5.6-sol","agent_model_source":"environment","agent_instance":"3f43cca22c8538dbf0bf9483","agent_provenance":{"model":{"value":"gpt-5.6-sol","source":"environment"},"effort":null,"role":null,"topic":null},"op":"reopen","patch":[{"op":"remove","path":"/metadata/close_reason"},{"op":"remove","path":"/metadata/actual_result"},{"op":"remove","path":"/metadata/expected_result"},{"op":"remove","path":"/metadata/resolution"},{"op":"remove","path":"/metadata/completed_at"},{"op":"remove","path":"/metadata/closed_at"},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-18T03:43:37.356Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"e2fc273e9bf0b64081ffe2145aac1e10a547a1a4a0a772d60d2a314e50c4eb66","after_hash":"4be4dff3693b6aeac7b01bc175decd4ec4fb54cd67275081b15c122d21dbbad9","item_hash_version":3,"message":"Reopened after PR review so remaining acceptance work is not lost","context":{"recurrence":{"reason":"Review correctly identified unfinished index/server bootstrap acceptance coverage; keep the item active for the next tranche.","from_status":"closed","to_status":"in_progress","previous_terminal":{"close_reason":"Behavioral coverage tranche delivered and all local release gates pass; remaining 100% work is explicitly inventoried for the next tranche.","resolution":"Added real HTTP/PostgreSQL/pm-workspace collaboration tests for cross-client SSE delivery, concurrent no-loss writes with valid history, and broad routes/pm.ts lifecycle behavior; ratcheted coverage floors without weakening the branch gate.","expected_result":"The collaboration guarantees and pm route command surface are behaviorally covered, npm run release:check passes, and measured coverage floors only move upward.","actual_result":"npm run release:check exits 0 with 398/398 tests passing and aggregate src coverage L/B/F 76.82/80.32/73.00; floors are now 76/80/73. Remaining uncovered files and exact lines are recorded in the preceding item comment; public/src remains intentionally unmeasured at 14,884 TypeScript lines."}}},"event_class":"substantive","record_hash_version":1,"record_hash":"94d58c69b46bc1e839246dd880dd576a2b275423f37a4de9b6e4fb23da65dad8"} {"hash_algorithm":"sha256","ts":"2026-09-18T03:43:37.813Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"gpt-5.6-sol","agent_model_source":"environment","agent_instance":"3f43cca22c8538dbf0bf9483","agent_provenance":{"model":{"value":"gpt-5.6-sol","source":"environment"},"effort":null,"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/5","value":{"created_at":"2026-09-18T03:43:37.813Z","author":"pi-agent","text":"PR review finding accepted: the task remains in_progress because successful src/index.ts and src/server.ts bootstrap coverage and additional positional actions remain unfinished. This PR is a measured ratchet tranche, not completion of the full item."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-18T03:43:37.813Z"}],"before_hash":"4be4dff3693b6aeac7b01bc175decd4ec4fb54cd67275081b15c122d21dbbad9","after_hash":"1e4b5df72cc79f33b77776d5c6192e30f5c8587f118e30fc75ca5c3db35378cc","item_hash_version":3,"event_class":"substantive","record_hash_version":1,"record_hash":"9374a7dff4f7b0c7669848c744ade9ec2717a49ee1477e94cc24c82e383bac04"} +{"hash_algorithm":"sha256","ts":"2026-09-18T04:23:30.934Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"gpt-5.6-sol","agent_model_source":"environment","agent_instance":"daeba66ee0c70117756542f5","agent_provenance":{"model":{"value":"gpt-5.6-sol","source":"environment"},"effort":null,"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-18T04:23:30.934Z"},{"op":"add","path":"/metadata/assignee","value":"pi-agent"}],"before_hash":"1e4b5df72cc79f33b77776d5c6192e30f5c8587f118e30fc75ca5c3db35378cc","after_hash":"ce5e6cb62560bc1f5e4726a8d0146c945658c61ae12196ab9966dc8b76338fd0","item_hash_version":3,"event_class":"substantive","record_hash_version":1,"record_hash":"99551afee87ed8d802b0657505a666a54d492699bd73be26035d657257798da7"} +{"hash_algorithm":"sha256","ts":"2026-09-18T04:47:05.242Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"gpt-5.6-sol","agent_model_source":"environment","agent_instance":"daeba66ee0c70117756542f5","agent_provenance":{"model":{"value":"gpt-5.6-sol","source":"environment"},"effort":null,"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/6","value":{"created_at":"2026-09-18T04:47:05.242Z","author":"pi-agent","text":"Bootstrap tranche measured with npm run coverage: 400/400 pass; aggregate src L/B/F 80.10/80.05/75.93 (baseline 76.82/80.29/73.00). Real src/server.ts child starts on an ephemeral loopback port against the test PostgreSQL database and temporary PROJECTS_ROOT, answers /healthz, then shuts down gracefully on SIGTERM. Real host command dispatch covers status up/down/degraded, doctor free/occupied port, and stop absent/stale/live PID behavior. Ratcheted floors to L/B/F 80/80/75. Remaining uncovered lines by file: app.ts 77-78,93-94,153-156,182-183,219-221; auth.ts 10-11; board.ts 49; db.ts 20-27,200-204; health.ts 117-118,222-226,355-365; index.ts 27-30,196-211,277,297-298,344-399,460,491-494; oidc.ts 136-138,161-162,164-165,170-171,229-230,299-300,315-316,318-319,327-328,404-405,434-435,441-442,444-445,451-452,567-572; routes/admin.ts 37-39,90-92,107-109,125-127,136-138,149-151,165-167,186-188,202-204,225-227,244-245; routes/auth.ts 84-86,123-125,145-147,161-163,186-188,200-202; routes/extensions.ts 218-229,237-248,256-267; routes/github.ts 120-133,138-161,166-195,249-251,262-264,266-272,274-275,283-290,295-380,385-444,449-466; routes/groups.ts 28-30,60-62,101-103,125-127,143-145,195-197,232-234; routes/oidc.ts 46-52,65-78,91-108,147-152,154-156,163-188,192-247; routes/pm.ts 42-55,71-74,82-93,104-107,167-169,182-184,187-189,191-194,207-213,226-237,287-394,422-452,458,481-484,557-561,592-593,603,766-768,786-788,879-881,916-950,1178-1238,1401-1403,1432-1434,1452-1454,1464-1466,1479-1501,1506-1533,1538-1562,1567-1590,1595-1604,1609-1625,1630-1644,1649-1663,1668-1682,1687-1701,1706-1715,1720-1739,1744-1752,1757-1765,1770-1785,1790-1807,1812-1828,1833-1841,1846-1854,1859-1877,1882-1890,1895-1904,1909-1919,1937-1939,1945-1970,1972-2008,2018-2070,2075-2120,2128-2198,2203-2211,2216-2239,2244-2256,2261-2268,2273-2276,2281-2284,2289-2292,2297-2300,2305-2309,2314-2323,2342-2365,2384-2406,2411-2421,2426-2443,2448-2465,2470-2489,2494-2511,2516-2533,2538-2558,2563-2580,2585-2602,2607-2625,2630-2653,2658-2678,2683-2701,2709-2718,2722-2736,2741-2758,2768-2771,2784-2787,2804-2809; routes/projects.ts 78-80,117-129,137-139,152-154,172-174,191-193; routes/sharing.ts 59-61,156-158,181-183,210-212; server.ts 28-29,70-72,76-77,93-94; services/mutation-event-watcher.ts 109; services/pm-runner.ts 123-130,162-167,207-213,231-233,326-333,367-369,487-488,534-540,566-572,712-714,803-809,876-878,886-888,1006-1007; services/project-watcher.ts 47,156-157,205-206; services/realtime-bus.ts 187-190,192-196,216-221; services/sse.ts 322-323,344-345,405-429. Item remains in_progress because all metrics are not 100."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-18T04:47:05.242Z"}],"before_hash":"ce5e6cb62560bc1f5e4726a8d0146c945658c61ae12196ab9966dc8b76338fd0","after_hash":"0905e22ba0a988fb6b354a1fed6d323bb57ebc9d231e33910f8dae51cba3b22d","item_hash_version":3,"event_class":"substantive","record_hash_version":1,"record_hash":"650475e847a9104ebdbce724c8eaa3a382a034dd6386f0b53529438e69058d7b"} +{"hash_algorithm":"sha256","ts":"2026-09-18T04:47:22.708Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"gpt-5.6-sol","agent_model_source":"environment","agent_instance":"daeba66ee0c70117756542f5","agent_provenance":{"model":{"value":"gpt-5.6-sol","source":"environment"},"effort":null,"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"add","path":"/metadata/tests/1","value":{"command":"npm run coverage","scope":"project","provenance":{"author":"pi-agent","created_at":"2026-09-18T04:47:22.446Z","source_kind":"local_mutation","source_ref":"test/cover-pm-web-bootstrap-and-remaining-routes"}}},{"op":"add","path":"/metadata/files/2","value":{"path":"test/smoke.test.ts","scope":"project"}},{"op":"add","path":"/metadata/files/3","value":{"path":"src/server.ts","scope":"project"}},{"op":"add","path":"/metadata/files/4","value":{"path":"test/extension.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-18T04:47:22.708Z"}],"before_hash":"0905e22ba0a988fb6b354a1fed6d323bb57ebc9d231e33910f8dae51cba3b22d","after_hash":"b8f8ef459e8f495cb65fc7b57e8f99f0d40e1fd6e6623d429f55e8d21fab91ed","item_hash_version":3,"event_class":"maintenance","record_hash_version":1,"record_hash":"826dcac23b34a1b1dddf1908d6528ad5c3cdd5fcac77c7f7156755ea1b57f398"} diff --git a/.agents/pm/tasks/pm-web-ulgy.toon b/.agents/pm/tasks/pm-web-ulgy.toon index 1784f467..a93e303c 100644 --- a/.agents/pm/tasks/pm-web-ulgy.toon +++ b/.agents/pm/tasks/pm-web-ulgy.toon @@ -6,24 +6,51 @@ status: in_progress priority: 2 tags: [] created_at: "2026-07-29T17:26:40.207Z" -updated_at: "2026-09-18T03:43:37.813Z" +updated_at: "2026-09-18T04:47:22.708Z" +assignee: pi-agent claim_principal: pi-agent author: codex estimated_minutes: 1800 acceptance_criteria: routes/pm.ts is exercised against a real pm workspace rather than a fake pm binary so in-process SDK dispatch is observable; a regression test asserts that a '..' folder traversal is rejected and that items never land outside the pm root; POSITIONAL_KEYS coverage proves no positional is silently dropped; index.ts and server.ts bootstrap paths are covered; thresholds ratchet again parent: pm-web-9ulj -comments[6]{created_at,author,text}: +comments[7]{created_at,author,text}: "2026-09-18T03:12:09.394Z",pi-agent,"Baseline npm run coverage: 395/395 tests pass; aggregate src coverage L/B/F 71.06/81.28/65.71. Per-file baseline is captured in the coverage run for this branch; largest gaps are routes/pm.ts 30.67/58.95/14.41, index.ts 52.40/94.59/30.00, routes/github.ts 45.63/71.79/35.71, routes/oidc.ts 48.00/100.00/22.22, server.ts 62.79/33.33/33.33, and db.ts 37.56/70.00/66.67." "2026-09-18T03:12:10.032Z",pi-agent,"Scope note: public/src is intentionally excluded from this PR's coverage gate and is currently unmeasured. It contains 14,884 TypeScript source lines by find public/src -type f -name '*.ts' | xargs wc -l." "2026-09-18T03:28:07.103Z",pi-agent,"Milestone: added real loopback HTTP + real PostgreSQL + real temporary pm workspace coverage. Client B now opens the production SSE endpoint and observes client A's create event. Two edit collaborators concurrently PATCH distinct fields on one item; both writes survive and every history JSONL row parses, with both messages present. A lifecycle scenario exercises rich create, reads, updates, comments, notes, append/history, dependencies, relationships, status views, close/delete, authorization misses, validation errors, and missing-item failures." "2026-09-18T03:28:07.561Z",pi-agent,"Measured after npm run coverage: 398/398 pass; aggregate src L/B/F 76.82/80.06/73.00 (baseline 71.06/81.28/65.71). Ratcheted declared floors to L/B/F 76/80/73; branch floor remains at 80 rather than weakening. Remaining uncovered source lines by file: app.ts 77-78,93-94,153-156,182-183,219-221; auth.ts 10-11; board.ts 49; db.ts 20-27,86-205; health.ts 117-118,222-226,355-365; index.ts 27-30,182-185,196-211,214-236,239-249,251-254,268-279,290-299,311-319,344-399,415-435,451-505,521-582; oidc.ts 136-138,161-162,164-165,170-171,229-230,238-239,299-300,315-316,318-319,327-328,404-405,434-435,441-442,444-445,451-452,567-572; routes/admin.ts 37-39,90-92,107-109,125-127,136-138,149-151,165-167,186-188,202-204,225-227,244-245; routes/auth.ts 84-86,123-125,145-147,161-163,186-188,200-202; routes/extensions.ts 218-229,237-248,256-267; routes/github.ts 120-133,138-161,166-195,249-251,262-264,266-272,274-275,283-290,295-380,385-444,449-466; routes/groups.ts 28-30,60-62,101-103,125-127,143-145,195-197,232-234; routes/oidc.ts 46-52,65-78,91-108,147-152,154-156,163-188,192-247; routes/pm.ts 42-55,71-74,82-93,104-107,167-169,182-184,187-189,191-194,207-213,226-237,287-394,422-452,458,481-484,557-561,592-593,603,766-768,786-788,879-881,916-950,1178-1238,1401-1403,1432-1434,1452-1454,1464-1466,1479-1501,1506-1533,1538-1562,1567-1590,1595-1604,1609-1625,1630-1644,1649-1663,1668-1682,1687-1701,1706-1715,1720-1739,1744-1752,1757-1765,1770-1785,1790-1807,1812-1828,1833-1841,1846-1854,1859-1877,1882-1890,1895-1904,1909-1919,1937-1939,1945-1970,1972-2008,2018-2070,2075-2120,2128-2198,2203-2211,2216-2239,2244-2256,2261-2268,2273-2276,2281-2284,2289-2292,2297-2300,2305-2309,2314-2323,2342-2365,2384-2406,2411-2421,2426-2443,2448-2465,2470-2489,2494-2511,2516-2533,2538-2558,2563-2580,2585-2602,2607-2625,2630-2653,2658-2678,2683-2701,2709-2718,2722-2736,2741-2758,2768-2771,2784-2787,2804-2809; routes/projects.ts 78-80,117-129,137-139,152-154,172-174,191-193; routes/sharing.ts 59-61,156-158,181-183,210-212; server.ts 28-29,57-86; services/mutation-event-watcher.ts 109; services/pm-runner.ts 123-130,162-167,207-213,231-233,326-333,367-369,487-488,534-540,566-572,712-714,803-809,876-878,886-888,1006-1007; services/project-watcher.ts 44-49,156-157,205-206,325-333; services/realtime-bus.ts 187-190,192-196,216-221; services/sse.ts 322-323,344-345,405-429. Files already at 100 lines: crypto.ts, csrf.ts, ical.ts, middleware/auth.ts, rate-limit.ts, route-params.ts, services/fleet-snapshot.ts, services/package-catalog.ts." "2026-09-18T03:35:19.045Z",pi-agent,"First release:check run exposed branch-coverage nondeterminism at 79.97% versus the preserved 80% floor. Added observable alternate-path coverage for unfiltered list/list-all, an SDK-issued compatible pagination cursor, invalid context depth, activity without a limit, presence without connected clients, and a missing item read. The cursor test initially omitted its original filters and correctly received the pm SDK fingerprint-mismatch 400; it now replays the originating type/limit filters and passes." "2026-09-18T03:43:37.813Z",pi-agent,"PR review finding accepted: the task remains in_progress because successful src/index.ts and src/server.ts bootstrap coverage and additional positional actions remain unfinished. This PR is a measured ratchet tranche, not completion of the full item." + "2026-09-18T04:47:05.242Z",pi-agent,"Bootstrap tranche measured with npm run coverage: 400/400 pass; aggregate src L/B/F 80.10/80.05/75.93 (baseline 76.82/80.29/73.00). Real src/server.ts child starts on an ephemeral loopback port against the test PostgreSQL database and temporary PROJECTS_ROOT, answers /healthz, then shuts down gracefully on SIGTERM. Real host command dispatch covers status up/down/degraded, doctor free/occupied port, and stop absent/stale/live PID behavior. Ratcheted floors to L/B/F 80/80/75. Remaining uncovered lines by file: app.ts 77-78,93-94,153-156,182-183,219-221; auth.ts 10-11; board.ts 49; db.ts 20-27,200-204; health.ts 117-118,222-226,355-365; index.ts 27-30,196-211,277,297-298,344-399,460,491-494; oidc.ts 136-138,161-162,164-165,170-171,229-230,299-300,315-316,318-319,327-328,404-405,434-435,441-442,444-445,451-452,567-572; routes/admin.ts 37-39,90-92,107-109,125-127,136-138,149-151,165-167,186-188,202-204,225-227,244-245; routes/auth.ts 84-86,123-125,145-147,161-163,186-188,200-202; routes/extensions.ts 218-229,237-248,256-267; routes/github.ts 120-133,138-161,166-195,249-251,262-264,266-272,274-275,283-290,295-380,385-444,449-466; routes/groups.ts 28-30,60-62,101-103,125-127,143-145,195-197,232-234; routes/oidc.ts 46-52,65-78,91-108,147-152,154-156,163-188,192-247; routes/pm.ts 42-55,71-74,82-93,104-107,167-169,182-184,187-189,191-194,207-213,226-237,287-394,422-452,458,481-484,557-561,592-593,603,766-768,786-788,879-881,916-950,1178-1238,1401-1403,1432-1434,1452-1454,1464-1466,1479-1501,1506-1533,1538-1562,1567-1590,1595-1604,1609-1625,1630-1644,1649-1663,1668-1682,1687-1701,1706-1715,1720-1739,1744-1752,1757-1765,1770-1785,1790-1807,1812-1828,1833-1841,1846-1854,1859-1877,1882-1890,1895-1904,1909-1919,1937-1939,1945-1970,1972-2008,2018-2070,2075-2120,2128-2198,2203-2211,2216-2239,2244-2256,2261-2268,2273-2276,2281-2284,2289-2292,2297-2300,2305-2309,2314-2323,2342-2365,2384-2406,2411-2421,2426-2443,2448-2465,2470-2489,2494-2511,2516-2533,2538-2558,2563-2580,2585-2602,2607-2625,2630-2653,2658-2678,2683-2701,2709-2718,2722-2736,2741-2758,2768-2771,2784-2787,2804-2809; routes/projects.ts 78-80,117-129,137-139,152-154,172-174,191-193; routes/sharing.ts 59-61,156-158,181-183,210-212; server.ts 28-29,70-72,76-77,93-94; services/mutation-event-watcher.ts 109; services/pm-runner.ts 123-130,162-167,207-213,231-233,326-333,367-369,487-488,534-540,566-572,712-714,803-809,876-878,886-888,1006-1007; services/project-watcher.ts 47,156-157,205-206; services/realtime-bus.ts 187-190,192-196,216-221; services/sse.ts 322-323,344-345,405-429. Item remains in_progress because all metrics are not 100." notes[1]{created_at,author,text}: "2026-08-21T11:41:29.704Z",codex,"The certified complete-read tranche adds a real-workspace end-to-end test for paginated list-all,complete board/search/export/calendar views and raises routes/pm.ts from 27.28 to 30.70 percent lines and from 7.08 to 14.41 percent functions; branches are 58.95 percent. The shared output-owner test also raises src/index.ts to 51.20 lines,94.12 branches,and 31.58 functions. Most command and bootstrap paths remain uncovered,so this task stays open." -files[2]{path,scope,note}: - test/pm-collaboration.test.ts,project,Real HTTP collaboration and command lifecycle coverage - package.json,project,Coverage floors ratcheted to measured whole-number floors -tests[1]{command,path,scope,timeout_seconds,provenance{author,created_at,source_kind,source_ref}}: - npm run coverage,.,project,1200,pi-agent,"2026-09-18T03:28:08.077Z",local_mutation,test/cover-pm-web-server-routes-and-services +files[5]: + - path: test/pm-collaboration.test.ts + scope: project + note: Real HTTP collaboration and command lifecycle coverage + - path: package.json + scope: project + note: Coverage floors ratcheted to measured whole-number floors + - path: test/smoke.test.ts + scope: project + - path: src/server.ts + scope: project + - path: test/extension.test.ts + scope: project +tests[2]: + - command: npm run coverage + path: . + scope: project + timeout_seconds: 1200 + provenance: + author: pi-agent + created_at: "2026-09-18T03:28:08.077Z" + source_kind: local_mutation + source_ref: test/cover-pm-web-server-routes-and-services + - command: npm run coverage + scope: project + provenance: + author: pi-agent + created_at: "2026-09-18T04:47:22.446Z" + source_kind: local_mutation + source_ref: test/cover-pm-web-bootstrap-and-remaining-routes body: "" diff --git a/package.json b/package.json index fa40077c..85037b8c 100644 --- a/package.json +++ b/package.json @@ -132,9 +132,9 @@ "test/*.test.ts" ], "thresholds": { - "lines": 76, + "lines": 80, "branches": 80, - "functions": 73 + "functions": 75 }, "ignore": [] } diff --git a/src/server.ts b/src/server.ts index afa5e328..6c592070 100644 --- a/src/server.ts +++ b/src/server.ts @@ -76,9 +76,18 @@ initSchema() console.error(`Server error on :${PORT}:`, err.message); process.exit(1); }); - server.on("close", () => { stopProjectWatcher(); stopMutationEventWatcher(); void closeRealtimeBus(); }); // Periodic cleanup of stale SSE clients - setInterval(cleanupStaleClients, 5 * 60 * 1000); + const staleClientTimer = setInterval(cleanupStaleClients, 5 * 60 * 1000); + server.on("close", () => { + clearInterval(staleClientTimer); + stopProjectWatcher(); + stopMutationEventWatcher(); + void closeRealtimeBus() + .finally(() => pool.end()) + .finally(() => process.exit(0)); + }); + process.once("SIGINT", () => server.close()); + process.once("SIGTERM", () => server.close()); }) .catch((err) => { console.error("Failed to initialize pm-web runtime:", err instanceof Error ? err.message : err); diff --git a/test/extension.test.ts b/test/extension.test.ts index 552561bc..16e9b68c 100644 --- a/test/extension.test.ts +++ b/test/extension.test.ts @@ -41,6 +41,8 @@ test("nodeVersionMeetsRequirement enforces the package engine floor", () => { assert.strictEqual(nodeVersionMeetsRequirement("22.18.0"), true); assert.strictEqual(nodeVersionMeetsRequirement("22.19.0"), true); assert.strictEqual(nodeVersionMeetsRequirement("23.0.0"), true); + assert.strictEqual(nodeVersionMeetsRequirement("not-a-version"), false); + assert.strictEqual(nodeVersionMeetsRequirement("v22.18.0-nightly"), true); }); test("shapeStatusResult marks a reachable server as up", () => { diff --git a/test/smoke.test.ts b/test/smoke.test.ts index 863f97eb..eb59505c 100644 --- a/test/smoke.test.ts +++ b/test/smoke.test.ts @@ -1,5 +1,10 @@ import assert from "node:assert/strict"; -import { execFileSync } from "node:child_process"; +import { execFileSync, spawn } from "node:child_process"; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { createServer as createHttpServer } from "node:http"; +import { createServer as createNetServer } from "node:net"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import test from "node:test"; import { isHostOutputSuppressed } from "@unbrained/pm-cli/sdk"; @@ -27,6 +32,28 @@ async function harness() { return created; } +/** Reserve and release an ephemeral loopback port for a child server test. */ +async function ephemeralPort(): Promise { + const reserver = createNetServer(); + await new Promise((resolve, reject) => { + reserver.once("error", reject); + reserver.listen(0, "127.0.0.1", resolve); + }); + const address = reserver.address(); + assert.ok(address && typeof address !== "string"); + const port = address.port; + await new Promise((resolve, reject) => { + reserver.close((err) => err ? reject(err) : resolve()); + }); + return port; +} + +/** Extract the extension value retained by the real host command runner. */ +function commandResult(value: { handled: boolean; result: unknown }): unknown { + assert.equal(value.handled, true); + return isHostOutputSuppressed(value.result) ? value.result.result : value.result; +} + test("pm-web extension activates cleanly against the real pm host", async () => { const ext = await harness(); assert.strictEqual(ext.name, "pm-web"); @@ -96,6 +123,138 @@ test("server entrypoint exits non-zero without DATABASE_URL", () => { assert.notEqual(status, 0, "expected a non-zero exit code"); }); +test("production server bootstrap listens on an ephemeral port and answers HTTP", { timeout: 30_000 }, async () => { + const port = await ephemeralPort(); + const root = mkdtempSync(join(tmpdir(), "pm-web-")); + const child = spawn(process.execPath, ["src/server.ts"], { + cwd: process.cwd(), + env: { + ...process.env, + PORT: String(port), + PROJECTS_ROOT: root, + PM_REALTIME_MUTATION_EVENTS: "false", + }, + stdio: ["ignore", "pipe", "pipe"], + }); + let stdout = ""; + let stderr = ""; + child.stdout.setEncoding("utf8"); + child.stderr.setEncoding("utf8"); + child.stdout.on("data", (chunk: string) => { stdout += chunk; }); + child.stderr.on("data", (chunk: string) => { stderr += chunk; }); + + try { + await new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error(`server did not start; stdout=${stdout} stderr=${stderr}`)), 20_000); + const inspect = () => { + if (stdout.includes(`pm-web running on :${port}`)) { + clearTimeout(timer); + resolve(); + } + }; + child.stdout.on("data", inspect); + child.once("exit", (code, signal) => { + clearTimeout(timer); + reject(new Error(`server exited before listening: code=${code} signal=${signal}; stderr=${stderr}`)); + }); + }); + + const response = await fetch(`http://127.0.0.1:${port}/healthz`); + const body = await response.json() as { ok: boolean; version: string }; + assert.equal(response.status, 200); + assert.equal(body.ok, true); + assert.match(body.version, /^2026\./); + } finally { + child.kill("SIGTERM"); + await new Promise((resolve) => { + if (child.exitCode !== null || child.signalCode !== null) resolve(); + else child.once("exit", () => resolve()); + }); + rmSync(root, { recursive: true, force: true }); + } +}); + +test("registered status, doctor, and stop commands use real environment state", { timeout: 30_000 }, async () => { + const port = await ephemeralPort(); + const root = mkdtempSync(join(tmpdir(), "pm-web-")); + const pmRoot = join(root, ".agents", "pm"); + const stateDir = join(root, "state"); + mkdirSync(pmRoot, { recursive: true }); + mkdirSync(stateDir, { recursive: true }); + writeFileSync(join(pmRoot, "settings.json"), "{}\n", "utf8"); + const previousProjectsRoot = process.env.PROJECTS_ROOT; + const previousStateDir = process.env.PM_WEB_STATE_DIR; + process.env.PROJECTS_ROOT = root; + process.env.PM_WEB_STATE_DIR = stateDir; + const ext = await harness(); + + try { + const down = commandResult(await ext.runCommand({ command: "web status", options: { port }, pmRoot })) as { status?: string; port?: number }; + assert.equal(down.status, "down"); + assert.equal(down.port, port); + + const doctor = commandResult(await ext.runCommand({ command: "web doctor", options: { port }, pmRoot })) as { version?: string; port?: number }; + assert.match(doctor.version ?? "", /^2026\./); + assert.equal(doctor.port, port); + + let healthStatus = 200; + let healthBody = JSON.stringify({ ok: true, version: "test-version" }); + const healthServer = createHttpServer((_request, response) => { + response.writeHead(healthStatus, { "content-type": "application/json" }); + response.end(healthBody); + }); + await new Promise((resolve, reject) => { + healthServer.once("error", reject); + healthServer.listen(port, "127.0.0.1", resolve); + }); + try { + const up = commandResult(await ext.runCommand({ command: "web status", options: { port }, global: { json: false }, pmRoot })) as { status?: string; version?: string }; + assert.equal(up.status, "up"); + assert.equal(up.version, "test-version"); + + healthStatus = 503; + healthBody = "not-json"; + const degraded = commandResult(await ext.runCommand({ command: "web status", options: { port }, global: { json: false }, pmRoot })) as { status?: string; version?: string | null }; + assert.equal(degraded.status, "degraded"); + assert.equal(degraded.version, null); + + const occupiedDoctor = commandResult(await ext.runCommand({ command: "web doctor", options: { port }, pmRoot })) as { checks?: Array<{ name: string; ok: boolean }> }; + assert.equal(occupiedDoctor.checks?.find((check) => check.name === "port_available")?.ok, false); + } finally { + await new Promise((resolve, reject) => { + healthServer.close((err) => err ? reject(err) : resolve()); + }); + } + + const stopped = commandResult(await ext.runCommand({ command: "web stop", options: { port }, pmRoot })) as { status?: string; port?: number }; + assert.equal(stopped.status, "not_running"); + assert.equal(stopped.port, port); + + writeFileSync(join(stateDir, `pm-web-${port}.pid`), "2147483647\n", "utf8"); + const stale = commandResult(await ext.runCommand({ command: "web stop", options: { port }, pmRoot })) as { status?: string; pid?: number }; + assert.equal(stale.status, "not_running"); + assert.equal(stale.pid, 2147483647); + + const target = spawn(process.execPath, ["-e", "setInterval(() => {}, 1000)"], { stdio: "ignore" }); + assert.equal(typeof target.pid, "number"); + writeFileSync(join(stateDir, `pm-web-${port}.pid`), `${target.pid}\n`, "utf8"); + const terminated = commandResult(await ext.runCommand({ command: "web stop", options: { port }, pmRoot })) as { status?: string; pid?: number }; + assert.equal(terminated.status, "stopped"); + assert.equal(terminated.pid, target.pid); + await new Promise((resolve) => { + if (target.exitCode !== null || target.signalCode !== null) resolve(); + else target.once("exit", () => resolve()); + }); + } finally { + await ext.deactivate(); + if (previousProjectsRoot === undefined) delete process.env.PROJECTS_ROOT; + else process.env.PROJECTS_ROOT = previousProjectsRoot; + if (previousStateDir === undefined) delete process.env.PM_WEB_STATE_DIR; + else process.env.PM_WEB_STATE_DIR = previousStateDir; + rmSync(root, { recursive: true, force: true }); + } +}); + test("no command redeclares a host-owned global flag", async () => { // Guards the whole surface, not just the commands that regressed: // registering any of these makes the host reject the command outright, and From 5f94a3800b59ad0a069653f2159baee3bc221512 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Fri, 18 Sep 2026 07:12:55 +0200 Subject: [PATCH 2/4] fix: close streaming clients during shutdown --- .agents/pm/history/pm-web-ulgy.jsonl | 2 ++ .agents/pm/tasks/pm-web-ulgy.toon | 15 ++++++++++++--- dist/server.js | 20 +++++++++++++++++--- dist/server.js.map | 2 +- dist/services/sse.d.ts | 7 +++++++ dist/services/sse.js | 16 ++++++++++++++++ dist/services/sse.js.map | 2 +- src/server.ts | 11 ++++++++--- src/services/sse.ts | 16 ++++++++++++++++ test/smoke.test.ts | 18 ++++++++++++------ test/sse.test.ts | 24 ++++++++++++++++++++++++ 11 files changed, 116 insertions(+), 17 deletions(-) diff --git a/.agents/pm/history/pm-web-ulgy.jsonl b/.agents/pm/history/pm-web-ulgy.jsonl index 9aafa4c7..a3d7ae39 100644 --- a/.agents/pm/history/pm-web-ulgy.jsonl +++ b/.agents/pm/history/pm-web-ulgy.jsonl @@ -15,3 +15,5 @@ {"hash_algorithm":"sha256","ts":"2026-09-18T04:23:30.934Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"gpt-5.6-sol","agent_model_source":"environment","agent_instance":"daeba66ee0c70117756542f5","agent_provenance":{"model":{"value":"gpt-5.6-sol","source":"environment"},"effort":null,"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-18T04:23:30.934Z"},{"op":"add","path":"/metadata/assignee","value":"pi-agent"}],"before_hash":"1e4b5df72cc79f33b77776d5c6192e30f5c8587f118e30fc75ca5c3db35378cc","after_hash":"ce5e6cb62560bc1f5e4726a8d0146c945658c61ae12196ab9966dc8b76338fd0","item_hash_version":3,"event_class":"substantive","record_hash_version":1,"record_hash":"99551afee87ed8d802b0657505a666a54d492699bd73be26035d657257798da7"} {"hash_algorithm":"sha256","ts":"2026-09-18T04:47:05.242Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"gpt-5.6-sol","agent_model_source":"environment","agent_instance":"daeba66ee0c70117756542f5","agent_provenance":{"model":{"value":"gpt-5.6-sol","source":"environment"},"effort":null,"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/6","value":{"created_at":"2026-09-18T04:47:05.242Z","author":"pi-agent","text":"Bootstrap tranche measured with npm run coverage: 400/400 pass; aggregate src L/B/F 80.10/80.05/75.93 (baseline 76.82/80.29/73.00). Real src/server.ts child starts on an ephemeral loopback port against the test PostgreSQL database and temporary PROJECTS_ROOT, answers /healthz, then shuts down gracefully on SIGTERM. Real host command dispatch covers status up/down/degraded, doctor free/occupied port, and stop absent/stale/live PID behavior. Ratcheted floors to L/B/F 80/80/75. Remaining uncovered lines by file: app.ts 77-78,93-94,153-156,182-183,219-221; auth.ts 10-11; board.ts 49; db.ts 20-27,200-204; health.ts 117-118,222-226,355-365; index.ts 27-30,196-211,277,297-298,344-399,460,491-494; oidc.ts 136-138,161-162,164-165,170-171,229-230,299-300,315-316,318-319,327-328,404-405,434-435,441-442,444-445,451-452,567-572; routes/admin.ts 37-39,90-92,107-109,125-127,136-138,149-151,165-167,186-188,202-204,225-227,244-245; routes/auth.ts 84-86,123-125,145-147,161-163,186-188,200-202; routes/extensions.ts 218-229,237-248,256-267; routes/github.ts 120-133,138-161,166-195,249-251,262-264,266-272,274-275,283-290,295-380,385-444,449-466; routes/groups.ts 28-30,60-62,101-103,125-127,143-145,195-197,232-234; routes/oidc.ts 46-52,65-78,91-108,147-152,154-156,163-188,192-247; routes/pm.ts 42-55,71-74,82-93,104-107,167-169,182-184,187-189,191-194,207-213,226-237,287-394,422-452,458,481-484,557-561,592-593,603,766-768,786-788,879-881,916-950,1178-1238,1401-1403,1432-1434,1452-1454,1464-1466,1479-1501,1506-1533,1538-1562,1567-1590,1595-1604,1609-1625,1630-1644,1649-1663,1668-1682,1687-1701,1706-1715,1720-1739,1744-1752,1757-1765,1770-1785,1790-1807,1812-1828,1833-1841,1846-1854,1859-1877,1882-1890,1895-1904,1909-1919,1937-1939,1945-1970,1972-2008,2018-2070,2075-2120,2128-2198,2203-2211,2216-2239,2244-2256,2261-2268,2273-2276,2281-2284,2289-2292,2297-2300,2305-2309,2314-2323,2342-2365,2384-2406,2411-2421,2426-2443,2448-2465,2470-2489,2494-2511,2516-2533,2538-2558,2563-2580,2585-2602,2607-2625,2630-2653,2658-2678,2683-2701,2709-2718,2722-2736,2741-2758,2768-2771,2784-2787,2804-2809; routes/projects.ts 78-80,117-129,137-139,152-154,172-174,191-193; routes/sharing.ts 59-61,156-158,181-183,210-212; server.ts 28-29,70-72,76-77,93-94; services/mutation-event-watcher.ts 109; services/pm-runner.ts 123-130,162-167,207-213,231-233,326-333,367-369,487-488,534-540,566-572,712-714,803-809,876-878,886-888,1006-1007; services/project-watcher.ts 47,156-157,205-206; services/realtime-bus.ts 187-190,192-196,216-221; services/sse.ts 322-323,344-345,405-429. Item remains in_progress because all metrics are not 100."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-18T04:47:05.242Z"}],"before_hash":"ce5e6cb62560bc1f5e4726a8d0146c945658c61ae12196ab9966dc8b76338fd0","after_hash":"0905e22ba0a988fb6b354a1fed6d323bb57ebc9d231e33910f8dae51cba3b22d","item_hash_version":3,"event_class":"substantive","record_hash_version":1,"record_hash":"650475e847a9104ebdbce724c8eaa3a382a034dd6386f0b53529438e69058d7b"} {"hash_algorithm":"sha256","ts":"2026-09-18T04:47:22.708Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"gpt-5.6-sol","agent_model_source":"environment","agent_instance":"daeba66ee0c70117756542f5","agent_provenance":{"model":{"value":"gpt-5.6-sol","source":"environment"},"effort":null,"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"add","path":"/metadata/tests/1","value":{"command":"npm run coverage","scope":"project","provenance":{"author":"pi-agent","created_at":"2026-09-18T04:47:22.446Z","source_kind":"local_mutation","source_ref":"test/cover-pm-web-bootstrap-and-remaining-routes"}}},{"op":"add","path":"/metadata/files/2","value":{"path":"test/smoke.test.ts","scope":"project"}},{"op":"add","path":"/metadata/files/3","value":{"path":"src/server.ts","scope":"project"}},{"op":"add","path":"/metadata/files/4","value":{"path":"test/extension.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-18T04:47:22.708Z"}],"before_hash":"0905e22ba0a988fb6b354a1fed6d323bb57ebc9d231e33910f8dae51cba3b22d","after_hash":"b8f8ef459e8f495cb65fc7b57e8f99f0d40e1fd6e6623d429f55e8d21fab91ed","item_hash_version":3,"event_class":"maintenance","record_hash_version":1,"record_hash":"826dcac23b34a1b1dddf1908d6528ad5c3cdd5fcac77c7f7156755ea1b57f398"} +{"hash_algorithm":"sha256","ts":"2026-09-18T05:12:25.878Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"gpt-5.6-sol","agent_model_source":"environment","agent_instance":"daeba66ee0c70117756542f5","agent_provenance":{"model":{"value":"gpt-5.6-sol","source":"environment"},"effort":null,"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/7","value":{"created_at":"2026-09-18T05:12:25.877Z","author":"pi-agent","text":"PR review follow-up: accepted both Greptile findings. Graceful signal shutdown now ends and removes all active SSE clients before server.close so streaming responses cannot block shutdown; added direct observable coverage. The live-PID test now tracks its helper child outside the body and always terminates/awaits it in finally. CI also required freshly built tracked dist/server.js and source map, which are included."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-18T05:12:25.878Z"}],"before_hash":"b8f8ef459e8f495cb65fc7b57e8f99f0d40e1fd6e6623d429f55e8d21fab91ed","after_hash":"c7c618be24f5b50ea3f3745c6d5efd466c1d18404d095131a8f59f5207a35ea6","item_hash_version":3,"event_class":"substantive","record_hash_version":1,"record_hash":"009beffe99acba2dcdaa0ad8fa95f1fd0994f947d7b6314329b3c6f807ffa520"} +{"hash_algorithm":"sha256","ts":"2026-09-18T05:12:26.831Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"gpt-5.6-sol","agent_model_source":"environment","agent_instance":"daeba66ee0c70117756542f5","agent_provenance":{"model":{"value":"gpt-5.6-sol","source":"environment"},"effort":null,"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"add","path":"/metadata/files/5","value":{"path":"src/services/sse.ts","scope":"project"}},{"op":"add","path":"/metadata/files/6","value":{"path":"test/sse.test.ts","scope":"project"}},{"op":"add","path":"/metadata/files/7","value":{"path":"dist/server.js","scope":"project"}},{"op":"add","path":"/metadata/files/8","value":{"path":"dist/server.js.map","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-18T05:12:26.831Z"}],"before_hash":"c7c618be24f5b50ea3f3745c6d5efd466c1d18404d095131a8f59f5207a35ea6","after_hash":"e301d0660871aeeb2acf716cf047e8a3550462fe7851131aa009298442613986","item_hash_version":3,"event_class":"maintenance","record_hash_version":1,"record_hash":"c8f1644814cc41c8f64125ab98e6b8a49076b8df5baab2ac0e74537209b2446a"} diff --git a/.agents/pm/tasks/pm-web-ulgy.toon b/.agents/pm/tasks/pm-web-ulgy.toon index a93e303c..bc7db297 100644 --- a/.agents/pm/tasks/pm-web-ulgy.toon +++ b/.agents/pm/tasks/pm-web-ulgy.toon @@ -6,14 +6,14 @@ status: in_progress priority: 2 tags: [] created_at: "2026-07-29T17:26:40.207Z" -updated_at: "2026-09-18T04:47:22.708Z" +updated_at: "2026-09-18T05:12:26.831Z" assignee: pi-agent claim_principal: pi-agent author: codex estimated_minutes: 1800 acceptance_criteria: routes/pm.ts is exercised against a real pm workspace rather than a fake pm binary so in-process SDK dispatch is observable; a regression test asserts that a '..' folder traversal is rejected and that items never land outside the pm root; POSITIONAL_KEYS coverage proves no positional is silently dropped; index.ts and server.ts bootstrap paths are covered; thresholds ratchet again parent: pm-web-9ulj -comments[7]{created_at,author,text}: +comments[8]{created_at,author,text}: "2026-09-18T03:12:09.394Z",pi-agent,"Baseline npm run coverage: 395/395 tests pass; aggregate src coverage L/B/F 71.06/81.28/65.71. Per-file baseline is captured in the coverage run for this branch; largest gaps are routes/pm.ts 30.67/58.95/14.41, index.ts 52.40/94.59/30.00, routes/github.ts 45.63/71.79/35.71, routes/oidc.ts 48.00/100.00/22.22, server.ts 62.79/33.33/33.33, and db.ts 37.56/70.00/66.67." "2026-09-18T03:12:10.032Z",pi-agent,"Scope note: public/src is intentionally excluded from this PR's coverage gate and is currently unmeasured. It contains 14,884 TypeScript source lines by find public/src -type f -name '*.ts' | xargs wc -l." "2026-09-18T03:28:07.103Z",pi-agent,"Milestone: added real loopback HTTP + real PostgreSQL + real temporary pm workspace coverage. Client B now opens the production SSE endpoint and observes client A's create event. Two edit collaborators concurrently PATCH distinct fields on one item; both writes survive and every history JSONL row parses, with both messages present. A lifecycle scenario exercises rich create, reads, updates, comments, notes, append/history, dependencies, relationships, status views, close/delete, authorization misses, validation errors, and missing-item failures." @@ -21,9 +21,10 @@ comments[7]{created_at,author,text}: "2026-09-18T03:35:19.045Z",pi-agent,"First release:check run exposed branch-coverage nondeterminism at 79.97% versus the preserved 80% floor. Added observable alternate-path coverage for unfiltered list/list-all, an SDK-issued compatible pagination cursor, invalid context depth, activity without a limit, presence without connected clients, and a missing item read. The cursor test initially omitted its original filters and correctly received the pm SDK fingerprint-mismatch 400; it now replays the originating type/limit filters and passes." "2026-09-18T03:43:37.813Z",pi-agent,"PR review finding accepted: the task remains in_progress because successful src/index.ts and src/server.ts bootstrap coverage and additional positional actions remain unfinished. This PR is a measured ratchet tranche, not completion of the full item." "2026-09-18T04:47:05.242Z",pi-agent,"Bootstrap tranche measured with npm run coverage: 400/400 pass; aggregate src L/B/F 80.10/80.05/75.93 (baseline 76.82/80.29/73.00). Real src/server.ts child starts on an ephemeral loopback port against the test PostgreSQL database and temporary PROJECTS_ROOT, answers /healthz, then shuts down gracefully on SIGTERM. Real host command dispatch covers status up/down/degraded, doctor free/occupied port, and stop absent/stale/live PID behavior. Ratcheted floors to L/B/F 80/80/75. Remaining uncovered lines by file: app.ts 77-78,93-94,153-156,182-183,219-221; auth.ts 10-11; board.ts 49; db.ts 20-27,200-204; health.ts 117-118,222-226,355-365; index.ts 27-30,196-211,277,297-298,344-399,460,491-494; oidc.ts 136-138,161-162,164-165,170-171,229-230,299-300,315-316,318-319,327-328,404-405,434-435,441-442,444-445,451-452,567-572; routes/admin.ts 37-39,90-92,107-109,125-127,136-138,149-151,165-167,186-188,202-204,225-227,244-245; routes/auth.ts 84-86,123-125,145-147,161-163,186-188,200-202; routes/extensions.ts 218-229,237-248,256-267; routes/github.ts 120-133,138-161,166-195,249-251,262-264,266-272,274-275,283-290,295-380,385-444,449-466; routes/groups.ts 28-30,60-62,101-103,125-127,143-145,195-197,232-234; routes/oidc.ts 46-52,65-78,91-108,147-152,154-156,163-188,192-247; routes/pm.ts 42-55,71-74,82-93,104-107,167-169,182-184,187-189,191-194,207-213,226-237,287-394,422-452,458,481-484,557-561,592-593,603,766-768,786-788,879-881,916-950,1178-1238,1401-1403,1432-1434,1452-1454,1464-1466,1479-1501,1506-1533,1538-1562,1567-1590,1595-1604,1609-1625,1630-1644,1649-1663,1668-1682,1687-1701,1706-1715,1720-1739,1744-1752,1757-1765,1770-1785,1790-1807,1812-1828,1833-1841,1846-1854,1859-1877,1882-1890,1895-1904,1909-1919,1937-1939,1945-1970,1972-2008,2018-2070,2075-2120,2128-2198,2203-2211,2216-2239,2244-2256,2261-2268,2273-2276,2281-2284,2289-2292,2297-2300,2305-2309,2314-2323,2342-2365,2384-2406,2411-2421,2426-2443,2448-2465,2470-2489,2494-2511,2516-2533,2538-2558,2563-2580,2585-2602,2607-2625,2630-2653,2658-2678,2683-2701,2709-2718,2722-2736,2741-2758,2768-2771,2784-2787,2804-2809; routes/projects.ts 78-80,117-129,137-139,152-154,172-174,191-193; routes/sharing.ts 59-61,156-158,181-183,210-212; server.ts 28-29,70-72,76-77,93-94; services/mutation-event-watcher.ts 109; services/pm-runner.ts 123-130,162-167,207-213,231-233,326-333,367-369,487-488,534-540,566-572,712-714,803-809,876-878,886-888,1006-1007; services/project-watcher.ts 47,156-157,205-206; services/realtime-bus.ts 187-190,192-196,216-221; services/sse.ts 322-323,344-345,405-429. Item remains in_progress because all metrics are not 100." + "2026-09-18T05:12:25.877Z",pi-agent,"PR review follow-up: accepted both Greptile findings. Graceful signal shutdown now ends and removes all active SSE clients before server.close so streaming responses cannot block shutdown; added direct observable coverage. The live-PID test now tracks its helper child outside the body and always terminates/awaits it in finally. CI also required freshly built tracked dist/server.js and source map, which are included." notes[1]{created_at,author,text}: "2026-08-21T11:41:29.704Z",codex,"The certified complete-read tranche adds a real-workspace end-to-end test for paginated list-all,complete board/search/export/calendar views and raises routes/pm.ts from 27.28 to 30.70 percent lines and from 7.08 to 14.41 percent functions; branches are 58.95 percent. The shared output-owner test also raises src/index.ts to 51.20 lines,94.12 branches,and 31.58 functions. Most command and bootstrap paths remain uncovered,so this task stays open." -files[5]: +files[9]: - path: test/pm-collaboration.test.ts scope: project note: Real HTTP collaboration and command lifecycle coverage @@ -36,6 +37,14 @@ files[5]: scope: project - path: test/extension.test.ts scope: project + - path: src/services/sse.ts + scope: project + - path: test/sse.test.ts + scope: project + - path: dist/server.js + scope: project + - path: dist/server.js.map + scope: project tests[2]: - command: npm run coverage path: . diff --git a/dist/server.js b/dist/server.js index 591552ba..e6aa1fc2 100644 --- a/dist/server.js +++ b/dist/server.js @@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url"; import { initSchema, assertDbConfigured, pool } from "./db.js"; import { createApp } from "./app.js"; import { projectsRoot } from "./services/pm-runner.js"; -import { cleanupStaleClients } from "./services/sse.js"; +import { cleanupStaleClients, closeAllSSEClients } from "./services/sse.js"; import { startRealtimeBus } from "./services/realtime-bus.js"; import { startProjectWatcher } from "./services/project-watcher.js"; import { startMutationEventWatcher } from "./services/mutation-event-watcher.js"; @@ -70,9 +70,23 @@ initSchema() console.error(`Server error on :${PORT}:`, err.message); process.exit(1); }); - server.on("close", () => { stopProjectWatcher(); stopMutationEventWatcher(); void closeRealtimeBus(); }); // Periodic cleanup of stale SSE clients - setInterval(cleanupStaleClients, 5 * 60 * 1000); + const staleClientTimer = setInterval(cleanupStaleClients, 5 * 60 * 1000); + server.on("close", () => { + clearInterval(staleClientTimer); + stopProjectWatcher(); + stopMutationEventWatcher(); + void closeRealtimeBus() + .finally(() => pool.end()) + .finally(() => process.exit(0)); + }); + /** End streaming responses before waiting for the HTTP server to close. */ + const shutdown = () => { + closeAllSSEClients(); + server.close(); + }; + process.once("SIGINT", shutdown); + process.once("SIGTERM", shutdown); }) .catch((err) => { console.error("Failed to initialize pm-web runtime:", err instanceof Error ? err.message : err); diff --git a/dist/server.js.map b/dist/server.js.map index 3dd06e54..ff6b8aed 100644 --- a/dist/server.js.map +++ b/dist/server.js.map @@ -1 +1 @@ -{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/services/sse.d.ts b/dist/services/sse.d.ts index f5f30082..b4187050 100644 --- a/dist/services/sse.d.ts +++ b/dist/services/sse.d.ts @@ -152,6 +152,13 @@ export declare function getProjectPresence(projectId: string): PresenceUser[]; export declare function setupSSEHeaders(res: Response): void; /** Return the total number of currently connected SSE clients. */ export declare function getSSEClientCount(): number; +/** + * End every active SSE response and remove it from the connection indexes. + * + * Server shutdown calls this before `server.close()` because open streaming + * responses otherwise keep the HTTP server's close event pending indefinitely. + */ +export declare function closeAllSSEClients(): void; /** * Close long-lived clients and prune stale signal entries. * diff --git a/dist/services/sse.js b/dist/services/sse.js index 7141a740..2a044e6b 100644 --- a/dist/services/sse.js +++ b/dist/services/sse.js @@ -352,6 +352,22 @@ export function setupSSEHeaders(res) { export function getSSEClientCount() { return byId.size; } +/** + * End every active SSE response and remove it from the connection indexes. + * + * Server shutdown calls this before `server.close()` because open streaming + * responses otherwise keep the HTTP server's close event pending indefinitely. + */ +export function closeAllSSEClients() { + for (const client of [...byId.values()]) { + try { + client.res.end(); + } + finally { + removeClient(client); + } + } +} /** * Close long-lived clients and prune stale signal entries. * diff --git a/dist/services/sse.js.map b/dist/services/sse.js.map index 5ccfcaef..a649c051 100644 --- a/dist/services/sse.js.map +++ b/dist/services/sse.js.map @@ -1 +1 @@ -{"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/services/sse.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/services/sse.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 6c592070..3436f28d 100644 --- a/src/server.ts +++ b/src/server.ts @@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url"; import { initSchema, assertDbConfigured, pool } from "./db.ts"; import { createApp } from "./app.ts"; import { projectsRoot } from "./services/pm-runner.ts"; -import { cleanupStaleClients } from "./services/sse.ts"; +import { cleanupStaleClients, closeAllSSEClients } from "./services/sse.ts"; import { startRealtimeBus } from "./services/realtime-bus.ts"; import { startProjectWatcher } from "./services/project-watcher.ts"; import { startMutationEventWatcher } from "./services/mutation-event-watcher.ts"; @@ -86,8 +86,13 @@ initSchema() .finally(() => pool.end()) .finally(() => process.exit(0)); }); - process.once("SIGINT", () => server.close()); - process.once("SIGTERM", () => server.close()); + /** End streaming responses before waiting for the HTTP server to close. */ + const shutdown = (): void => { + closeAllSSEClients(); + server.close(); + }; + process.once("SIGINT", shutdown); + process.once("SIGTERM", shutdown); }) .catch((err) => { console.error("Failed to initialize pm-web runtime:", err instanceof Error ? err.message : err); diff --git a/src/services/sse.ts b/src/services/sse.ts index 3596db78..daeeab57 100644 --- a/src/services/sse.ts +++ b/src/services/sse.ts @@ -394,6 +394,22 @@ export function getSSEClientCount(): number { return byId.size; } +/** + * End every active SSE response and remove it from the connection indexes. + * + * Server shutdown calls this before `server.close()` because open streaming + * responses otherwise keep the HTTP server's close event pending indefinitely. + */ +export function closeAllSSEClients(): void { + for (const client of [...byId.values()]) { + try { + client.res.end(); + } finally { + removeClient(client); + } + } +} + /** * Close long-lived clients and prune stale signal entries. * diff --git a/test/smoke.test.ts b/test/smoke.test.ts index eb59505c..4eb9e855 100644 --- a/test/smoke.test.ts +++ b/test/smoke.test.ts @@ -187,6 +187,7 @@ test("registered status, doctor, and stop commands use real environment state", process.env.PROJECTS_ROOT = root; process.env.PM_WEB_STATE_DIR = stateDir; const ext = await harness(); + let target: ReturnType | null = null; try { const down = commandResult(await ext.runCommand({ command: "web status", options: { port }, pmRoot })) as { status?: string; port?: number }; @@ -235,17 +236,22 @@ test("registered status, doctor, and stop commands use real environment state", assert.equal(stale.status, "not_running"); assert.equal(stale.pid, 2147483647); - const target = spawn(process.execPath, ["-e", "setInterval(() => {}, 1000)"], { stdio: "ignore" }); - assert.equal(typeof target.pid, "number"); - writeFileSync(join(stateDir, `pm-web-${port}.pid`), `${target.pid}\n`, "utf8"); + const startedTarget = spawn(process.execPath, ["-e", "setInterval(() => {}, 1000)"], { stdio: "ignore" }); + target = startedTarget; + assert.equal(typeof startedTarget.pid, "number"); + writeFileSync(join(stateDir, `pm-web-${port}.pid`), `${startedTarget.pid}\n`, "utf8"); const terminated = commandResult(await ext.runCommand({ command: "web stop", options: { port }, pmRoot })) as { status?: string; pid?: number }; assert.equal(terminated.status, "stopped"); - assert.equal(terminated.pid, target.pid); + assert.equal(terminated.pid, startedTarget.pid); await new Promise((resolve) => { - if (target.exitCode !== null || target.signalCode !== null) resolve(); - else target.once("exit", () => resolve()); + if (startedTarget.exitCode !== null || startedTarget.signalCode !== null) resolve(); + else startedTarget.once("exit", () => resolve()); }); } finally { + if (target && target.exitCode === null && target.signalCode === null) { + target.kill("SIGTERM"); + await new Promise((resolve) => target?.once("exit", () => resolve())); + } await ext.deactivate(); if (previousProjectsRoot === undefined) delete process.env.PROJECTS_ROOT; else process.env.PROJECTS_ROOT = previousProjectsRoot; diff --git a/test/sse.test.ts b/test/sse.test.ts index 9840de70..44ade39d 100644 --- a/test/sse.test.ts +++ b/test/sse.test.ts @@ -5,6 +5,7 @@ import type { Response } from "express"; import { addSSEClient, broadcastProjectEvent, + closeAllSSEClients, configureProjectEventPublisher, consumeSignaledItemMutation, getProjectPresence, @@ -100,6 +101,29 @@ test("project events and presence are isolated per project and clients are index assert.equal(getProjectPresence(projectId).length, 0); }); +test("shutdown closes and removes every active SSE client", () => { + let ended = 0; + const response = { + write: () => true, + end: () => { ended += 1; }, + } as unknown as Response; + addSSEClient({ + id: "shutdown-a", projectId, userId: "user-a", displayName: "User A", + currentView: "items", res: response, connectedAt: new Date(), + }); + addSSEClient({ + id: "shutdown-b", projectId: otherProjectId, userId: "user-b", displayName: "User B", + currentView: "items", res: response, connectedAt: new Date(), + }); + + closeAllSSEClients(); + + assert.equal(ended, 2); + assert.equal(getSSEClientCount(), 0); + assert.deepEqual(getProjectPresence(projectId), []); + assert.deepEqual(getProjectPresence(otherProjectId), []); +}); + test("broadcastProjectEvent notes per-item signal when data has a string itemId", () => { // A route broadcast with a granular itemId payload should register a per-item // signal that the mutation-event watcher can consume to skip the duplicate. From 37caa6d121ed8cec6d88d3a9ef72fbd175b6ce51 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Fri, 18 Sep 2026 07:20:59 +0200 Subject: [PATCH 3/4] test: stabilize extension branch coverage --- test/extension.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/extension.test.ts b/test/extension.test.ts index 16e9b68c..879a3673 100644 --- a/test/extension.test.ts +++ b/test/extension.test.ts @@ -21,8 +21,10 @@ test("resolvePort defaults to 4000", () => { assert.strictEqual(resolvePort({}, {}), "4000"); }); -test("resolvePort ignores empty flag and empty env", () => { +test("resolvePort ignores absent values while retaining numeric zero", () => { assert.strictEqual(resolvePort({ port: "" }, { PORT: "" }), "4000"); + assert.strictEqual(resolvePort({ port: null }, { PORT: "4555" }), "4555"); + assert.strictEqual(resolvePort({ port: 0 }, { PORT: "4555" }), "0"); }); test("pidfilePath keys by port in the temp dir by default", () => { @@ -33,6 +35,7 @@ test("pidfilePath keys by port in the temp dir by default", () => { test("pidfilePath honors PM_WEB_STATE_DIR", () => { const p = pidfilePath("4000", { PM_WEB_STATE_DIR: "/var/state" }, os.tmpdir()); assert.strictEqual(p, path.join("/var/state", "pm-web-4000.pid")); + assert.strictEqual(pidfilePath("4001", { PM_WEB_STATE_DIR: "" }, "/tmp/fallback"), path.join("/tmp/fallback", "pm-web-4001.pid")); }); test("nodeVersionMeetsRequirement enforces the package engine floor", () => { From e4d2ffc005ac3fb353670132af91702c5baa1621 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Fri, 18 Sep 2026 07:38:47 +0200 Subject: [PATCH 4/4] Keep shutting down when one SSE response cannot end closeAllSSEClients removed a client in finally but let an exception from res.end() escape. That stopped the loop, left later streams open and indexed, and skipped the server.close() call in the signal handler, so one broken socket could stall the whole shutdown (CodeRabbit, major). Each failure is now logged and the loop continues; a test proves the healthy client after a throwing one still ends and every index is emptied. The smoke test also activates the harness inside its try block now, so a failed activation still restores PROJECTS_ROOT and PM_WEB_STATE_DIR and removes the temp root. --- dist/services/sse.js | 6 ++++++ dist/services/sse.js.map | 2 +- src/services/sse.ts | 5 +++++ test/smoke.test.ts | 5 +++-- test/sse.test.ts | 27 +++++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/dist/services/sse.js b/dist/services/sse.js index 2a044e6b..d3eb0324 100644 --- a/dist/services/sse.js +++ b/dist/services/sse.js @@ -363,6 +363,12 @@ export function closeAllSSEClients() { try { client.res.end(); } + catch (error) { + // One response that cannot end (e.g. its socket already failed) must not + // abort shutdown: the remaining streams still have to close, and the + // caller still has to reach server.close(). + console.error(`SSE client ${client.id} failed to end during shutdown:`, error instanceof Error ? error.message : error); + } finally { removeClient(client); } diff --git a/dist/services/sse.js.map b/dist/services/sse.js.map index a649c051..eddb81a0 100644 --- a/dist/services/sse.js.map +++ b/dist/services/sse.js.map @@ -1 +1 @@ -{"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/services/sse.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/services/sse.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/services/sse.ts b/src/services/sse.ts index daeeab57..6dc82971 100644 --- a/src/services/sse.ts +++ b/src/services/sse.ts @@ -404,6 +404,11 @@ export function closeAllSSEClients(): void { for (const client of [...byId.values()]) { try { client.res.end(); + } catch (error) { + // One response that cannot end (e.g. its socket already failed) must not + // abort shutdown: the remaining streams still have to close, and the + // caller still has to reach server.close(). + console.error(`SSE client ${client.id} failed to end during shutdown:`, error instanceof Error ? error.message : error); } finally { removeClient(client); } diff --git a/test/smoke.test.ts b/test/smoke.test.ts index 4eb9e855..943e7ef1 100644 --- a/test/smoke.test.ts +++ b/test/smoke.test.ts @@ -186,10 +186,11 @@ test("registered status, doctor, and stop commands use real environment state", const previousStateDir = process.env.PM_WEB_STATE_DIR; process.env.PROJECTS_ROOT = root; process.env.PM_WEB_STATE_DIR = stateDir; - const ext = await harness(); + let ext: Awaited> | undefined; let target: ReturnType | null = null; try { + ext = await harness(); const down = commandResult(await ext.runCommand({ command: "web status", options: { port }, pmRoot })) as { status?: string; port?: number }; assert.equal(down.status, "down"); assert.equal(down.port, port); @@ -252,7 +253,7 @@ test("registered status, doctor, and stop commands use real environment state", target.kill("SIGTERM"); await new Promise((resolve) => target?.once("exit", () => resolve())); } - await ext.deactivate(); + await ext?.deactivate(); if (previousProjectsRoot === undefined) delete process.env.PROJECTS_ROOT; else process.env.PROJECTS_ROOT = previousProjectsRoot; if (previousStateDir === undefined) delete process.env.PM_WEB_STATE_DIR; diff --git a/test/sse.test.ts b/test/sse.test.ts index 44ade39d..5315ddfa 100644 --- a/test/sse.test.ts +++ b/test/sse.test.ts @@ -164,3 +164,30 @@ test("consumeSignaledItemMutation is isolated per project", () => { assert.equal(consumeSignaledItemMutation(otherProjectId, "shared-item"), false, "different project has no signal"); assert.equal(consumeSignaledItemMutation(projectId, "shared-item"), true, "original project has the signal"); }); + +test("shutdown keeps closing the remaining SSE clients when one response fails to end", () => { + let ended = 0; + const failing = { + write: () => true, + end: () => { throw new Error("socket already destroyed"); }, + } as unknown as Response; + const healthy = { + write: () => true, + end: () => { ended += 1; }, + } as unknown as Response; + addSSEClient({ + id: "shutdown-failing", projectId, userId: "user-a", displayName: "User A", + currentView: "items", res: failing, connectedAt: new Date(), + }); + addSSEClient({ + id: "shutdown-healthy", projectId: otherProjectId, userId: "user-b", displayName: "User B", + currentView: "items", res: healthy, connectedAt: new Date(), + }); + + assert.doesNotThrow(() => closeAllSSEClients()); + + assert.equal(ended, 1); + assert.equal(getSSEClientCount(), 0); + assert.deepEqual(getProjectPresence(projectId), []); + assert.deepEqual(getProjectPresence(otherProjectId), []); +});