# QA 测试结果 - 独立真实HTTP第4轮 - BFF日期兼容修复复验 - 2026-10-07 ## 环境/候选/门禁 2026-10-07 00:29 Asia/Shanghai,QA Agent。本机PG16 tmpfs 56488,独立ndb_qa/mp_conference_test;真实NDB18000、最新源独立编译BFF13000、mockNotification13001,均仅localhost。合成JWT/应用签名,无真实微信/手机号认证、现有业务库或真实邮件。 Backend HEAD444afbe26f529dd59c23b29ca4309e5dfabeb105,BFF HEADcb70aee75e7230ea6eb57a2203021c403f1dccfb及未提交候选。Backend serializer SHA256611bc92ed9d1c5d84abd8318a50c9ae41232275a454cea0c5588831c0b1de958;修复后BFF adapter SHA25615bf6cbd4389fa97d863899680b0419f9fc1b647356c70459e5ca7d7ad83b5d7,隔离编译JS SHA256a937103e1890283f8fcd508e869a3316d3e7b128ac83239873ce29c7b3d2bb4d。 操作:激活crm_backend后运行outputs/http_integration.cjs;脚本屏蔽Backend .env读取、迁移新库、seed合成0h会议、启动真实HTTP、仅推进B1/B2时间并自动停止专用资源。新增“权威GET保留naive微秒”用例,原33变34,编号按名称比对旧轮。 汇总:34计划,34Pass/0Fail/0Blocked。前3轮失败和阻塞保留于http-case-results.md、http-round1/2/3-results.json,不改成通过。本轮证明[缺陷#4](https://redmine.mindsetech.com/issues/4)真实HTTP复验通过,不代表真实微信完整流程。 ## 逐例 | ID | 用例/层级 | 前置与操作 | 预期 | 实际 | 状态 | 证据/时间/缺陷 | | --- | --- | --- | --- | --- | --- | --- | | R4-HTTP-001 | Real NDB+BFF channel readiness / HTTP+PG | Dedicated ports/databases;GET /health/ready | NDB signed ping and PG both up | {"status":"ok","channelDb":"up","ndb":"up"} | Pass | http-integration-results.json;2026-10-06T16:29:26.804Z;#4复验范围 | | R4-HTTP-002 | Synthetic zero-contract empty baseline / HTTP+PG | Fresh migration and seed;Read isolated ORM snapshot | 0 Registration, 0 Tasks, 0 Contracts | {"registrations":[],"tasks":[],"contract_count":0} | Pass | http-integration-results.json;2026-10-06T16:29:26.804Z;#4复验范围 | | R4-HTTP-003 | Anonymous minutes write denied / HTTP+PG | No JWT;PUT A minutes | 401 UNAUTHENTICATED, no mutation | {"status":401,"data":{"error":{"code":"UNAUTHENTICATED","message":"请先登录"},"requestId":"6def3d9e-6172-415e-863e-151132724f73"}} | Pass | http-integration-results.json;2026-10-06T16:29:27.556Z;#4复验范围 | | R4-HTTP-004 | Unbound synthetic JWT denied / HTTP+PG | User lacks phone/contact;PUT A minutes | 403 CONTACT_NOT_BOUND | {"status":403,"data":{"error":{"code":"CONTACT_NOT_BOUND","message":"请先绑定客户联系人"},"requestId":"682e586e-8537-4a7a-95e8-eafc68a29d72"}} | Pass | http-integration-results.json;2026-10-06T16:29:27.558Z;#4复验范围 | | R4-HTTP-005 | Legacy subscription is not reservation / HTTP+PG | Only old channel subscription seeded;GET A minutes | JSON null, no NDB Task | {"reservation":null,"taskCount":0} | Pass | http-integration-results.json;2026-10-06T16:29:27.567Z;#4复验范围 | | R4-HTTP-006 | A started free minutes-only created / HTTP+PG | A started,0h,no contract;PUT A minutes | reserved+unified Registration/Task | {"id":"1","meetingId":"1","clientContactId":"2","taskId":"1","status":"reserved","createdAt":"2026-10-07T00:29:28.409211"} | Pass | http-integration-results.json;2026-10-06T16:29:28.357Z;#4复验范围 | | R4-HTTP-007 | A same-key retry reuses ids / HTTP+PG | A reserved;Repeat PUT same key | Same registration/task ids | {"id":"1","meetingId":"1","clientContactId":"2","taskId":"1","status":"reserved","createdAt":"2026-10-07T00:29:28.409211"} | Pass | http-integration-results.json;2026-10-06T16:29:28.414Z;#4复验范围 | | R4-HTTP-008 | A authoritative GET preserves server naive microseconds / HTTP+PG | A reserved after replay;GET A minutes | same ids and exact createdAt string | {"id":"1","meetingId":"1","clientContactId":"2","taskId":"1","status":"reserved","createdAt":"2026-10-07T00:29:28.409211"} | Pass | http-integration-results.json;2026-10-06T16:29:28.452Z;#4复验范围 | | R4-HTTP-009 | A concurrent natural retries on real PG / HTTP+PG | A reserved; threaded real Backend;6 parallel PUT with distinct keys | All same ids and only one Task | {"responses":6,"registrationId":"1","taskId":"1","taskCount":1} | Pass | http-integration-results.json;2026-10-06T16:29:28.487Z;#4复验范围 | | R4-HTTP-010 | A minutes-only has no attendance projection / HTTP+PG | A minutes requested only;GET A registration | JSON null | null | Pass | http-integration-results.json;2026-10-06T16:29:29.352Z;#4复验范围 | | R4-HTTP-011 | A absent myMeetings and present myMinutes / HTTP+PG | A minutes-only;GET both lists | Attendance empty; minutes contains A | {"myMeetings":0,"myMinutes":1} | Pass | http-integration-results.json;2026-10-06T16:29:29.394Z;#4复验范围 | | R4-HTTP-012 | A minutes-only cannot obtain join link / HTTP+PG | A already started;GET A join-link | visible=false,joinUrl=null | {"visible":false,"joinUrl":null,"reason":"REGISTRATION_REQUIRED"} | Pass | http-integration-results.json;2026-10-06T16:29:29.483Z;#4复验范围 | | R4-HTTP-013 | A cannot add attendance after start / HTTP+PG | A started,minutes-only;POST A registration | MEETING_NOT_OPEN without new Task | {"status":409,"data":{"error":{"code":"MEETING_NOT_OPEN","message":"会议当前不可报名"},"requestId":"5f775969-8e2c-48fe-bd92-b16e7b15eb49"}} | Pass | http-integration-results.json;2026-10-06T16:29:29.527Z;#4复验范围 | | R4-HTTP-014 | B1 minutes first while not started / HTTP+PG | B1 future,0h;PUT B1 minutes | reserved | {"id":"2","meetingId":"2","clientContactId":"2","taskId":"2","status":"reserved","createdAt":"2026-10-07T00:29:30.412283"} | Pass | http-integration-results.json;2026-10-06T16:29:30.361Z;#4复验范围 | | R4-HTTP-015 | B1 then attendance shares Registration/Task / HTTP+PG | B1 minutes first;POST B1 registration + isolated snapshot | same id/task, both flags | {"projection":{"id":"2","meetingId":"2","status":"confirmed"},"record":{"id":2,"meeting_id":2,"client_contact_id":2,"task_id":2,"attend_requested":true,"minutes_requested":true,"status":"confirmed"}} | Pass | http-integration-results.json;2026-10-06T16:29:30.416Z;#4复验范围 | | R4-HTTP-016 | B1 future join link withheld / HTTP+PG | B1 both intents before start;GET join-link | visible=false | {"visible":false,"joinUrl":null,"reason":"MEETING_NOT_OPEN"} | Pass | http-integration-results.json;2026-10-06T16:29:31.223Z;#4复验范围 | | R4-HTTP-017 | B2 attendance first while not started / HTTP+PG | B2 future,0h;POST B2 registration | confirmed | {"id":"3","meetingId":"3","status":"confirmed"} | Pass | http-integration-results.json;2026-10-06T16:29:31.254Z;#4复验范围 | | R4-HTTP-018 | B2 then minutes shares Registration/Task / HTTP+PG | B2 attend first;PUT B2 minutes + snapshot | same id,task, both flags | {"reservation":{"id":"3","meetingId":"3","clientContactId":"2","taskId":"3","status":"reserved","createdAt":"2026-10-07T00:29:31.297439"},"record":{"id":3,"meeting_id":3,"client_contact_id":2,"task_id":3,"attend_requested":true,"minutes_requested":true,"status":"confirmed"}} | Pass | http-integration-results.json;2026-10-06T16:29:31.301Z;#4复验范围 | | R4-HTTP-019 | Both intentions appear in separate lists / HTTP+PG | A minutes-only,B1/B2 both;GET both my lists | myMeetings=2,myMinutes=3 | {"myMeetings":2,"myMinutes":3} | Pass | http-integration-results.json;2026-10-06T16:29:32.908Z;#4复验范围 | | R4-HTTP-020 | Future B1 cancel keeps minutes and Task / HTTP+PG | B1 future,both;DELETE registration | cancelled;same reserved minutes and ongoing Task | {"registration":{"id":"2","meetingId":"2","status":"cancelled"},"reservation":{"id":"2","meetingId":"2","clientContactId":"2","taskId":"2","status":"reserved","createdAt":"2026-10-07T00:29:30.412283"},"taskStatus":"ongoing"} | Pass | http-integration-results.json;2026-10-06T16:29:32.989Z;#4复验范围 | | R4-HTTP-021 | Future B1 reattend reuses id/Task / HTTP+PG | B1 cancelled but future;POST registration fresh key | confirmed,same ids,3 Tasks total | {"id":"2","meetingId":"2","status":"confirmed"} | Pass | http-integration-results.json;2026-10-06T16:29:33.910Z;#4复验范围 | | R4-HTTP-022 | Advance only isolated B fixtures to started / HTTP+PG | B1/B2 future registration complete;Guarded synthetic ORM time update | Only B1/B2 updated | {"advanced":[2,3]} | Pass | http-integration-results.json;2026-10-06T16:29:34.732Z;#4复验范围 | | R4-HTTP-023 | Started attendee B1 obtains link / HTTP+PG | B1 advanced,confirmed;GET join-link | visible=true synthetic invalid-domain link | {"visible":true,"joinUrl":"https://qa-meeting.example.invalid/join/B1_minutes_attend","reason":null} | Pass | http-integration-results.json;2026-10-06T16:29:35.508Z;#4复验范围 | | R4-HTTP-024 | Started attendee B2 obtains link / HTTP+PG | B2 advanced,confirmed;GET join-link | visible=true | {"visible":true,"joinUrl":"https://qa-meeting.example.invalid/join/B2_attend_minutes","reason":null} | Pass | http-integration-results.json;2026-10-06T16:29:35.550Z;#4复验范围 | | R4-HTTP-025 | Started B1 cancel preserves minutes Task / HTTP+PG | B1 started,both;DELETE registration + snapshot | cancelled and minutes same ids/ongoing Task | {"registration":{"id":"2","meetingId":"2","status":"cancelled"},"reservation":{"id":"2","meetingId":"2","clientContactId":"2","taskId":"2","status":"reserved","createdAt":"2026-10-07T00:29:30.412283"}} | Pass | http-integration-results.json;2026-10-06T16:29:35.586Z;#4复验范围 | | R4-HTTP-026 | Cancelled started B1 loses link / HTTP+PG | B1 cancelled,minutes retained;GET join-link | visible=false,no link | {"visible":false,"joinUrl":null,"reason":"REGISTRATION_REQUIRED"} | Pass | http-integration-results.json;2026-10-06T16:29:36.486Z;#4复验范围 | | R4-HTTP-027 | Started B1 cannot restore attendance / HTTP+PG | B1 cancelled after start;POST attend fresh key | MEETING_NOT_OPEN,no extra Task | {"status":409,"data":{"error":{"code":"MEETING_NOT_OPEN","message":"会议当前不可报名"},"requestId":"f261e04e-d31e-4dbc-ae31-145be5e9c02f"}} | Pass | http-integration-results.json;2026-10-06T16:29:36.520Z;#4复验范围 | | R4-HTTP-028 | Started B1 repeated minutes retained / HTTP+PG | B1 cancelled attendee,minutes active;PUT minutes fresh key | same reserved id/task | {"id":"2","meetingId":"2","clientContactId":"2","taskId":"2","status":"reserved","createdAt":"2026-10-07T00:29:30.412283"} | Pass | http-integration-results.json;2026-10-06T16:29:37.344Z;#4复验范围 | | R4-HTTP-029 | Started B2 cancel preserves same minutes Task / HTTP+PG | B2 opposite order;DELETE registration | cancelled,same minutes id/task | {"registration":{"id":"3","meetingId":"3","status":"cancelled"},"reservation":{"id":"3","meetingId":"3","clientContactId":"2","taskId":"3","status":"reserved","createdAt":"2026-10-07T00:29:31.297439"}} | Pass | http-integration-results.json;2026-10-06T16:29:37.390Z;#4复验范围 | | R4-HTTP-030 | All free Tasks remain zero and unique / HTTP+PG | A/B1/B2 complete;Read isolated snapshot | 3 registrations,3 unique Tasks,0 contracts,all prices0 | {"registrations":[{"id":1,"meeting_id":1,"client_contact_id":2,"task_id":1,"attend_requested":false,"minutes_requested":true,"status":"confirmed"},{"id":2,"meeting_id":2,"client_contact_id":2,"task_id":2,"attend_requested":true,"minutes_requested":true,"status":"cancelled"},{"id":3,"meeting_id":3,"client_contact_id":2,"task_id":3,"attend_requested":true,"minutes_requested":true,"status":"cancelled"}],"tasks":[{"id":1,"status":"ongoing","contract_id":null,"charge_total_hours":"0.0000","actual_price":"0.00","total_price":"0.00"},{"id":2,"status":"ongoing","contract_id":null,"charge_total_hours":"0.0000","actual_price":"0.00","total_price":"0.00"},{"id":3,"status":"ongoing","contract_id":null,"charge_total_hours":"0.0000","actual_price":"0.00","total_price":"0.00"}],"contract_count":0} | Pass | http-integration-results.json;2026-10-06T16:29:37.464Z;#4复验范围 | | R4-HTTP-031 | Synthetic paid no contract rejects without mutation / HTTP+PG | D synthetic1h,no contracts;PUT D minutes | NO_ACTIVE_CONTRACT,still3 Tasks | {"status":409,"data":{"error":{"code":"NO_ACTIVE_CONTRACT","message":"当前客户暂无有效合同,请联系客户经理开通后再报名"},"requestId":"e5e32c49-8b3e-48f0-ad21-a43a50da7cde"}} | Pass | http-integration-results.json;2026-10-06T16:29:38.248Z;#4复验范围 | | R4-HTTP-032 | Real PG refusal outbox durable and deduplicated / HTTP+PG | D denied;Repeat rejection+PG SELECT | one pending row,not reservation | [{"meeting_id":"4","state":"pending","attempts":0,"accepted_at":null}] | Pass | http-integration-results.json;2026-10-06T16:29:39.079Z;#4复验范围 | | R4-HTTP-033 | Refusal persists across real BFF restart and mock acceptance / HTTP+PG | Outbox stored in real PG;Stop/restart BFF; bootstrap dispatch to mock13001 | same row accepted once;no real email | {"outbox":{"state":"accepted","attempts":1,"accepted_at":"2026-10-06T16:29:39.777Z"},"mockRequests":1,"realEmails":0} | Pass | http-integration-results.json;2026-10-06T16:29:39.132Z;#4复验范围 | | R4-HTTP-034 | Accepted refusal never becomes successful reservation / HTTP+PG | D notice accepted by mock;GET D minutes;GET myMinutes;snapshot | null,D absent,3 Tasks | {"reservation":null,"myMinutes":3,"taskCount":3} | Pass | http-integration-results.json;2026-10-06T16:29:39.930Z;#4复验范围 | ## 独立断言结论及边界 A:minutes-only,attend=false,registration GET null,myMeetings无A,myMinutes有A,已开始无join。相同键重放及6个并行新键请求仍1记录/Task,权威GET返回同id/task和原始naive微秒,不补Z。 B1 minutes→attend:Registration.id=2/Task.id=2;B2 attend→minutes:id=3/task=3。尚未开始时先完成两意图,然后只推进这两条合成会议时间,两者获得synthetic example.invalid入会链接。取消参会后minutes保留同id/task,B1无入会权且不能在开始后恢复参会。三个免费Task全部ongoing、contract=null、actual/total_price=0,数目3,无重复计费。 D为合成1h/no-contract,业务拒绝NO_ACTIVE_CONTRACT,仍只有3Task。PG outbox只有1条;BFF真实重启后同条outbox accepted/attempts=1,mock13001接收1次,实际员工邮件0。accepted只表示本地mock HTTP接受,不表示投递;拒绝不能变成成功预约,D不在myMinutes。 ## 资源与手册 Backend PID53599、BFF PID53601/53625均SIGTERM停止;明确QA容器ndb-qa-minutes-http-20261007-56488停止,四端口lsof无监听。tmpfs丢失仅本轮合成数据,无现有数据删除;保留日志。未改业务源码/共享Mini产物,未commit/push/deploy。 UI+Mini已复核手册3图只读范围。HTTP成功不新增“成功预约”UI截图;原生暂不登录、真实微信/手机号认证和完整成功手册仍未覆盖。此前单元/模拟322项属于旧BFF候选;本轮BFF全量独立127/127,最新跨端单元/模拟汇总331项330Pass/1旧Mini样式Fail;HTTP34单列不混算。