|
20275
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/do-not-merge/gate-negative-proof
|
0
|
|
1785507509
|
Edit
Delete
|
|
20276
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/do-not-merge/gate-negative-proof
|
0
|
{"Commits":[],"HeadCommit":{"S {"Commits":[],"HeadCommit":{"Sha1":"ec4bf7fdec43629ca034c3b16fa6fd8b3b456286","Message":"test(negative): 受控失败——删除一个测试文件以验证远端门禁真会阻断\n\nG14 关闭条件的最后一项:证明违规确实被 CI + 分支保护阻断,而不是\n「门禁存在但从没拦住过任何东西」(假绿五形态第 5 形态:假生效门禁)。\n\n刻意注入的违规:删除 apps/api-fastify/test/outbox.deadletter.test.ts。\n预期 ownTests 从 11 掉到 10,低于 reports/baseline.json 的地板 11,\nstatic job 的 check:governance 棘轮应判红;runtime job 因 needs 依赖不会启动。\n\n预期结果:PR 无法合并(required checks: Static governance +\nRuntime and UI acceptance,enforce_admins=true)。\n验证完毕后本 PR 直接关闭,绝不合并。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-07-31T06:49:50-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/main...ec4bf7fdec43629ca034c3b16fa6fd8b3b456286","Len":0}...
|
1785507509
|
Edit
Delete
|
|
20277
|
5
|
17
|
5
|
72
|
0
|
0
|
refs/heads/chore/gate-negative-proof
|
0
|
|
1785507542
|
Edit
Delete
|
|
20278
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/chore/governance-ghost-assets-and-readm refs/heads/chore/governance-ghost-assets-and-readmodel...
|
0
|
|
1785568903
|
Edit
Delete
|
|
20279
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/chore/governance-ghost-assets-and-readm refs/heads/chore/governance-ghost-assets-and-readmodel...
|
0
|
{"Commits":[{"Sha1":"8fe17cd57 {"Commits":[{"Sha1":"8fe17cd57002bf1ed691e63a80c71db5c03d0f5f","Message":"feat(solutions): 项目名称默认取客户名称 + 经营目标/目标客群/硬性约束标签化\n\n本轮直接改动(客户方案域):\n\n- 建项表单选定客户后默认预填客户名称,且**只覆盖上一次自动填入的值**——用户手改过\n 就彻底交还给他,换客户也不再动它。仍是纯 UI 预填:name 在 contracts 里照旧 min(1)\n 必填,后端不替任何调用方脑补名称。\n- 经营目标、目标客群、硬性约束统一改为标签(chips)录入,候选从本租户已有方案现算、\n 按频次排序,不建标签字典表(用户裁决:只做到「表单内标签输入 + 历史候选」这一层;\n 租户级标签库是独立主数据对象,要走 owner-matrix 登记与完整模块流程)。\n- 标签归一(去空白 → 拒空串 → 去重保序)提为 contracts 单源 tagListSchema,读取侧\n 归一为 normalizeTagList。只在前端 chips 去重挡不住 curl([\"A\",\"A \"] 就能撑破上限、\n 读模型里并排出现两个肉眼一样的标签,C26 同一条教训),两后端各补一条读回断言,\n 证明规则真在服务端生效。\n- targetSegment 由单值 VARCHAR(500) 迁为 Json 标签数组,迁移\n 20260731090000_solution_target_segment_tags(两后端同份):就地\n ALTER ... TYPE ... USING,空串→[]、非空单值→单元素数组;**刻意不按分隔符拆**——\n 存量是自由文本,猜标签边界会造出谁也没确认过的半句话标签。转换语义已在真实\n PostgreSQL 上用 legacy 值单独验证。\n- 发布闸门判空由 .trim() 改为经 normalizeTagList 后 length===0,并同步\n check:role-permissions 的绊网正则;normalizeTagList 同时兜住迁移前写下的冻结快照\n (公开页读的就是快照,按定义不可回填)。\n- 定位确认凭证比对标签改用集合语义 sameTagSet:纯拖动排序不改变复核人确认过的那组\n 客群,按数组逐位比对会把已确认误判成 POSITIONING_STALE,逼销售重走复核。\n- E2E 首跑抓到一个真问题:标签成签会让输入框长高一行、提交按钮随之下移,mousedown\n 与 mouseup 落到不同元素上,那一下点击被浏览器吞掉。测试改为显式 Tab 失焦,并保留\n 「只失焦不回车也不丢标签」这条断言(失焦即成签,避免静默丢数据)。\n\n⚠️ 共享工作区提交:提交时本仓有并行会话在途(收款域、报价对客策略与发送对话框、\n操作手册生成、队列隔离与设计令牌门禁、商品目录导入等),其改动一并进入本提交。\n无法按文件拆分——packages/contracts/src/solution.ts 同时含两边内容。\n\n验收(作用域=本地工作区,提交时 dirty,不得外推为远端已验证):\n- pnpm check 全绿(14 个静态门禁 + lint + typecheck)\n- pnpm check:runtime 267 用例 / 地板 265,{contracts:187, api-nestjs:40, api-fastify:40}\n- pnpm check:ui 8/8 真浏览器用例\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T00:21:22-07:00"},{"Sha1":"a79aa437569cad73ecee4ede960c6f80a88bf98d","Message":"docs: 记录 G24 分页形态裁决(方案①:列表分页 + 独立 options 接口)并说明本轮未落地的原因\n\n用户已裁决方案①,设计细节(Paginated/OptionList 契约形状、3 个 options 接口的字段\n及其反推依据)一并写入 G24,下一轮直接落地,不必重新讨论。\n\n本轮未落地的原因写明:这是会改变 4 个接口响应形状的跨层迁移,而同一 checkout 上\n有并行会话正在开发收款域——改到一半的跨层 API 迁移留在共享工作区会直接卡住对方,\n因此整体回退、只留裁决结论。\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-07-31T08:15:46-07:00"},{"Sha1":"cafbd90f352d9fae794c4cae88c8435d84ebf132","Message":"feat(overview): 关闭 C27——把总览 KPI 从浏览器搬到服务端聚合 owner\n\n问题不在今天,在加分页那天\n总览 6 个指标全部在 MobileOverview.tsx 里对 GET /api/quotations 的全量返回 reduce,\n且「什么算已提交/已通过/已确认」「主报价金额怎么取」只以三个本地 Set + 一个函数的形式\n存在于这一个前端组件里。当前列表无分页,所以指标确实是对的——但它的正确性依赖\n一个没人承诺过的前提。只要给列表加分页或后端加截断,指标会静默退化成「当前页的统计」,\n页面还印着「全部来自当前租户」,没有任何测试会红。\n\n修法:把「谁决定聚合范围」搬回拥有数据的那一层\n- contracts 出单源:QUOTATION_{SUBMITTED,APPROVED,CONFIRMED,WAITING_CUSTOMER}_STATES\n + summarizeQuotations + countQuotationStates\n- 两后端 GET /api/quotations/summary 只做窄投影取行,算账一律调那个纯函数;\n 采购侧 GET /api/fulfillment/purchase-orders/summary 用 groupBy 让数据库出计数\n- 前端只做展示与单位换算(分→元、小时→天)\n\n顺带买到 G17 的一小块:聚合逻辑放 contracts 而非两后端各写一遍,\n双后端行为对等在这条路径上是结构保证,不需要门禁,因为不存在两份实现。\n\n同轮下线两处假值\n- 写死的「你好,本地报价管理员」→ 按 contracts 角色显示,不编造用户姓名\n- 最近报价表每行恒为「当前团队」的「负责人」列 → 整列移除(Quotation 无归属人字段),\n 口径同「开票收款尚未建模」:宁可不显示,也不用假值占位;同步收窄 grid 列数\n\n证据\n- pnpm check exit=0\n- pnpm check:runtime:本轮净贡献 87 用例(地板 83→87、ownTestCases 93→95)\n 两后端各 1 条聚合验收:空集均值 0 而非 NaN/byState 覆盖状态全集/跨租户不参与/403 负例\n- pnpm check:ui:8/8 全绿\n\n未闭环并显式登记为 G24:列表本身仍无分页。分页形态是真岔路——这 4 个列表同时喂着\n展示列表和 15 个关联对象下拉,天真分页会让 C22 的「关联对象来自真实读模型选择」\n退化成「只能选到第一页」。三个候选方案已写入 G24,待裁决。\n\n注:本仓有并行会话正在开发收款域。本次提交按文件路径逐个对账,\nquotation.ts 用补丁级 hunk 过滤剔除了对方的 ROLE_PERMISSIONS 改动;\nreports/*.latest.json 因计数被对方未提交的测试污染,本轮不提交,只提交经独立核算的 baseline.json。\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-07-31T08:06:50-07:00"},{"Sha1":"ec76984fa40261041bf89feeb5d64680a77ad577","Message":"feat(products,customers): 补编辑入口并关闭 C26——partial 更新 schema 丢掉的价格不变量\n\n审计口径「后端有写接口、前端无入口」:PATCH /api/products/:id 与 /api/customers/:id 在\n两后端都已实现且写链治理齐全(权限 + 租户条件写 + 0 行 404 + outbox 同 tx),\n但前端全仓只有 solutions 用过 PATCH——商品和客户建完就再也改不了。\n\n动工前核契约,先发现一个必须先补的洞(C26)\n- 价格不变量「底价/成本价 ≤ 标准价」只写在 createProductSchema 的 superRefine 里;\n updateProductSchema = productBaseSchema.partial() 从 base 派生,PATCH 完全绕过它。\n- 长期不可达只因前端没有编辑入口——补 UI 会把它立刻变成可达。\n- 修法不是给 update schema 也加个 refine:PATCH 可能只带 minimumPriceCents,\n 单看请求体判不出来。拆两半:contracts 提单源 checkProductPriceInvariant;\n update schema 只判同一请求内同时出现的字段,两后端在 tx 内合并现有记录后调同一函数兜底。\n\n前端\n- products/customers 各补 update API + mutation hook + 内联编辑器。\n- 编辑表单刻意用 createProductSchema 校验(它带不变量),让前后端拦同一件事。\n- SKU 与商品类型不开放编辑:已被历史报价明细固化为不可变快照,事后改会让旧报价口径漂移。\n- useUpdateCustomer 一并失效 quotations 缓存:客户名是报价抬头来源,否则报价列表继续显示旧抬头。\n\n证据\n- pnpm check exit=0\n- pnpm check:runtime:85 用例(31/27/27),地板同步 83→85、ownTestCases 91→93\n 新增两后端各 1 条 HTTP 负例:单字段 PATCH→400、双字段 PATCH→400、失败后读回未变、合法 PATCH 写库\n- pnpm check:ui:8/8 全绿,新增编辑闭环与不变量提示断言\n\n过程中自伤两次并已修正(记入 C26)\n- 编辑用例改了共享商品 PRODUCT_NAME → 下游 3 条用例全挂;改用专用 EDIT_SKU 商品并在常量处写明禁令\n- 新增商品让报价下拉选项从 3 变 4 → 更新计数并注明构成,保留其「挡重复项/挡漏建」的作用\n- 顺带把 productLibrary.locator(\"form\") 收窄为 getByTestId(\"product-create-form\")(C16 同款规则)\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-07-31T07:48:35-07:00"},{"Sha1":"aed0147384ea12244797a17c46bfe98c4cdeb0a2","Message":"fix(governance): 关闭 C25——删除幽灵 UI 资产并给「文档点名的代码符号」上门禁\n\n审计「每个菜单的真实对接」时发现 OrdersPanel/UsersPanel 在整个 apps/web/src 里\n没有任何 import,且是 9 个 API helper 的唯一消费者——一整棵约 560 行的死子树。\n\n真正的问题不是死代码,是三份治理文档仍拿它们当活资产:\n- owner-matrix 把 User/Order/OrderEvent 的读模型消费方写成这两个面板(追不到可达 UI);\n- CLAUDE.md C16 宣称的防线 getByTestId(\"users-panel\") 在 E2E 里早已不存在;\n- CLAUDE.md 的「apps/web 三面板样板」指向的布局已被 18 入口工作台取代。\n既有门禁交集恰好为空:typecheck 不报未使用导出;check:docs-truth 的幽灵资产断言\n管的是「文档给示例但文件不存在」(这里正相反);check:module-integration 只扫菜单内条目。\n\n处置(用户裁决:删除而非挂回 ADMIN 入口)\n- 删 OrdersPanel/UsersPanel + 9 个仅供其消费的 helper + 0 引用且注释失真的 api 泛型对象\n + 0 引用的 USD 版 formatCentsToCurrency;\n- /api/users、/api/orders、/api/jobs 后端与双后端 HTTP/DB 测试一律不动,只是不再有 UI 路径;\n- owner-matrix 三行改「无 UI 消费方」并加登记规则 5;C16 行改「规则仍生效、原实例已下线」;\n 「三面板样板」更正为现网 shell 结构;quotation-blueprint 两处同步;经验库补 C25 完整叙事。\n\n新增/重锚两处门禁(均已负向测试)\n- check:module-integration 加 owner-matrix-consumer-*:文档点名的组件必须在 apps/web 真实可达。\n 负向证据:真实磁盘造一个无人 import 的 ZombiePanel 并被点名 → 红;\n 摘掉 EventFeed 唯一 import → 红(证明正向路径不是空跑,Outbox 行确实在被断言)。\n- check:contract-consumers 的前端半边原本三条断言全锚在 OrdersPanel 上——即长期在守一个\n 没人走的路口。重锚到报价/商品/履约三个真实工作台,并从「好模式存在」改为「坏模式不存在」:\n 原断言局部改坏 1/3 处不会红(实测),新 reject 改坏一处即红(实测)。\n\n证据:pnpm check exit=0(本地工作区,7f1be32 基线同样 exit=0)\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-07-31T07:32:18-07:00"}],"HeadCommit":{"Sha1":"8fe17cd57002bf1ed691e63a80c71db5c03d0f5f","Message":"feat(solutions): 项目名称默认取客户名称 + 经营目标/目标客群/硬性约束标签化\n\n本轮直接改动(客户方案域):\n\n- 建项表单选定客户后默认预填客户名称,且**只覆盖上一次自动填入的值**——用户手改过\n 就彻底交还给他,换客户也不再动它。仍是纯 UI 预填:name 在 contracts 里照旧 min(1)\n 必填,后端不替任何调用方脑补名称。\n- 经营目标、目标客群、硬性约束统一改为标签(chips)录入,候选从本租户已有方案现算、\n 按频次排序,不建标签字典表(用户裁决:只做到「表单内标签输入 + 历史候选」这一层;\n 租户级标签库是独立主数据对象,要走 owner-matrix 登记与完整模块流程)。\n- 标签归一(去空白 → 拒空串 → 去重保序)提为 contracts 单源 tagListSchema,读取侧\n 归一为 normalizeTagList。只在前端 chips 去重挡不住 curl([\"A\",\"A \"] 就能撑破上限、\n 读模型里并排出现两个肉眼一样的标签,C26 同一条教训),两后端各补一条读回断言,\n 证明规则真在服务端生效。\n- targetSegment 由单值 VARCHAR(500) 迁为 Json 标签数组,迁移\n 20260731090000_solution_target_segment_tags(两后端同份):就地\n ALTER ... TYPE ... USING,空串→[]、非空单值→单元素数组;**刻意不按分隔符拆**——\n 存量是自由文本,猜标签边界会造出谁也没确认过的半句话标签。转换语义已在真实\n PostgreSQL 上用 legacy 值单独验证。\n- 发布闸门判空由 .trim() 改为经 normalizeTagList 后 length===0,并同步\n check:role-permissions 的绊网正则;normalizeTagList 同时兜住迁移前写下的冻结快照\n (公开页读的就是快照,按定义不可回填)。\n- 定位确认凭证比对标签改用集合语义 sameTagSet:纯拖动排序不改变复核人确认过的那组\n 客群,按数组逐位比对会把已确认误判成 POSITIONING_STALE,逼销售重走复核。\n- E2E 首跑抓到一个真问题:标签成签会让输入框长高一行、提交按钮随之下移,mousedown\n 与 mouseup 落到不同元素上,那一下点击被浏览器吞掉。测试改为显式 Tab 失焦,并保留\n 「只失焦不回车也不丢标签」这条断言(失焦即成签,避免静默丢数据)。\n\n⚠️ 共享工作区提交:提交时本仓有并行会话在途(收款域、报价对客策略与发送对话框、\n操作手册生成、队列隔离与设计令牌门禁、商品目录导入等),其改动一并进入本提交。\n无法按文件拆分——packages/contracts/src/solution.ts 同时含两边内容。\n\n验收(作用域=本地工作区,提交时 dirty,不得外推为远端已验证):\n- pnpm check 全绿(14 个静态门禁 + lint + typecheck)\n- pnpm check:runtime 267 用例 / 地板 265,{contracts:187, api-nestjs:40, api-fastify:40}\n- pnpm check:ui 8/8 真浏览器用例\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T00:21:22-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/9618d78da3e668f14e3c1611d7090f45eef0c085...8fe17cd57002bf1ed691e63a80c71db5c03d0f5f","Len":7}...
|
1785568903
|
Edit
Delete
|
|
20280
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/g24-list-pagination-and-options
|
0
|
|
1785589103
|
Edit
Delete
|
|
20281
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/g24-list-pagination-and-options
|
0
|
{"Commits":[{"Sha1":"ccbb031fd {"Commits":[{"Sha1":"ccbb031fdb47bc913ed2b4ba270c0a49889eb88d","Message":"feat(g24): 列表分页 + 选择器 options 双读路径(contracts 单源/双后端对等/三层负向证据)\n\n- contracts 新增 pagination.ts 单源:Paginated\u003cT\u003e{items,page,pageSize,total}(total=租户全量)、\n paginationQuerySchema(默认 20/上限 100,越界 Zod 400 不静默 clamp)、toPrismaPage/makePaginated、\n OptionList\u003cT\u003e{items,truncated} + OPTION_LIST_LIMIT=200 + makeOptionList,\n 三个 Option 投影重建对象字面量做字段白名单(成本/底价结构性缺席;按选择器反推补 versionNo/taxRateBps)\n- 两后端对等:quotations/products/customers/sales-orders 四列表改 Paginated 信封;\n 新增 /products/options(只返在售)、/customers/options(联系人 id/name/mobile)、\n /fulfillment/contract-requests/options(只返 REQUESTED);权限沿用列表同一 capability\n- 前端真实对接:9 个选择器站点切 options hook(key 挂既有根继承实时失效,\n sales_order.* 额外失效合同 options);合同激活手填 UUID 换真实下拉;\n page.tsx contracts/products prop 下钻退役;4 列表接分页条;truncated 显式提示\n- 对抗性测试三层负向均实跑判红后恢复:contracts 投影透传红 / module-integration\n degrade-selector-to-paginated-list 注入红 / E2E 选择器退化红(第 9 条浏览器用例);\n 顺带发现并修复 drop-fulfillment-realtime 注入被 payment 分支同字面量代码静默拔牙(replace→replaceAll)\n- 棘轮抬地板:testsPassed 265→279、uiTestsPassed 8→9、ownTests 17→18、ownTestCases 194→209\n- 回灌:CLAUDE.md 真源地图+动态区(G24 移入已关闭)、governance-experience 战役记录、\n owner-matrix 登记三个 options 读模型、api-standard 分页章节按裁决修正\n\n证据(本地工作区,2026-08-01):pnpm check exit 0;check:runtime 279/0(PG:55432/juhai_quotation_g24_20260801);\ncheck:ui 9/0(juhai_quotation_ui_g24_20260801)\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T05:57:57-07:00"}],"HeadCommit":{"Sha1":"ccbb031fdb47bc913ed2b4ba270c0a49889eb88d","Message":"feat(g24): 列表分页 + 选择器 options 双读路径(contracts 单源/双后端对等/三层负向证据)\n\n- contracts 新增 pagination.ts 单源:Paginated\u003cT\u003e{items,page,pageSize,total}(total=租户全量)、\n paginationQuerySchema(默认 20/上限 100,越界 Zod 400 不静默 clamp)、toPrismaPage/makePaginated、\n OptionList\u003cT\u003e{items,truncated} + OPTION_LIST_LIMIT=200 + makeOptionList,\n 三个 Option 投影重建对象字面量做字段白名单(成本/底价结构性缺席;按选择器反推补 versionNo/taxRateBps)\n- 两后端对等:quotations/products/customers/sales-orders 四列表改 Paginated 信封;\n 新增 /products/options(只返在售)、/customers/options(联系人 id/name/mobile)、\n /fulfillment/contract-requests/options(只返 REQUESTED);权限沿用列表同一 capability\n- 前端真实对接:9 个选择器站点切 options hook(key 挂既有根继承实时失效,\n sales_order.* 额外失效合同 options);合同激活手填 UUID 换真实下拉;\n page.tsx contracts/products prop 下钻退役;4 列表接分页条;truncated 显式提示\n- 对抗性测试三层负向均实跑判红后恢复:contracts 投影透传红 / module-integration\n degrade-selector-to-paginated-list 注入红 / E2E 选择器退化红(第 9 条浏览器用例);\n 顺带发现并修复 drop-fulfillment-realtime 注入被 payment 分支同字面量代码静默拔牙(replace→replaceAll)\n- 棘轮抬地板:testsPassed 265→279、uiTestsPassed 8→9、ownTests 17→18、ownTestCases 194→209\n- 回灌:CLAUDE.md 真源地图+动态区(G24 移入已关闭)、governance-experience 战役记录、\n owner-matrix 登记三个 options 读模型、api-standard 分页章节按裁决修正\n\n证据(本地工作区,2026-08-01):pnpm check exit 0;check:runtime 279/0(PG:55432/juhai_quotation_g24_20260801);\ncheck:ui 9/0(juhai_quotation_ui_g24_20260801)\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T05:57:57-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/8fe17cd57002bf1ed691e63a80c71db5c03d0f5f...ccbb031fdb47bc913ed2b4ba270c0a49889eb88d","Len":1}...
|
1785589103
|
Edit
Delete
|
|
20282
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/solution-tier-multi-product
|
0
|
|
1785590611
|
Edit
Delete
|
|
20283
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/solution-tier-multi-product
|
0
|
{"Commits":[{"Sha1":"815f9b634 {"Commits":[{"Sha1":"815f9b634be98eeea1ead07bc5daaf37dec909e0","Message":"feat(solution): 三档方案支持每档多商品,数量默认取项目包房总数\n\n## 多商品\n契约 `solutionOptionInputSchema.products` 一直是 `.min(1).max(500)`,后端逐条落\nSolutionOptionProduct、转报价桥接逐条转成报价明细、客户页也已在显示「配置 N 项」——\n整条链路早就支持多商品,**只有设计器表单把它写死成单元素数组**,能力在用户唯一\n够得着的那一层被锁掉(同 C25/C26 族:能力挂在哪一层决定它存不存在)。\n\n改法复用仓内既有 AccessoryEditor 模式:选择 + 数量 → 添加成可删除的清单行。\n刻意不用 `\u003cselect multiple\u003e`:每个商品要带自己的数量,多选框表达不了;且 Select\n原语的注释明确禁止把它改造成多选(E2E 按 locator(\"select\") 定位、Capacitor 依赖\n原生选择器)。同档重复添加同一商品累加数量,不并排出现两行肉眼一样的配置。\n\n## 数量默认值\n默认 = 本项目包房总数(kind===\"ROOM\" 的空间数量求和),只预填不锁定。\n回落 1 而非 0:契约 quantity 是 int().positive(),预填 0 会让按钮看着能点、\n提交却被 Zod 挡下(C24 同款:默认值不合法 = 表单永远提交不了)。\nUI 显式写明「数量默认按本项目包房总数 N 间预填,可逐项调整」,不做魔法数字。\n\n## 顺带修掉一个跨项目状态串味\nSolutionDesigner 的已选商品与包房数预填都是挂载时求值的本地状态,\n渲染处此前无 key——切换项目不重挂,会把上一个项目的选择带过去。已加 key={project.id}。\n\n## 证据(本地工作区 2026-08-01)\n- pnpm check exit 0;check:runtime 279/0;check:ui 9/0\n- 端到端读回:推荐档配 2 个商品 → 客户页断言「配置 2 项」→ 成功转成正式报价\n- 负向测试两条均实跑判红后恢复:\n ① addProduct 改为 `products: [line]`(回潮到单商品)→ 恰好 solution-product-row\n 期望 2 实得 1 判红;\n ② roomTotal 改为常量 1 → 恰好 待添加数量 期望 \"8\" 实得 \"1\" 判红\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T06:23:24-07:00"}],"HeadCommit":{"Sha1":"815f9b634be98eeea1ead07bc5daaf37dec909e0","Message":"feat(solution): 三档方案支持每档多商品,数量默认取项目包房总数\n\n## 多商品\n契约 `solutionOptionInputSchema.products` 一直是 `.min(1).max(500)`,后端逐条落\nSolutionOptionProduct、转报价桥接逐条转成报价明细、客户页也已在显示「配置 N 项」——\n整条链路早就支持多商品,**只有设计器表单把它写死成单元素数组**,能力在用户唯一\n够得着的那一层被锁掉(同 C25/C26 族:能力挂在哪一层决定它存不存在)。\n\n改法复用仓内既有 AccessoryEditor 模式:选择 + 数量 → 添加成可删除的清单行。\n刻意不用 `\u003cselect multiple\u003e`:每个商品要带自己的数量,多选框表达不了;且 Select\n原语的注释明确禁止把它改造成多选(E2E 按 locator(\"select\") 定位、Capacitor 依赖\n原生选择器)。同档重复添加同一商品累加数量,不并排出现两行肉眼一样的配置。\n\n## 数量默认值\n默认 = 本项目包房总数(kind===\"ROOM\" 的空间数量求和),只预填不锁定。\n回落 1 而非 0:契约 quantity 是 int().positive(),预填 0 会让按钮看着能点、\n提交却被 Zod 挡下(C24 同款:默认值不合法 = 表单永远提交不了)。\nUI 显式写明「数量默认按本项目包房总数 N 间预填,可逐项调整」,不做魔法数字。\n\n## 顺带修掉一个跨项目状态串味\nSolutionDesigner 的已选商品与包房数预填都是挂载时求值的本地状态,\n渲染处此前无 key——切换项目不重挂,会把上一个项目的选择带过去。已加 key={project.id}。\n\n## 证据(本地工作区 2026-08-01)\n- pnpm check exit 0;check:runtime 279/0;check:ui 9/0\n- 端到端读回:推荐档配 2 个商品 → 客户页断言「配置 2 项」→ 成功转成正式报价\n- 负向测试两条均实跑判红后恢复:\n ① addProduct 改为 `products: [line]`(回潮到单商品)→ 恰好 solution-product-row\n 期望 2 实得 1 判红;\n ② roomTotal 改为常量 1 → 恰好 待添加数量 期望 \"8\" 实得 \"1\" 判红\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T06:23:24-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/ccbb031fdb47bc913ed2b4ba270c0a49889eb88d...815f9b634be98eeea1ead07bc5daaf37dec909e0","Len":1}...
|
1785590611
|
Edit
Delete
|
|
20284
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/solution-tier-multi-product
|
0
|
{"Commits":[{"Sha1":"b0ed5cba7 {"Commits":[{"Sha1":"b0ed5cba764d5df24360ea70486bad9b6885431d","Message":"feat(solution): 数量来源可选空间分类或手动输入,选品增加商品分类筛选\n\n## 数量来源可选\n此前默认值写死「包房总数」。但走廊音响、前台设备、设备间机柜这类根本不按包房走,\n把包房硬编码成唯一口径等于只服务一种配置习惯。\n\n改为下拉可选:**按任一空间分类的数量合计**(包房/前台/走廊/水吧/办公室/设备间/其他,\n选项上直接标出该类当前数量)或**手动输入**。默认仍落在包房——点歌设备按包房逐间\n配是这门生意的常识配比。\n\n- 按空间分类走时数量输入框只读:避免出现「显示按包房、值却被手改过」这种谁也说不清\n 依据的数字;要自己填就显式切到「手动输入」。\n- 取值一律 Math.max(1, …):契约 quantity 是 int().positive(),项目里没录前台时\n 合计为 0,预填 0 会让「添加」看着能点、提交却被 Zod 挡下(C24 同款教训)。\n- 空间分类总数用枚举 reduce 统一算,不给某一类空间写死分支。\n\n## 选品增加商品分类\n选品下拉前置「全部分类 / 设备 / 服务 / 配件」筛选(沿用商品库同名口径,不另造说法)。\n切换分类时清空已选商品——否则下拉显示的是新分类、value 仍指向旧分类那件商品,\n提交的是用户此刻根本看不见的东西。\n\n添加一件后**保留分类与来源**、只清商品:连着配同一类设备是常态,每加一件就把筛选\n重置回默认等于逼人重选一遍。\n\n来源与分类只是挑选时的辅助,不进已选行——存进去会让配置多出两个读模型不消费、\n契约也不收的字段。\n\n## 证据(本地工作区 2026-08-01)\n- pnpm check exit 0;check:runtime 279/0;check:ui 9/0\n- E2E 新增断言:切「配件」后候选从 4 项收窄到 2 项(证明筛选真的干活);\n 切「手动输入」填 3 后该行数量为 3,而主设备那行仍是按包房总数的 8\n (证明两种来源可在同一档并存)\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T06:40:01-07:00"},{"Sha1":"18359985bc7144f7c67c3f17d80e7f848770d956","Message":"fix(ci): 让 UI 验收红盘可判责——失败时打印捕获输出并上传证据产物\n\nPR #6 的 Runtime and UI acceptance 在远端判红,但日志里只有一行\n「✗ playwright-test failed (exit 1)」,看不出是哪条断言、为什么失败。\n\n根因:check-ui-acceptance.mjs 的 runSync 把子进程 stdout/stderr 收进 report JSON\n的 stdoutTail/stderrTail 后**从不打印**,而 workflow 又不上传 report——\n证据在当场就被丢掉了。G23 ④ 记的「2026-07-31 红盘 tests-floor got 0 根因至今\n未定位」是同一个病根。\n\n- runSync 失败时把两条尾巴打到 stderr\n- workflow 失败时上传 reports/*.latest.json + e2e-results.json + playwright traces\n\n本机以全新库复跑 check:ui 为 9/9 全绿,无法复现远端红盘;按治理纪律\n「根因未定位不得归因」,先补证据链再判是偶发还是回归。\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T06:33:17-07:00"}],"HeadCommit":{"Sha1":"b0ed5cba764d5df24360ea70486bad9b6885431d","Message":"feat(solution): 数量来源可选空间分类或手动输入,选品增加商品分类筛选\n\n## 数量来源可选\n此前默认值写死「包房总数」。但走廊音响、前台设备、设备间机柜这类根本不按包房走,\n把包房硬编码成唯一口径等于只服务一种配置习惯。\n\n改为下拉可选:**按任一空间分类的数量合计**(包房/前台/走廊/水吧/办公室/设备间/其他,\n选项上直接标出该类当前数量)或**手动输入**。默认仍落在包房——点歌设备按包房逐间\n配是这门生意的常识配比。\n\n- 按空间分类走时数量输入框只读:避免出现「显示按包房、值却被手改过」这种谁也说不清\n 依据的数字;要自己填就显式切到「手动输入」。\n- 取值一律 Math.max(1, …):契约 quantity 是 int().positive(),项目里没录前台时\n 合计为 0,预填 0 会让「添加」看着能点、提交却被 Zod 挡下(C24 同款教训)。\n- 空间分类总数用枚举 reduce 统一算,不给某一类空间写死分支。\n\n## 选品增加商品分类\n选品下拉前置「全部分类 / 设备 / 服务 / 配件」筛选(沿用商品库同名口径,不另造说法)。\n切换分类时清空已选商品——否则下拉显示的是新分类、value 仍指向旧分类那件商品,\n提交的是用户此刻根本看不见的东西。\n\n添加一件后**保留分类与来源**、只清商品:连着配同一类设备是常态,每加一件就把筛选\n重置回默认等于逼人重选一遍。\n\n来源与分类只是挑选时的辅助,不进已选行——存进去会让配置多出两个读模型不消费、\n契约也不收的字段。\n\n## 证据(本地工作区 2026-08-01)\n- pnpm check exit 0;check:runtime 279/0;check:ui 9/0\n- E2E 新增断言:切「配件」后候选从 4 项收窄到 2 项(证明筛选真的干活);\n 切「手动输入」填 3 后该行数量为 3,而主设备那行仍是按包房总数的 8\n (证明两种来源可在同一档并存)\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T06:40:01-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/815f9b634be98eeea1ead07bc5daaf37dec909e0...b0ed5cba764d5df24360ea70486bad9b6885431d","Len":2}...
|
1785591608
|
Edit
Delete
|
|
20285
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/solution-tier-multi-product
|
0
|
{"Commits":[{"Sha1":"47ce4c614 {"Commits":[{"Sha1":"47ce4c6146a13c443db38c5816749d0591339e46","Message":"fix(quotations): 视图筛选下沉服务端 + 公开方案页不再泄漏后端英文报错\n\n## 1. 三个筛选视图的分页回归(G24 引入,本次修复)\n审核中心 / 客户报价页 / 报价版本 原先在浏览器里对列表结果 filter。列表全量返回时\n那样是对的,但 G24 加分页后客户端筛选只作用于**当前页**:待审报价不在最新一页\n就会被静默漏掉,而分页条的 total 仍是全量数,出现「共 N 条却一行不显示」的错位。\n与 C27「聚合范围由前端恰好拿到多少行决定」同族——筛选范围必须由拥有数据的一层决定。\n\n- contracts 新增 QUOTATION_VIEW_FILTERS 单源(inbox/review/customer/versions),\n 两后端各自翻译成 Prisma where;total 随之变成「该视图下的全量」;\n- quotationListViewSchema 刻意不 extend paginationQuerySchema——那会让\n quotation.ts 反向 import pagination.ts 的值,与既有 type import 形成循环;\n- 非法 view 是 400 而不是静默退回全部(静默兜底会让筛选看着生效实则没有);\n- 前端只保留一条**权限谓词**(无 customer_link.read 读不到 accessToken):\n 它对同一角色恒为全真或全假,不会像数据谓词那样把行留在分页窗口外。\n\n⚠️ 期间发现 QuoteOption.accessToken 是 @default(uuid()) **非空**,\n原「客户报价页」的 options.some(accessToken) 实际是权限谓词而非数据谓词。\n\n## 2. 公开方案页把后端英文报错渲染给客户\nPublicCustomerSolutionView 直接渲染 query.error.message,客户看到的是\n「Solution project not found」——看不懂、像坏了,还把内部对象名暴露出去。\n改为统一中文文案,且**不区分**链接无效/未发布/返工中:区分开等于告诉持链接的人\n「方案确实存在,只是没轮到你看」,而服务端刻意用 404 而非 403 正是为了不泄露存在性\n(方案蓝图 S1 审计裁决)。前端说破,那道防线就白立了。\n\n## 门禁断言随执行点搬家(不是放宽)\nquotation-review-server-state / quotation-version-snapshot 钉的是被移走的客户端谓词。\n不变量没变、执行点变了,故改为钉:前端必须把 mode 作为 view 传下去、\n两后端必须从 contracts 单源翻译 where,并加 reject 断言禁止客户端重新实现视图谓词。\n\n## 证据(本地工作区 2026-08-01)\n- pnpm check 0 / check:runtime 294 / check:ui 9\n- 两后端各 +3 条断言(view=review 只回待审、total 随筛选收窄、非法 view 400)\n- 负向两条实跑判红后恢复:\n ① 服务端忽略 view → view=review 期望 1 实得 2 判红\n ② 视图谓词写回客户端 → quotation-view-client-side-filter 判红\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T08:35:11-07:00"},{"Sha1":"b0ba9576541405a0613ae827ad3c80873437ef53","Message":"docs(flow): 重新梳理业务模块与主干流程,补齐审批中心并画 mermaid 流程图\n\nbusiness-flow-overview.md 写于同一天更早的提交,正文里一次都没提 approval-inbox\n(468a6cd 才落地的 9 类聚合)。本次按 contracts 与前端导航逐条核对后重梳理:\n\n- 新增 §6 待办审批中心:明确它是**横切在主干之上的视图,不是主干的一个节点**\n (无写端点、不重定义「什么算待审」、入口不设独立 capability 而挂 event.read),\n 并给出 9 类条目各自落在主干哪一步;第 8/9 类是待办不是审批闸门。\n- 新增 §1.1 业务模块分层,并标注 owner-matrix 里的 inventory/procurement/logistics\n **不是物理模块**:三个域的写链全部实现在 fulfillment.service.ts(~2100 行,\n 两后端各一份),按目录名去搜会扑空。\n- §3 补两条被 happy path 掩盖的主干分支:报价审批是**条件审批**\n (quotationMachine 的 DRAFT 同时接 send,服务端仅在存在 requiresApproval 的\n 方案档时抛 409 拦截);销售订单可先转 PROCUREMENT_REQUIRED,纯服务订单反向跳过发运。\n- §1 主干图 ASCII 换 mermaid(GitHub 可渲染),信息等价。\n\n仍不复制状态机全图——那是 P1 禁止的第二真源,图里只点 contracts 导出符号名。\n未新建流程图文档:本文件已是该事项的登记位置,另开一份即第二真源。\n\n证据:本地工作区 check:docs-truth / check:naming / check:governance-docs /\ncheck:gate-selftest 均绿(静态级,工作区 dirty,不外推远端与运行态)。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T08:08:20-07:00"},{"Sha1":"f315e642beda9d7ccc527955b2397f4702d33a9c","Message":"chore(gate): docs-truth 负向测试固化 + 幽灵资产清单扩面\n\n会话里手工跑一次的红盘不留证据,下轮重构会静默失效(G13)。把 docs-truth 的\n负向测试固化为 scripts/lib/docs-truth.test.mjs,由 check:gate-selftest 重跑,\ngateSelftests 地板 2 → 3。\n\n幽灵资产两条断言扩面:\n- no-phantom-config-prisma 清单补入 docs/README.md 与 naming-standard.md\n- 新增 contracts-package-name,递归扫描 docs/ 全域,真值取自\n packages/contracts/package.json 的 name(非黑名单)\n\n扩清单判据:该文件是在**主张**这条幽灵引用,还是在**否定**它?只有前者进清单——\nADR 澄清段与治理经验库引用它正是为宣告作废,扫进来即咬住纠错本身(C1 同类陷阱)。\n\nCLAUDE.md 会话收尾清单同步回灌两个已实锤两次的坑:注入点是否真落在目标断言上\n(replace 只换第一处),以及红是否红在点子上(崩溃/环境不满足同样非 0 退出)。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T08:08:01-07:00"},{"Sha1":"468a6cd2ab36d76237ae9c4d849eda17204d722c","Message":"feat(approval-inbox): 跨域待办审批中心(切片一)——9 类聚合 + 中心内直接审批\n\n经 business-module-intake 接待流程落地,蓝图见 docs/domain/approval-inbox-blueprint.md。\n\n## 解决的问题\n全系统有 7 道审批闸门,只有报价那一道有「审核中心」入口;技术复核、经理批准、\n采购、盘点、红冲、退款全部藏在各自工作台深处,待办不可发现。\n\n## 设计要害:聚合层不得成为第二真源\n- **不新建聚合写入口**:中心里的审批动作一律打各域原有端点,写入唯一点不变;\n- **不重新定义「什么算待审」**:判据只引用各域自己的状态枚举;\n- **入参 schema 直接引用各域已有 Zod schema**(APPROVAL_PAYLOAD_SCHEMAS),\n 某域改入参→改自己的 schema→中心类型立刻变→编译期红,而不是运行时静默 400/409;\n- 描述表 `satisfies Record\u003cApprovalTaskKind, …\u003e` 锁全集,漏一类即编译不过;\n- capability 裁剪、自批过滤、排序计数统一交给 contracts 纯函数 buildApprovalInbox,\n 两后端共用——行为对等由结构保证(G17)。\n\n## 覆盖 9 类\n7 道审批 + 2 类客户决定后的待办(客户已接受待申请合同、客户已选档待转报价)。\n后两类**不是审批闸门**:客户决定仍立即生效,只是把「决定之后必须有人做的那一步」\n从各自工作台捞出来,零状态机改动、零迁移、零对客语义变更。\n\n## 实测暴露并绕开的坑\n- 盘点列表 capability(inventory.stocktake) ≠ 审批 capability(.approve),\n SALES_MANAGER 只持后者——中心按**审批** capability 判定,否则审批人看不到自己该审的单;\n- 采购员不持 procurement.approve(三责分离),中心里必须继续不可见;\n- 方案两类需回传 versionNo 做 STALE_VERSION 校验,中心从聚合结果带出。\n\n## 诚实边界(蓝图 §7,切片二再补)\n发起人字段只有 2/7 个域存在,自批过滤只在红冲/退款生效,UI 如实标注可判定范围。\n**本模块不得宣称「已保证不可自批」**——只有这两类成立。\n\n## 证据(本地工作区 2026-08-01)\n- pnpm check 0 / check:runtime 294(地板 279→294)/ check:ui 9\n- contracts 13 条单测;两后端各 1 条聚合 HTTP 验收(capability 裁剪/跨租户/自批计数/待办类)\n- check:module-integration 新增 5 条断言,三条负向实跑判红后恢复:\n ① 聚合层加写端点 ② capability 裁剪改全放行 ③ 去掉实时失效\n 其中 ② 首轮未判红——断言只查标识符、留着未使用 import 就能骗过,\n 已收紧到锚定调用点(断言剧场自查)\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T07:45:16-07:00"}],"HeadCommit":{"Sha1":"47ce4c6146a13c443db38c5816749d0591339e46","Message":"fix(quotations): 视图筛选下沉服务端 + 公开方案页不再泄漏后端英文报错\n\n## 1. 三个筛选视图的分页回归(G24 引入,本次修复)\n审核中心 / 客户报价页 / 报价版本 原先在浏览器里对列表结果 filter。列表全量返回时\n那样是对的,但 G24 加分页后客户端筛选只作用于**当前页**:待审报价不在最新一页\n就会被静默漏掉,而分页条的 total 仍是全量数,出现「共 N 条却一行不显示」的错位。\n与 C27「聚合范围由前端恰好拿到多少行决定」同族——筛选范围必须由拥有数据的一层决定。\n\n- contracts 新增 QUOTATION_VIEW_FILTERS 单源(inbox/review/customer/versions),\n 两后端各自翻译成 Prisma where;total 随之变成「该视图下的全量」;\n- quotationListViewSchema 刻意不 extend paginationQuerySchema——那会让\n quotation.ts 反向 import pagination.ts 的值,与既有 type import 形成循环;\n- 非法 view 是 400 而不是静默退回全部(静默兜底会让筛选看着生效实则没有);\n- 前端只保留一条**权限谓词**(无 customer_link.read 读不到 accessToken):\n 它对同一角色恒为全真或全假,不会像数据谓词那样把行留在分页窗口外。\n\n⚠️ 期间发现 QuoteOption.accessToken 是 @default(uuid()) **非空**,\n原「客户报价页」的 options.some(accessToken) 实际是权限谓词而非数据谓词。\n\n## 2. 公开方案页把后端英文报错渲染给客户\nPublicCustomerSolutionView 直接渲染 query.error.message,客户看到的是\n「Solution project not found」——看不懂、像坏了,还把内部对象名暴露出去。\n改为统一中文文案,且**不区分**链接无效/未发布/返工中:区分开等于告诉持链接的人\n「方案确实存在,只是没轮到你看」,而服务端刻意用 404 而非 403 正是为了不泄露存在性\n(方案蓝图 S1 审计裁决)。前端说破,那道防线就白立了。\n\n## 门禁断言随执行点搬家(不是放宽)\nquotation-review-server-state / quotation-version-snapshot 钉的是被移走的客户端谓词。\n不变量没变、执行点变了,故改为钉:前端必须把 mode 作为 view 传下去、\n两后端必须从 contracts 单源翻译 where,并加 reject 断言禁止客户端重新实现视图谓词。\n\n## 证据(本地工作区 2026-08-01)\n- pnpm check 0 / check:runtime 294 / check:ui 9\n- 两后端各 +3 条断言(view=review 只回待审、total 随筛选收窄、非法 view 400)\n- 负向两条实跑判红后恢复:\n ① 服务端忽略 view → view=review 期望 1 实得 2 判红\n ② 视图谓词写回客户端 → quotation-view-client-side-filter 判红\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T08:35:11-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/b0ed5cba764d5df24360ea70486bad9b6885431d...47ce4c6146a13c443db38c5816749d0591339e46","Len":4}...
|
1785598520
|
Edit
Delete
|
|
20286
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/solution-tier-multi-product
|
0
|
{"Commits":[{"Sha1":"fb6c5ad39 {"Commits":[{"Sha1":"fb6c5ad390153c534787dc66d90284ff257d28d1","Message":"docs(governance): 治理入口新增业务主干导航行\n\nCLAUDE.md「使用入口」表加一行,把「搞清业务从头到尾怎么跑 / 新人上手」\n路由到 docs/domain/business-flow-overview.md。\n\n为什么要有这一行:跨域主干导航文档已经落在 docs/domain/ 下,但治理入口表\n不指向它——一份没有入口的资产下轮就没人读、也没人维护,随后静默漂移\n(C25 同族:文档指向的东西运行时到不了,反过来也一样成立)。\n\n该行同时写明它是**派生非真源**、据它下判断前必须回到 contracts / blueprint\n复核,避免入口表把导航文档抬成第二份状态机口径(P1)。\n\nAGENTS.md 为符号链接自动跟随;pnpm check exit 0(含 check:governance-docs)。\n作用域:本地工作区 dirty(并发会话有 approval-inbox 切片二在途),本提交\n只含 CLAUDE.md 一个文件。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T19:58:50-07:00"},{"Sha1":"9e6f5674f9279f43dd8bd6b4acbcfbe5d7af29b6","Message":"test(e2e): 待办审批中心转报价的浏览器级用例——堵住两个 bug 的共同逃逸路径\n\n## 为什么补这一条\n\n审批中心 9 类待办里,「客户已选档·待转报价」是**唯一没被 E2E 打过**的一类。\n两个 bug 正是从这个缺口同时逃逸,且叠在一起:\n1. 前端把端点拼成 /api/solutions/:id/quotation,真实路由是 /create-quotation → 404;\n2. 动作必填 optionId,而 ApprovalTask 投影里没有这个字段 → 400。\n路径修对了也只是把 404 换成 400,这个按钮**从落地起就没成功执行过**。\n\n同轮已补两道静态门禁(check:api-routes / check:approval-payload),但它们各自\n只覆盖一半:前者只校验路径不看请求体,后者只校验入参不看路由。本用例是第三层,\n一次性覆盖两者,且打的是真实浏览器链路。\n\n## 用例设计\n\n前置数据用 API 铺(方案要走完设计→双审核→发布→客户选档才会进待办)——本用例的\n验证目标是**中心内的执行动作**,那条设计链路早已被第 4 条用例覆盖。铺数据序列先在\n真实后端上逐步试通,撞到四个真实约束,均已写进用例:\n- 无 body 的动作要传 {},否则 Fastify 回 FST_ERR_CTP_EMPTY_JSON_BODY;\n- design 必须三档齐全(经济/推荐/旗舰);\n- targetSegment 必须**建时就给**:APPROVED 后禁改输入,而对客发布闸门要求它非空;\n- 客户联系人字段是 primaryContact;档案无联系人时选档一律拒绝(审计 S3 fail-closed)。\n\n断言锚到 [data-testid=\"approval-task-row\"][data-approval-kind=\"...\"],不用全页\ngetByText——页面含实时事件流面板会污染宽断言(C16)。成功判据不看 toast,看\n**服务端读回** QUOTATION_CREATED + quotationId:路径 404 或 optionId 缺失都会\n让这里读不到,且条目不会消失。\n\n自带客户与联系人常量(INBOX_*),不复用 CUSTOMER_NAME/CONTACT_NAME,避免这条链路\n把早期用例的客户档案推进到 QUOTATION_CREATED 而影响它们的断言。\n\n## 证据\n\n- 正向:pnpm check:ui 10 用例 0 失败(PG:55432/juhai_quotation_ui_final_20260801 +\n Redis:6382,UI_WEB_PORT=3132/UI_API_PORT=3232)。\n- 负向:移除 NestJS 聚合的 optionId 后重跑,**恰好第 10 条判红、其余 9 条全绿**,\n 失败点正是 approval-blocked-reason 出现(UI 如设计般禁用按钮而非发必然 400 的请求);\n 现场已恢复。\n- ⚠️ 负向那次会把 ui-acceptance.latest.json 覆盖成 status=failed/9 passed——那是负向\n 测试的产物不能当证据,故重跑正向后才提交(本提交内报告为 passed/10)。\n- baseline uiTestsPassed 9→10;CLAUDE.md 证据新鲜度行与 ui-acceptance 基线行同步回灌。\n- pnpm check 全绿 exit=0(18 项,自检 5 组)。本轮未跑 check:runtime(没动写链逻辑)。\n\n工作区 dirty:并发会话在途的报价域改动未包含在本提交内。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T09:52:10-07:00"},{"Sha1":"80fbe00d13bcdf8c4bf8ed64dca1c18cccbab449","Message":"feat(gate): 审批中心入参完整性门禁——从 Zod schema 反推必填字段,契约层与实现层各查一遍\n\n## 背景:一个从落地起就没成功执行过的按钮\n\n待办审批第 9 类「客户已选档·待转报价」的动作接口必填 optionId,而 ApprovalTask\n投影里根本没有这个字段,前端 buildPayload 返回 {}。同轮还有一个路径拼错的 bug\n盖在前面,路径修对了也只是把 404 换成 400 optionId Required。\n\n四道防线同时失效:\n- check:api-routes(同轮新建)只校验路径存在,不看请求体;\n- typecheck 抓不到——`optionId?: string` 是**可选**字段,后端不返回照样编译通过;\n- 9 类待办里恰好只有这一类没被 E2E 打过;\n- descriptor 里的 requiresVersionNo 是**手写布尔标记**,作者当初意识到「有些类\n 需要额外字段」却把它硬编码成单个布尔,新增 optionId 时没有对应标记。\n\n## 判据\n\n对每个 kind 从 Zod schema **反推**必填集合(不依赖任何手写标记),逐字段两层校验:\n1. 契约层:不在中心表单可输入白名单(comment/note/internalComment)的必填字段,\n 必须出现在 ApprovalTask 投影里;\n2. 实现层:**两个后端的聚合都真的产出了它**。第二层是关键——可选字段漏返回\n 不会被 typecheck 抓到,本次 bug 正是这么活下来的。\n\n某域给自己的 schema 加一个必填字段,中心立刻红,而不是等用户点按钮才发现。\n\n## 过程中修正了自己的一次误报\n\n初版用「kind 字面量 ±600 字符」文本窗口,一跑报 4 条红。先判责再改代码:方案两类\n共用 solutionReviews() 一个方法,两个 kind 字面量在方法开头推查询条件,而 versionNo\n在 25 行后的统一 map() 里,超窗即被判「没产出」。**字段产出点与 kind 声明点不在\n同一处是正常写法,门禁不该逼代码迁就它**——改按方法体切块。该坑已写成专门的自检\n用例钉死,防止以后被「优化」回窗口法而静默回潮。\n\n## 已知边界(不得外推)\n\n只校验必填不校验可选;不校验字段类型;USER_INPUT_FIELDS 白名单人工维护,\n往里加字段等于宣称中心表单能收集它,加之前必须确认 UI 真渲染了对应控件。\n\n## 证据\n\n- 真实源码两层负向测试:删 ApprovalTask.optionId → 契约层红;删 NestJS 聚合的\n optionId → 实现层红并点名 NestJS;现场均已恢复。\n- 自检 14 项(scripts/lib/approval-payload-contract.test.mjs),含 1 条基线绿 +\n 3 条端到端红,断言 exit **恰好为 1**(崩溃同样非 0,会让「期望红」以错误理由通过)。\n- 接线:pnpm check / governance-report 棘轮 / baseline(新增 0 地板,\n gateSelftests 4→5)/ CLAUDE.md 基线表。\n- 本地工作区 pnpm check 全绿 exit=0(18 项,自检 5 组);工作区 dirty\n (含并发会话在途的报价域改动,未包含在本提交内)。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T09:14:36-07:00"}],"HeadCommit":{"Sha1":"fb6c5ad390153c534787dc66d90284ff257d28d1","Message":"docs(governance): 治理入口新增业务主干导航行\n\nCLAUDE.md「使用入口」表加一行,把「搞清业务从头到尾怎么跑 / 新人上手」\n路由到 docs/domain/business-flow-overview.md。\n\n为什么要有这一行:跨域主干导航文档已经落在 docs/domain/ 下,但治理入口表\n不指向它——一份没有入口的资产下轮就没人读、也没人维护,随后静默漂移\n(C25 同族:文档指向的东西运行时到不了,反过来也一样成立)。\n\n该行同时写明它是**派生非真源**、据它下判断前必须回到 contracts / blueprint\n复核,避免入口表把导航文档抬成第二份状态机口径(P1)。\n\nAGENTS.md 为符号链接自动跟随;pnpm check exit 0(含 check:governance-docs)。\n作用域:本地工作区 dirty(并发会话有 approval-inbox 切片二在途),本提交\n只含 CLAUDE.md 一个文件。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T19:58:50-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/47ce4c6146a13c443db38c5816749d0591339e46...fb6c5ad390153c534787dc66d90284ff257d28d1","Len":3}...
|
1785639545
|
Edit
Delete
|
|
20287
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/solution-tier-multi-product
|
0
|
{"Commits":[{"Sha1":"bc5d9cc90 {"Commits":[{"Sha1":"bc5d9cc90007b3602ccd809110887ba581f0e4b0","Message":"docs(truth): 清除 docs/README.md 三处幽灵资产 + 两条断言机器化\n\nREADME「配套的工程工具」表长期把 commitlint 与 Husky 列成已有工具\n(\"本地 commit 时自动触发 lint + commitlint\"),实测两者从未存在:\n无 commitlint.config.js、无 .husky/、package.json 也无相关依赖。\n实锤旁证——本轮真实提交 fb6c5ad 未触发任何 hook。\n\n**\"以为有门禁\"比\"知道没有\"更危险**:读者据此跳过手工检查,等于文档\n凭空注销了一道本就不存在的卡口。故两行改为 🟡 规划未落地并写明\n\"提交前请手工跑 pnpm check\",落地模板指回 git-standard。\n\n同表第三处:`scripts/check-naming` 缺扩展名,真身是 check-naming.mjs。\n另把该表补一列「状态」,让已落地/未落地在结构上就分得开。\n\n机器化(C14 纪律:只为已实锤的漂移加断言):\n- commitlint-husky-honesty 从只扫 git-standard 扩到同时扫 README,\n 触发条件同时看 commitlint.config.js 与 .husky 两者是否存在\n- 新增 no-phantom-script-path:README 反引号里的 scripts/xxx 必须真实存在;\n 只扫反引号内且路径后至少一个字符,正文\"scripts/ + CI\"这类泛指不参与判定\n\n负向证据(scripts/lib/docs-truth.test.mjs,由 check:gate-selftest 重跑):\n- 抹掉 README / git-standard 的未落地标注 → 各自判红并命中对应断言 id\n (用 replaceAll,避免只替换第一处而静默拔牙)\n- README 写入不存在的脚本路径 → 判红命中 no-phantom-script-path\n- 泛指目录 \"scripts/ 目录\" → 保持绿,确认新断言够窄不误杀\n\npnpm check exit 0;check:gate-selftest 5 组全绿。作用域:本地工作区\ndirty(并发会话 approval-inbox 切片二在途),本提交只含这 3 个文件。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:09:57-07:00"}],"HeadCommit":{"Sha1":"bc5d9cc90007b3602ccd809110887ba581f0e4b0","Message":"docs(truth): 清除 docs/README.md 三处幽灵资产 + 两条断言机器化\n\nREADME「配套的工程工具」表长期把 commitlint 与 Husky 列成已有工具\n(\"本地 commit 时自动触发 lint + commitlint\"),实测两者从未存在:\n无 commitlint.config.js、无 .husky/、package.json 也无相关依赖。\n实锤旁证——本轮真实提交 fb6c5ad 未触发任何 hook。\n\n**\"以为有门禁\"比\"知道没有\"更危险**:读者据此跳过手工检查,等于文档\n凭空注销了一道本就不存在的卡口。故两行改为 🟡 规划未落地并写明\n\"提交前请手工跑 pnpm check\",落地模板指回 git-standard。\n\n同表第三处:`scripts/check-naming` 缺扩展名,真身是 check-naming.mjs。\n另把该表补一列「状态」,让已落地/未落地在结构上就分得开。\n\n机器化(C14 纪律:只为已实锤的漂移加断言):\n- commitlint-husky-honesty 从只扫 git-standard 扩到同时扫 README,\n 触发条件同时看 commitlint.config.js 与 .husky 两者是否存在\n- 新增 no-phantom-script-path:README 反引号里的 scripts/xxx 必须真实存在;\n 只扫反引号内且路径后至少一个字符,正文\"scripts/ + CI\"这类泛指不参与判定\n\n负向证据(scripts/lib/docs-truth.test.mjs,由 check:gate-selftest 重跑):\n- 抹掉 README / git-standard 的未落地标注 → 各自判红并命中对应断言 id\n (用 replaceAll,避免只替换第一处而静默拔牙)\n- README 写入不存在的脚本路径 → 判红命中 no-phantom-script-path\n- 泛指目录 \"scripts/ 目录\" → 保持绿,确认新断言够窄不误杀\n\npnpm check exit 0;check:gate-selftest 5 组全绿。作用域:本地工作区\ndirty(并发会话 approval-inbox 切片二在途),本提交只含这 3 个文件。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:09:57-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/fb6c5ad390153c534787dc66d90284ff257d28d1...bc5d9cc90007b3602ccd809110887ba581f0e4b0","Len":1}...
|
1785640209
|
Edit
Delete
|
|
20288
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/solution-tier-multi-product
|
0
|
{"Commits":[{"Sha1":"b15cb5471 {"Commits":[{"Sha1":"b15cb54710ee5d1849b806adc01fcdddcaff5484","Message":"Merge remote-tracking branch 'github/feat/solution-tier-multi-product' into feat/solution-tier-multi-product\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:10:47-07:00"},{"Sha1":"7b9d30ff91324ab64ec0c115a0f05c0c503f81b6","Message":"Merge branch 'main' into feat/solution-tier-multi-product","AuthorEmail":"158980461+laoluojuhai@users.noreply.github.com","AuthorName":"laoluojuhai","CommitterEmail":"noreply@github.com","CommitterName":"GitHub","Timestamp":"2026-08-01T20:02:59-07:00"},{"Sha1":"ab8275d49416872ad2c720a1274d8d59068d2495","Message":"Merge pull request #5 from laoluojuhai/feat/g24-list-pagination-and-options\n\nfeat(g24): 列表分页 + 选择器 options 双读路径(关闭 G24)","AuthorEmail":"158980461+laoluojuhai@users.noreply.github.com","AuthorName":"laoluojuhai","CommitterEmail":"noreply@github.com","CommitterName":"GitHub","Timestamp":"2026-08-01T20:01:54-07:00"},{"Sha1":"ad018e731c9be66721d9bae55dde85574d03e6c2","Message":"Merge pull request #4 from laoluojuhai/chore/governance-c25-c26-c27\n\n治理修复:关闭 C25/C26/C27 —— 幽灵 UI 资产、partial schema 丢失的不变量、浏览器端 KPI","AuthorEmail":"158980461+laoluojuhai@users.noreply.github.com","AuthorName":"laoluojuhai","CommitterEmail":"noreply@github.com","CommitterName":"GitHub","Timestamp":"2026-08-01T20:01:33-07:00"},{"Sha1":"c1777844395fa1863bec92d95284c2f74250136b","Message":"Merge pull request #3 from laoluojuhai/chore/close-g14-remote-gate\n\ndocs: 关闭 G14——远端门禁从「能绿」推进到「会拦」","AuthorEmail":"158980461+laoluojuhai@users.noreply.github.com","AuthorName":"laoluojuhai","CommitterEmail":"noreply@github.com","CommitterName":"GitHub","Timestamp":"2026-08-01T20:00:23-07:00"}],"HeadCommit":{"Sha1":"b15cb54710ee5d1849b806adc01fcdddcaff5484","Message":"Merge remote-tracking branch 'github/feat/solution-tier-multi-product' into feat/solution-tier-multi-product\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:10:47-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/bc5d9cc90007b3602ccd809110887ba581f0e4b0...b15cb54710ee5d1849b806adc01fcdddcaff5484","Len":5}...
|
1785640342
|
Edit
Delete
|
|
20289
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/solution-tier-multi-product
|
0
|
{"Commits":[{"Sha1":"3a22776ed {"Commits":[{"Sha1":"3a22776ed81118bba6b93bfb50d5b36a70b2ee35","Message":"feat(approval-inbox): 切片二(4/5 域)——补发起人字段与不可自批硬校验\n\n蓝图 §7 裁决的方案 B:中心先落地、内控另立一片。本片补齐 5 个缺发起人的域中的 4 个。\n\n## 新增字段(两后端 schema 对等 + 迁移)\n- SolutionProject.reviewRequestedBy —— **把方案推进到当前待审状态的人**。\n 单列足够:同一时刻只有一个待审阶段。技术复核阶段=提交人、经理批准阶段=技术复核人,\n 正好都是各自阶段该被禁止自批的那一个。\n- PurchaseOrder.acceptedBy —— 接单人\n- Stocktake.submittedBy —— 提交盘点的人\n\n三列一律 nullable:迁移前的存量行无从追溯发起人,**判不了就不判**。\n给个假默认值会让不可自批看起来生效、实际保护不了任何一行。\n\n## 不可自批硬校验(两后端对等)\n方案技术复核 / 方案经理批准 / 采购审批 / 盘点审批各加 approver ≠ requester 校验,\n403 + code=SELF_APPROVAL_FORBIDDEN,与红冲/退款既有口径一致。\n角色分离挡不住 ADMIN(持全部 capability),所以必须按 actor 再判一次——两层缺一不可。\n\nactor 串进 5 条写链(两后端各自的 controller/route 补 RequestActor/requestActor)。\n\n## 契约与聚合\nselfRequestedKnown 对这 4 类转 true;聚合把 requestedBy 带出来,\n自批过滤随之在这 4 类上真正生效。UI 文案改为点名「报价审批暂未记录发起人」。\n\n## E2E 随内控调整(这些红盘是闸门在正确工作,不是回归)\n原用例里同一个 ADMIN 既提交又审批——正是不可自批要禁的。浏览器会话取不到身份时\nactor 兜底为 `role:\u003cROLE\u003e`(同角色视为同一人,刻意 fail-closed,不得为测试放宽)。\n故改为:先在浏览器断言「点了没推进」(这是该闸门唯一的浏览器级证据),\n再换一个审批人身份走 API 放行。覆盖方案双审核、采购审批、盘点审批三处。\n\n## 证据(本地工作区 2026-08-01)\n- pnpm check 0 / check:runtime 296(地板 294→296)/ check:ui 10(地板 9→10)\n- 两后端各 +1 条自批 403 断言;contracts 单测覆盖 selfRequestedKnown 全集\n- 负向实跑判红后恢复:拔掉采购不可自批校验 → 期望 403 实得 200 判红\n\n## 仍未闭环\n报价审批(ApprovalRequest 无 requestedBy)**本片未做**——并发会话正在改报价域,\n避让以免卡住对方。中心因此仍**不得**笼统宣称「已保证不可自批」。\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:15:14-07:00"}],"HeadCommit":{"Sha1":"3a22776ed81118bba6b93bfb50d5b36a70b2ee35","Message":"feat(approval-inbox): 切片二(4/5 域)——补发起人字段与不可自批硬校验\n\n蓝图 §7 裁决的方案 B:中心先落地、内控另立一片。本片补齐 5 个缺发起人的域中的 4 个。\n\n## 新增字段(两后端 schema 对等 + 迁移)\n- SolutionProject.reviewRequestedBy —— **把方案推进到当前待审状态的人**。\n 单列足够:同一时刻只有一个待审阶段。技术复核阶段=提交人、经理批准阶段=技术复核人,\n 正好都是各自阶段该被禁止自批的那一个。\n- PurchaseOrder.acceptedBy —— 接单人\n- Stocktake.submittedBy —— 提交盘点的人\n\n三列一律 nullable:迁移前的存量行无从追溯发起人,**判不了就不判**。\n给个假默认值会让不可自批看起来生效、实际保护不了任何一行。\n\n## 不可自批硬校验(两后端对等)\n方案技术复核 / 方案经理批准 / 采购审批 / 盘点审批各加 approver ≠ requester 校验,\n403 + code=SELF_APPROVAL_FORBIDDEN,与红冲/退款既有口径一致。\n角色分离挡不住 ADMIN(持全部 capability),所以必须按 actor 再判一次——两层缺一不可。\n\nactor 串进 5 条写链(两后端各自的 controller/route 补 RequestActor/requestActor)。\n\n## 契约与聚合\nselfRequestedKnown 对这 4 类转 true;聚合把 requestedBy 带出来,\n自批过滤随之在这 4 类上真正生效。UI 文案改为点名「报价审批暂未记录发起人」。\n\n## E2E 随内控调整(这些红盘是闸门在正确工作,不是回归)\n原用例里同一个 ADMIN 既提交又审批——正是不可自批要禁的。浏览器会话取不到身份时\nactor 兜底为 `role:\u003cROLE\u003e`(同角色视为同一人,刻意 fail-closed,不得为测试放宽)。\n故改为:先在浏览器断言「点了没推进」(这是该闸门唯一的浏览器级证据),\n再换一个审批人身份走 API 放行。覆盖方案双审核、采购审批、盘点审批三处。\n\n## 证据(本地工作区 2026-08-01)\n- pnpm check 0 / check:runtime 296(地板 294→296)/ check:ui 10(地板 9→10)\n- 两后端各 +1 条自批 403 断言;contracts 单测覆盖 selfRequestedKnown 全集\n- 负向实跑判红后恢复:拔掉采购不可自批校验 → 期望 403 实得 200 判红\n\n## 仍未闭环\n报价审批(ApprovalRequest 无 requestedBy)**本片未做**——并发会话正在改报价域,\n避让以免卡住对方。中心因此仍**不得**笼统宣称「已保证不可自批」。\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:15:14-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/b15cb54710ee5d1849b806adc01fcdddcaff5484...3a22776ed81118bba6b93bfb50d5b36a70b2ee35","Len":1}...
|
1785640521
|
Edit
Delete
|
|
20290
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/solution-tier-multi-product
|
0
|
{"Commits":[{"Sha1":"fbdc3680f {"Commits":[{"Sha1":"fbdc3680f4ef53e824738ca8cbf8f0e6deefce55","Message":"fix(gate): 文档诚实注收窄到行——文件级标注不该赦免同文件里的另一句谎话\n\ngit-standard 导语长期写「Conventional Commits 规范 + commitlint 自动校验」,\n而同文件 §三 已标注「🟡 规划未落地」。上一轮加的 commitlint-husky-honesty\n是**文件级**判定(文件里出现「规划未落地」即算诚实),因此绿着放行了这句\n自相矛盾的导语——而读者读的正是导语,不会翻到第三节。\n\n粒度选得比缺陷粗一级,门禁就会用\"文件里有免责声明\"赦免\"文件里另一句谎话\"。\n读者是按句子受骗的,断言就得按句子判。\n\n改动:\n- git-standard 导语改为如实描述:校验靠人、无 hook、提交前手工跑 pnpm check\n- 新增 commitlint-husky-no-auto-claim:同一行既点名 commitlint/husky\n 又宣称\"自动\"、且该行无未落地字样 → 判红。收窄到行是为了不误咬 CI 侧\n 合法的\"自动\"表述(C14:宁可窄,不造模糊规则)\n\n负向证据(scripts/lib/docs-truth.test.mjs,由 check:gate-selftest 重跑):\n- 向两份文件各写回**那句历史原文** → 各自 exit 恰为 1 且命中\n [commitlint-husky-no-auto-claim:\u003cfile\u003e],确认红在点子上而非撞上别的断言\n- 同行带未落地标注的「自动」表述 → 仍绿,锁住新断言的窄边界。\n 只有红测试的门禁会朝越咬越宽漂移,直到有人为了让它闭嘴而删掉它\n\npnpm check exit 0;check:gate-selftest 5 组全绿。作用域:本地工作区 dirty\n(并发会话 approval-inbox 切片二在途),本提交只含这 3 个文件。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:18:46-07:00"}],"HeadCommit":{"Sha1":"fbdc3680f4ef53e824738ca8cbf8f0e6deefce55","Message":"fix(gate): 文档诚实注收窄到行——文件级标注不该赦免同文件里的另一句谎话\n\ngit-standard 导语长期写「Conventional Commits 规范 + commitlint 自动校验」,\n而同文件 §三 已标注「🟡 规划未落地」。上一轮加的 commitlint-husky-honesty\n是**文件级**判定(文件里出现「规划未落地」即算诚实),因此绿着放行了这句\n自相矛盾的导语——而读者读的正是导语,不会翻到第三节。\n\n粒度选得比缺陷粗一级,门禁就会用\"文件里有免责声明\"赦免\"文件里另一句谎话\"。\n读者是按句子受骗的,断言就得按句子判。\n\n改动:\n- git-standard 导语改为如实描述:校验靠人、无 hook、提交前手工跑 pnpm check\n- 新增 commitlint-husky-no-auto-claim:同一行既点名 commitlint/husky\n 又宣称\"自动\"、且该行无未落地字样 → 判红。收窄到行是为了不误咬 CI 侧\n 合法的\"自动\"表述(C14:宁可窄,不造模糊规则)\n\n负向证据(scripts/lib/docs-truth.test.mjs,由 check:gate-selftest 重跑):\n- 向两份文件各写回**那句历史原文** → 各自 exit 恰为 1 且命中\n [commitlint-husky-no-auto-claim:\u003cfile\u003e],确认红在点子上而非撞上别的断言\n- 同行带未落地标注的「自动」表述 → 仍绿,锁住新断言的窄边界。\n 只有红测试的门禁会朝越咬越宽漂移,直到有人为了让它闭嘴而删掉它\n\npnpm check exit 0;check:gate-selftest 5 组全绿。作用域:本地工作区 dirty\n(并发会话 approval-inbox 切片二在途),本提交只含这 3 个文件。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:18:46-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/3a22776ed81118bba6b93bfb50d5b36a70b2ee35...fbdc3680f4ef53e824738ca8cbf8f0e6deefce55","Len":1}...
|
1785640746
|
Edit
Delete
|
|
20291
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/solution-tier-multi-product
|
0
|
{"Commits":[{"Sha1":"479c74220 {"Commits":[{"Sha1":"479c742208b50c7e788cb4eea1b6b878fcfe615e","Message":"feat(approval-inbox): 切片二收口——报价审批补发起人,不可自批 6/6 道闭环\n\n上一轮避让并发会话对报价域的改动,留下 ApprovalRequest 这最后一道。\n现该域已干净(其工作已合入 b15cb54),补齐收口。\n\n## 改动\n- ApprovalRequest.requestedBy(两后端 schema 对等 + 迁移,nullable)\n- requestApproval 落发起人;decideApproval 校验 approver ≠ requester\n → 403 + code=SELF_APPROVAL_FORBIDDEN,与其余五道口径一致\n- actor 串进两后端的 request-approval / approve / reject-approval\n (Fastify 的 request-approval 因此脱离无 actor 的 noBodyAction)\n- contracts 描述表 QUOTATION_APPROVAL.selfRequestedKnown → true;\n 聚合把 PENDING 子行的 requestedBy 带出来,自批过滤在报价审批上真正生效\n- UI 文案去掉「报价审批暂未记录发起人」的例外说明\n\n## 至此不可自批覆盖 6/6 道真审批\n报价 / 方案技术复核 / 方案经理批准 / 采购 / 盘点 / 红冲 / 退款\n(仅剩的两类 selfRequestedKnown=false 是「客户决定后的待办」,本就无发起人概念)\n\n## 诚实边界(未因本片改变)\n- 列均 nullable:建列前的存量行判不了就不判,**不假装判过**\n- 仍受 G8 制约:Bearer 未验签、actor 取不到时兜底 `role:\u003cROLE\u003e`,\n 所以这是「防误操作」不是「防抵赖」,**不得宣称为审计级职责分离**\n\n## 证据(本地工作区 2026-08-01)\n- pnpm check 0 / check:runtime 296 / check:ui 10\n- 两后端各 +1 条报价自批 403 断言,并各配一条「换个审批人即通过」的反证\n ——只断言 403 无法区分「拦对了」与「谁都批不了」的误杀\n- 负向实跑判红后恢复:拔掉报价不可自批校验 → 期望 403 实得 200\n- 回灌:CLAUDE.md 真源地图新增「跨域待办审批与不可自批」条目 +\n 证据新鲜度/棘轮行更新;蓝图 §4 缺口 1、2 标记闭环\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:28:00-07:00"}],"HeadCommit":{"Sha1":"479c742208b50c7e788cb4eea1b6b878fcfe615e","Message":"feat(approval-inbox): 切片二收口——报价审批补发起人,不可自批 6/6 道闭环\n\n上一轮避让并发会话对报价域的改动,留下 ApprovalRequest 这最后一道。\n现该域已干净(其工作已合入 b15cb54),补齐收口。\n\n## 改动\n- ApprovalRequest.requestedBy(两后端 schema 对等 + 迁移,nullable)\n- requestApproval 落发起人;decideApproval 校验 approver ≠ requester\n → 403 + code=SELF_APPROVAL_FORBIDDEN,与其余五道口径一致\n- actor 串进两后端的 request-approval / approve / reject-approval\n (Fastify 的 request-approval 因此脱离无 actor 的 noBodyAction)\n- contracts 描述表 QUOTATION_APPROVAL.selfRequestedKnown → true;\n 聚合把 PENDING 子行的 requestedBy 带出来,自批过滤在报价审批上真正生效\n- UI 文案去掉「报价审批暂未记录发起人」的例外说明\n\n## 至此不可自批覆盖 6/6 道真审批\n报价 / 方案技术复核 / 方案经理批准 / 采购 / 盘点 / 红冲 / 退款\n(仅剩的两类 selfRequestedKnown=false 是「客户决定后的待办」,本就无发起人概念)\n\n## 诚实边界(未因本片改变)\n- 列均 nullable:建列前的存量行判不了就不判,**不假装判过**\n- 仍受 G8 制约:Bearer 未验签、actor 取不到时兜底 `role:\u003cROLE\u003e`,\n 所以这是「防误操作」不是「防抵赖」,**不得宣称为审计级职责分离**\n\n## 证据(本地工作区 2026-08-01)\n- pnpm check 0 / check:runtime 296 / check:ui 10\n- 两后端各 +1 条报价自批 403 断言,并各配一条「换个审批人即通过」的反证\n ——只断言 403 无法区分「拦对了」与「谁都批不了」的误杀\n- 负向实跑判红后恢复:拔掉报价不可自批校验 → 期望 403 实得 200\n- 回灌:CLAUDE.md 真源地图新增「跨域待办审批与不可自批」条目 +\n 证据新鲜度/棘轮行更新;蓝图 §4 缺口 1、2 标记闭环\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:28:00-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/fbdc3680f4ef53e824738ca8cbf8f0e6deefce55...479c742208b50c7e788cb4eea1b6b878fcfe615e","Len":1}...
|
1785641286
|
Edit
Delete
|
|
20292
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/solution-tier-multi-product
|
0
|
{"Commits":[{"Sha1":"06495232f {"Commits":[{"Sha1":"06495232f1936d9e221f7745216962379994b261","Message":"chore: 合并 main 后刷新治理报告\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:45:47-07:00"},{"Sha1":"ae1046edf9b030e1a67c30235ddd345590e61fdf","Message":"Merge remote-tracking branch 'github/main' into feat/solution-tier-multi-product\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:42:40-07:00"},{"Sha1":"8ab2314d69f1885d49bbbf1fc3c5b3c6cccb6711","Message":"Merge pull request #6 from laoluojuhai/feat/solution-tier-multi-product\n\nfeat(solution): 三档方案每档多商品 + 数量默认取包房总数","AuthorEmail":"158980461+laoluojuhai@users.noreply.github.com","AuthorName":"laoluojuhai","CommitterEmail":"noreply@github.com","CommitterName":"GitHub","Timestamp":"2026-08-01T20:09:43-07:00"}],"HeadCommit":{"Sha1":"06495232f1936d9e221f7745216962379994b261","Message":"chore: 合并 main 后刷新治理报告\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:45:47-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/479c742208b50c7e788cb4eea1b6b878fcfe615e...06495232f1936d9e221f7745216962379994b261","Len":3}...
|
1785642353
|
Edit
Delete
|
|
20293
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/main
|
0
|
{"Commits":[{"Sha1":"7619cf796 {"Commits":[{"Sha1":"7619cf79681794bf28a8dceeb606c9dcfec21a5c","Message":"Merge pull request #7 from laoluojuhai/feat/solution-tier-multi-product\n\nfeat(approval-inbox): 跨域待办审批中心切片二——不可自批 6/6 道闭环","AuthorEmail":"158980461+laoluojuhai@users.noreply.github.com","AuthorName":"laoluojuhai","CommitterEmail":"noreply@github.com","CommitterName":"GitHub","Timestamp":"2026-08-01T22:08:17-07:00"},{"Sha1":"06495232f1936d9e221f7745216962379994b261","Message":"chore: 合并 main 后刷新治理报告\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:45:47-07:00"},{"Sha1":"ae1046edf9b030e1a67c30235ddd345590e61fdf","Message":"Merge remote-tracking branch 'github/main' into feat/solution-tier-multi-product\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:42:40-07:00"},{"Sha1":"479c742208b50c7e788cb4eea1b6b878fcfe615e","Message":"feat(approval-inbox): 切片二收口——报价审批补发起人,不可自批 6/6 道闭环\n\n上一轮避让并发会话对报价域的改动,留下 ApprovalRequest 这最后一道。\n现该域已干净(其工作已合入 b15cb54),补齐收口。\n\n## 改动\n- ApprovalRequest.requestedBy(两后端 schema 对等 + 迁移,nullable)\n- requestApproval 落发起人;decideApproval 校验 approver ≠ requester\n → 403 + code=SELF_APPROVAL_FORBIDDEN,与其余五道口径一致\n- actor 串进两后端的 request-approval / approve / reject-approval\n (Fastify 的 request-approval 因此脱离无 actor 的 noBodyAction)\n- contracts 描述表 QUOTATION_APPROVAL.selfRequestedKnown → true;\n 聚合把 PENDING 子行的 requestedBy 带出来,自批过滤在报价审批上真正生效\n- UI 文案去掉「报价审批暂未记录发起人」的例外说明\n\n## 至此不可自批覆盖 6/6 道真审批\n报价 / 方案技术复核 / 方案经理批准 / 采购 / 盘点 / 红冲 / 退款\n(仅剩的两类 selfRequestedKnown=false 是「客户决定后的待办」,本就无发起人概念)\n\n## 诚实边界(未因本片改变)\n- 列均 nullable:建列前的存量行判不了就不判,**不假装判过**\n- 仍受 G8 制约:Bearer 未验签、actor 取不到时兜底 `role:\u003cROLE\u003e`,\n 所以这是「防误操作」不是「防抵赖」,**不得宣称为审计级职责分离**\n\n## 证据(本地工作区 2026-08-01)\n- pnpm check 0 / check:runtime 296 / check:ui 10\n- 两后端各 +1 条报价自批 403 断言,并各配一条「换个审批人即通过」的反证\n ——只断言 403 无法区分「拦对了」与「谁都批不了」的误杀\n- 负向实跑判红后恢复:拔掉报价不可自批校验 → 期望 403 实得 200\n- 回灌:CLAUDE.md 真源地图新增「跨域待办审批与不可自批」条目 +\n 证据新鲜度/棘轮行更新;蓝图 §4 缺口 1、2 标记闭环\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:28:00-07:00"},{"Sha1":"fbdc3680f4ef53e824738ca8cbf8f0e6deefce55","Message":"fix(gate): 文档诚实注收窄到行——文件级标注不该赦免同文件里的另一句谎话\n\ngit-standard 导语长期写「Conventional Commits 规范 + commitlint 自动校验」,\n而同文件 §三 已标注「🟡 规划未落地」。上一轮加的 commitlint-husky-honesty\n是**文件级**判定(文件里出现「规划未落地」即算诚实),因此绿着放行了这句\n自相矛盾的导语——而读者读的正是导语,不会翻到第三节。\n\n粒度选得比缺陷粗一级,门禁就会用\"文件里有免责声明\"赦免\"文件里另一句谎话\"。\n读者是按句子受骗的,断言就得按句子判。\n\n改动:\n- git-standard 导语改为如实描述:校验靠人、无 hook、提交前手工跑 pnpm check\n- 新增 commitlint-husky-no-auto-claim:同一行既点名 commitlint/husky\n 又宣称\"自动\"、且该行无未落地字样 → 判红。收窄到行是为了不误咬 CI 侧\n 合法的\"自动\"表述(C14:宁可窄,不造模糊规则)\n\n负向证据(scripts/lib/docs-truth.test.mjs,由 check:gate-selftest 重跑):\n- 向两份文件各写回**那句历史原文** → 各自 exit 恰为 1 且命中\n [commitlint-husky-no-auto-claim:\u003cfile\u003e],确认红在点子上而非撞上别的断言\n- 同行带未落地标注的「自动」表述 → 仍绿,锁住新断言的窄边界。\n 只有红测试的门禁会朝越咬越宽漂移,直到有人为了让它闭嘴而删掉它\n\npnpm check exit 0;check:gate-selftest 5 组全绿。作用域:本地工作区 dirty\n(并发会话 approval-inbox 切片二在途),本提交只含这 3 个文件。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-01T20:18:46-07:00"}],"HeadCommit":{"Sha1":"7619cf79681794bf28a8dceeb606c9dcfec21a5c","Message":"Merge pull request #7 from laoluojuhai/feat/solution-tier-multi-product\n\nfeat(approval-inbox): 跨域待办审批中心切片二——不可自批 6/6 道闭环","AuthorEmail":"158980461+laoluojuhai@users.noreply.github.com","AuthorName":"laoluojuhai","CommitterEmail":"noreply@github.com","CommitterName":"GitHub","Timestamp":"2026-08-01T22:08:17-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/9618d78da3e668f14e3c1611d7090f45eef0c085...7619cf79681794bf28a8dceeb606c9dcfec21a5c","Len":31}...
|
1785647350
|
Edit
Delete
|
|
20295
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/g8-verified-auth
|
0
|
|
1785690034
|
Edit
Delete
|
|
20296
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/g8-verified-auth
|
0
|
{"Commits":[{"Sha1":"90cb8a411 {"Commits":[{"Sha1":"90cb8a4111b88f700b2dba5913e7a1e29c9666b6","Message":"feat(auth): 用户与权限管理落地——登录、受管理令牌吊销、本地身份切换\n\n承接 G8 验签切片(已合入的 auth.ts 单源),把「令牌从哪来」这一半补上:\n此前只解决了「后端是否验签」,令牌仍靠带外发放、没有账号也无从吊销。\n\n## 用户与访问管理\n- 新增 `packages/password-auth`:口令哈希与校验独立成包,不与业务代码混在一起\n- 迁移 `20260802150000_user_access_management`(两后端对等)\n- 登录端点 + `/login` 页面 + `features/users` 管理界面\n- **受管理令牌**:claims 带 `authVersion`,每次鉴权比对 DB 快照\n (同租户/同用户/同角色/ACTIVE/版本一致),任一不符即 REVOKED——\n 这让「停用账号」「改角色」能**立即生效**,而不是等令牌自然过期;\n 未带 authVersion 的 bootstrap/local 令牌走原有受控接缝,不受影响\n- 蓝图见 docs/domain/user-access-management-blueprint.md\n\n## 本地身份切换(开发接缝,非生产能力)\n`LOCAL_DEVELOPMENT_IDENTITIES` 固定目录 + 显式开关,双后端仅在非生产签发;\nWeb 用同一 cookie 同步 SSR / REST / 实时连接。**只为职责分离验收而存在**\n(不可自批需要两个不同的 sub,而浏览器会话只有一个身份),生产模式无条件关闭。\n契约层单测钉住:目录 id/sub 唯一、不含 UNAUTHENTICATED、只接受目录内身份。\n\n## 方案技术复核邀请\n迁移 `20260802170000_solution_technical_reviewer_invitation` + 两后端服务与路由,\n配套 SolutionStatusBadge;owner-matrix 与方案蓝图同步登记。\n\n## 文档\n新增 docs/spatiotemporal-fact-model.md(时空事实模型范式规范);\n词典、角色权限蓝图、待办审批蓝图、操作手册、治理经验库同步更新;\nG8 交接文档转为已完成归档,并保留四条诚实边界(RLS 未做、令牌分发非生产级、\naccess_token 进 log 的残留风险、验签可信才谈防抵赖)。\n\n## 证据(本地工作区,dirty)\n`pnpm check` exit 0。runtime/ui 由本轮实现方记录于交接文档:\n312/312 与 11/11,其中浏览器真实完成管理员→技术→管理员切换。\n该证据绑定本地工作区,不外推为远端 CI、部署或生产签收。\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-02T09:40:29-07:00"},{"Sha1":"b1e5c5e57f3d4075b12289f9de89ae616d909b7e","Message":"feat(governance): 业务闭环五切片——事件如实、配货可取消复活、退款可撤回、合同申请可拒绝、过期报价可续期\n\n跨域闭环分析(2026-08-02)定位的死路与半接线修复:\n- A1 客户「暂缓」(DEFER)事件错标成 customer_selected → 契约新增 solution.customer_deferred,两后端按 action 如实发布\n- A2 配货单 cancel 死边接线:补 cancel 端点与订单取消级联;salesOrderId 唯一 → 取消后重配走 CANCELLED--recreate--\u003eDRAFT 同行复活,明细重建实拣归零\n- A3 退款撤回补 UI 入口 + 双后端「申请→撤回→冻结额度释放」验收\n- B1 合同申请拒绝:ContractRequestStatus 增 REJECTED(留痕三列),报价同 tx contract_reject→ACCEPTED,复活重申请;连带拆开 CONTRACT_REQUESTED 的语义混叠,新增 CONTRACT_EFFECTIVE 终态由 activateContract 同 tx 迁移(不变量测试逼出的裁决)\n- B2 过期报价续期:SENT 增 send 自环,重发=完整 send 流程(版本+1/刷新有效期/重快照开关),配件冻结幂等\n\n迁移:20260802100000_contract_request_rejection(两后端,ALTER TYPE ADD VALUE + 加列,均 additive)\n证据(本地 dirty 工作区,2026-08-02):pnpm check EXIT=0;check:runtime 330 用例全绿(地板312);check:ui 11/12——唯一红盘为并发会话 skip_technical_review WIP 与其 E2E 不同步,非本切片所致\n文档:business-flow-overview 状态机索引与主干分支、quotation-blueprint §10、inventory-blueprint §9\n\n注:本提交经 hunk 级分离,不含同工作区并发会话的用户管理/技术复核指派 WIP;\n快照缺 MobileOverview 的 CONTRACT_EFFECTIVE 标签一行(属对方文件),合并后自愈\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-02T09:21:45-07:00"},{"Sha1":"7b3c8328178440e90c3a332dfbbd563401c44edc","Message":"feat(g8): 验签切片完成——前端单 token、测试夹具真签、本地身份切换、auth-source 门禁与文档回灌\n\n本地三级门禁证据(2026-08-02, dirty 工作区): check 绿 / check:runtime 312 用例 / check:ui 11 用例。\n剩余 G8 边界(生产 IdP、令牌吊销轮换、RLS)见 docs/handoff/g8-verified-auth.md 与 CLAUDE.md 动态区。\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-02T08:31:43-07:00"},{"Sha1":"83498209dddd46dfc21f45b1806b94e18d903552","Message":"docs(handoff): G8 验签改造交接文档——已裁决/已完成/剩余四步/诚实边界/已踩的坑\n\n上下文将满,把接手所需信息落盘:两项用户裁决、已完成范围、剩余四步的具体做法\n(含「测试 helper 签名保持不变、只换内部实现」这个让上百处调用点零改动的关键做法)、\n必须保留的诚实边界(RLS 未做、令牌分发非生产级、access_token 进 log 的残留风险),\n以及两个已踩过的坑(pnpm store 错位会触发清空重装;contracts lib 刻意不含 DOM)。\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-02T00:21:26-07:00"},{"Sha1":"5f8b521692f93344dd494d71fdcfe3cd3786fa6f","Message":"feat(g8/wip): 访问令牌验签单源 + 两后端强制验签(进行中,测试与前端未改完)\n\n⚠️ **本提交是 WIP,尚未通过 runtime/ui 门禁**——两套 HTTP 验收与 E2E 仍在发\ndemo 头,必然红。推上来是为了不丢这批安全改造,勿合并。\n\n## 已完成\n- contracts 新增 auth.ts:HS256 验签单源\n · verifyAccessToken(token, secret):**显式限定 algorithms**(不写就允许\n alg:none / 算法混淆);密钥 \u003c32 字符当场抛 CONFIG 而非降级放行\n · secret 由调用方显式传入——contracts 会被打进浏览器包,自己读 env\n 就有把密钥内联进前端产物的风险\n · decodeAccessTokenClaimsUnverified 仅供 UI 门控,名字写明它不是安全校验\n · 11 条对抗性单测:alg:none / HS512 混淆 / 篡改租户 / 过期 / 弱密钥 / 非法角色\n- 两后端改为**每请求验签一次**(NestJS 全局 APP_GUARD、Fastify onRequest 钩子),\n claims 挂 req 供下游同步读取\n- 身份三件套(tenantId/role/sub)全部改从已验签 claims 取;\n **删除全部回退分支**——验签失败即 401,绝不落回 x-tenant-id/x-user-role/x-user-id\n (那条兼容路径在验签落地后就是降级攻击面,G8 原文点名)\n- SSE/WS 的 ?access_token= 同样验签;删除 ?tenantId= 明文参数\n (它等于让任何人自称任意租户)\n- 免认证白名单只有 /api/health 与 /api/public/**,默认拒绝\n\n## 一并暴露的既有问题\nCLAUDE.md 只记了「租户未验签」,实际**角色也未验签**:x-user-role 同样是\n客户端自报,整个 capability 体系建立在后端信任这个头之上。现已一并收进 token。\n\n## 未完成(下一步)\n- 前端与导入脚本仍发 demo 头\n- 两套 HTTP 验收 + E2E 需改用 jose 现签 token,并补 401 判责断言\n- contracts/tenant.ts 的旧未验签 seam 待删\n- 门禁断言(禁止 demo 头回退回潮)+ 治理回灌\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-02T00:07:43-07:00"}],"HeadCommit":{"Sha1":"90cb8a4111b88f700b2dba5913e7a1e29c9666b6","Message":"feat(auth): 用户与权限管理落地——登录、受管理令牌吊销、本地身份切换\n\n承接 G8 验签切片(已合入的 auth.ts 单源),把「令牌从哪来」这一半补上:\n此前只解决了「后端是否验签」,令牌仍靠带外发放、没有账号也无从吊销。\n\n## 用户与访问管理\n- 新增 `packages/password-auth`:口令哈希与校验独立成包,不与业务代码混在一起\n- 迁移 `20260802150000_user_access_management`(两后端对等)\n- 登录端点 + `/login` 页面 + `features/users` 管理界面\n- **受管理令牌**:claims 带 `authVersion`,每次鉴权比对 DB 快照\n (同租户/同用户/同角色/ACTIVE/版本一致),任一不符即 REVOKED——\n 这让「停用账号」「改角色」能**立即生效**,而不是等令牌自然过期;\n 未带 authVersion 的 bootstrap/local 令牌走原有受控接缝,不受影响\n- 蓝图见 docs/domain/user-access-management-blueprint.md\n\n## 本地身份切换(开发接缝,非生产能力)\n`LOCAL_DEVELOPMENT_IDENTITIES` 固定目录 + 显式开关,双后端仅在非生产签发;\nWeb 用同一 cookie 同步 SSR / REST / 实时连接。**只为职责分离验收而存在**\n(不可自批需要两个不同的 sub,而浏览器会话只有一个身份),生产模式无条件关闭。\n契约层单测钉住:目录 id/sub 唯一、不含 UNAUTHENTICATED、只接受目录内身份。\n\n## 方案技术复核邀请\n迁移 `20260802170000_solution_technical_reviewer_invitation` + 两后端服务与路由,\n配套 SolutionStatusBadge;owner-matrix 与方案蓝图同步登记。\n\n## 文档\n新增 docs/spatiotemporal-fact-model.md(时空事实模型范式规范);\n词典、角色权限蓝图、待办审批蓝图、操作手册、治理经验库同步更新;\nG8 交接文档转为已完成归档,并保留四条诚实边界(RLS 未做、令牌分发非生产级、\naccess_token 进 log 的残留风险、验签可信才谈防抵赖)。\n\n## 证据(本地工作区,dirty)\n`pnpm check` exit 0。runtime/ui 由本轮实现方记录于交接文档:\n312/312 与 11/11,其中浏览器真实完成管理员→技术→管理员切换。\n该证据绑定本地工作区,不外推为远端 CI、部署或生产签收。\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-02T09:40:29-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/7619cf79681794bf28a8dceeb606c9dcfec21a5c...90cb8a4111b88f700b2dba5913e7a1e29c9666b6","Len":5}...
|
1785690034
|
Edit
Delete
|
|
20297
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/g8-verified-auth
|
0
|
{"Commits":[{"Sha1":"9f1ea88bc {"Commits":[{"Sha1":"9f1ea88bcf8f55b6a1d25f0c5e5d0e282fd09a56","Message":"fix(dev): 本地启动链补齐 G8 验签所需 env——launch.json 与 README 都停在验签之前\n\n## 症状\n`preview_start` 起 web 后页面能开、功能全死:每个 API 请求 500 AUTH_NOT_CONFIGURED。\n(同时出现的 corepack `Invalid package.json` 是另一回事——那三次重试恰好撞在\n上一个 cherry-pick 的冲突窗口里,`package.json` 当时带着冲突标记,确实不是合法 JSON。\n冲突收干净后自动消失,不是配置问题。)\n\n## 根因\n`.claude/launch.json` 与 README 的「快速开始」都写于 G8 落地之前。\nG8 之后两个后端每请求都要 verifyAccessToken,缺 AUTH_JWT_SECRET 即 fail-closed 500\n——刻意不回 401:密钥没配是服务端故障,回 401 会把运维问题伪装成调用方的凭证问题。\n而 `.env.example` 双端其实都已补过密钥,漏的只有这两个**启动入口**。\n\n## 改动\n- launch.json 四个配置:后端补 AUTH_JWT_SECRET + LOCAL_IDENTITY_SWITCH_ENABLED\n- web 侧现签引导令牌:本地身份切换接口自己也要验签,没有引导令牌就是先有鸡还是\n 先有蛋。令牌用 contracts 的 signAccessToken 现签——**与后端同一份密钥、同一个\n 函数**,不另造第二条签发路径(那正是 P1 禁止的双真源形态)\n- 加 `test -n \"$NEXT_PUBLIC_AUTH_TOKEN\"`:签发失败当场退出,不让「令牌为空」\n 静默退化成满屏 401——那种失败看起来和「没登录」一模一样,最难判责\n- 顺手修 api-nestjs 的 `port` 字段:原为 3099,与实际监听的 3097 对不上\n- README 第 3 步补 AUTH_JWT_SECRET / NEXT_PUBLIC_AUTH_TOKEN 及其 fail-closed 语义;\n 命令表补 4 个已存在却从未登记的门禁(api-routes / approval-payload /\n workspace-hygiene / gate-selftest)\n\n## 证据(本地工作区,Fastify + PG:55432 + Redis:6382)\n- /api/health → `{\"status\":\"ok\",\"checks\":{\"database\":\"up\",\"redis\":\"up\"}}`\n- 无令牌 GET /api/quotations → 401;签名令牌 → 200 带真实数据;\n 令牌改一个字符 → 401(验签真的参与判定,不是摆设)\n- 浏览器 http://localhost:3098:demo-tenant 工作台渲染,KPI ¥2,733,893.60 / 5 份报价,\n 实时通道在线,本地身份切换 10 个角色可用,控制台零报错\n- `pnpm check` exit 0\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-02T18:24:48-07:00"},{"Sha1":"44a51eebab6962f641bdf6d95eb2742d082d0a32","Message":"fix(gate): 捡回工作区卫生门禁(C28)——check:ui 的 tsconfig 残留会自愈\n\n`34635aa` 是 2026-07-31 落在一个 detached worktree 里、**从未合入也未被重新实现**的\n改进。经核查:三个脚本文件在 main 血脉里全都不存在,棘轮无 workspaceHygieneViolations,\n而它修的 bug 至今仍在(restoreWebTypeArtifacts 仍是快照式还原)。故 cherry-pick 捡回。\n\n## 解决的问题\ncheck:ui 为规避 manifest/EMFILE 假 404(C17)给 Next 用独立 distDir `.next-ui-\u003cpid\u003e`,\nNext dev 会把该路径写进**被跟踪的** apps/web/tsconfig.json。\n此前的备份/还原是**快照式**——把启动前就存在的残留原样写回,永不自愈;\n而残留恰恰来自还原逻辑捕获不到的退出路径(SIGKILL / 并发轮次互相复活死条目)。\n本仓有并发会话共用 checkout,这条路径不是假想。\n\n## 修法两层\n- runner 还原后**无条件剥离** `.next-ui-*`:干净工作区上是恒等变换,仍严格净零\n- 独立门禁 `check:workspace-hygiene` 作第二道防线,入棘轮\n 判据刻意收窄防假门禁:只查 include 不查 exclude(node_modules 天然 gitignore,\n 一并查会注册即红,重蹈 C1);合法性只看首个路径段而非字面量白名单,\n 以免随 Next 版本增删而变成维护跑步机\n\n## 落地时的两处调整\n1. **编号 C25 → C28**:并行工作里已有另一个 C25(孤儿组件 `OrdersPanel`/`UsersPanel`),\n 撞车。按「编号只用于定位、不复用」的约定改为空号 C28。\n2. **只取代码不取报告**:原提交带的 baseline 与 20 份 report 快照已全部过期\n (gateSelftests 地板当时 2→3,现已是 6),直接取会把棘轮改坏。\n 报告全部还原为当前 HEAD,改由本轮重跑生成。\n\n## 门禁上线即抓到 5 处真实残留\n`.next-manual-10560` / `.next-ui-{49228,61102,94609}` / `.next-dev-3098`\n——比原提交记录的面更广:除 check:ui 外,手册截图 runner 与开发服务器同样会写入。\n已清理;runner 侧自愈只覆盖 `.next-ui-*`,其余 runner 若再产生仍由本门禁兜住。\n\n## 证据(本地工作区)\n`pnpm check` exit 0(含新门禁);`check:gate-selftest` 7 组负向全绿;\n真实链路负向:注入 `.next-ui-99999` → exit 1,移除后 → exit 0。\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-02T18:13:12-07:00"}],"HeadCommit":{"Sha1":"9f1ea88bcf8f55b6a1d25f0c5e5d0e282fd09a56","Message":"fix(dev): 本地启动链补齐 G8 验签所需 env——launch.json 与 README 都停在验签之前\n\n## 症状\n`preview_start` 起 web 后页面能开、功能全死:每个 API 请求 500 AUTH_NOT_CONFIGURED。\n(同时出现的 corepack `Invalid package.json` 是另一回事——那三次重试恰好撞在\n上一个 cherry-pick 的冲突窗口里,`package.json` 当时带着冲突标记,确实不是合法 JSON。\n冲突收干净后自动消失,不是配置问题。)\n\n## 根因\n`.claude/launch.json` 与 README 的「快速开始」都写于 G8 落地之前。\nG8 之后两个后端每请求都要 verifyAccessToken,缺 AUTH_JWT_SECRET 即 fail-closed 500\n——刻意不回 401:密钥没配是服务端故障,回 401 会把运维问题伪装成调用方的凭证问题。\n而 `.env.example` 双端其实都已补过密钥,漏的只有这两个**启动入口**。\n\n## 改动\n- launch.json 四个配置:后端补 AUTH_JWT_SECRET + LOCAL_IDENTITY_SWITCH_ENABLED\n- web 侧现签引导令牌:本地身份切换接口自己也要验签,没有引导令牌就是先有鸡还是\n 先有蛋。令牌用 contracts 的 signAccessToken 现签——**与后端同一份密钥、同一个\n 函数**,不另造第二条签发路径(那正是 P1 禁止的双真源形态)\n- 加 `test -n \"$NEXT_PUBLIC_AUTH_TOKEN\"`:签发失败当场退出,不让「令牌为空」\n 静默退化成满屏 401——那种失败看起来和「没登录」一模一样,最难判责\n- 顺手修 api-nestjs 的 `port` 字段:原为 3099,与实际监听的 3097 对不上\n- README 第 3 步补 AUTH_JWT_SECRET / NEXT_PUBLIC_AUTH_TOKEN 及其 fail-closed 语义;\n 命令表补 4 个已存在却从未登记的门禁(api-routes / approval-payload /\n workspace-hygiene / gate-selftest)\n\n## 证据(本地工作区,Fastify + PG:55432 + Redis:6382)\n- /api/health → `{\"status\":\"ok\",\"checks\":{\"database\":\"up\",\"redis\":\"up\"}}`\n- 无令牌 GET /api/quotations → 401;签名令牌 → 200 带真实数据;\n 令牌改一个字符 → 401(验签真的参与判定,不是摆设)\n- 浏览器 http://localhost:3098:demo-tenant 工作台渲染,KPI ¥2,733,893.60 / 5 份报价,\n 实时通道在线,本地身份切换 10 个角色可用,控制台零报错\n- `pnpm check` exit 0\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-02T18:24:48-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/90cb8a4111b88f700b2dba5913e7a1e29c9666b6...9f1ea88bcf8f55b6a1d25f0c5e5d0e282fd09a56","Len":2}...
|
1785720302
|
Edit
Delete
|
|
20306
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/g8-verified-auth
|
0
|
{"Commits":[{"Sha1":"4e06b6263 {"Commits":[{"Sha1":"4e06b62635319faead311e0b6799c2e47877f859","Message":"feat(fulfillment): 履约五模块 UI 优化——分批发运、采购看板、交接中文标签与终态确认\n\n- 契约单源:新增 SHIPMENT_STATE_LABELS 与 CONTRACT_REQUEST_STATUS_LABELS,\n 补齐发运/合同状态此前缺失的中文标签定义\n- 履约协同:订单卡显示下单时间与「当前责任人」指引(与安装页口径一致)\n- 采购协同:消费服务端 /purchase-orders/summary 聚合做看板指标(不在浏览器\n reduce,C27 口径);补加载/空态;四段进度条与明细待收展示\n- 物流发运:发运表单支持逐行下调本批数量(分批发运蓝图承诺补齐 UI 端);\n 新增「发运记录」区展示含已送达在内的全部发运单;配货清单补加载/空态\n- 技术履约:工单生成/开始/完成时间恒显(此前仅完工后可见)\n- 客服交接:发运/安装/合同状态全部改用 contracts 中文标签(消除英文枚举\n 直出);新增服务进度时间线;「关闭服务」终态动作补二次确认;显示交接备注\n\n证据:pnpm check 19 项全绿;contracts 240/240;check:ui 12/12 全绿\n(履约闭环用例覆盖新版发运表单/采购流转/交接卡;此前并行红项未复现)。\nCLAUDE.md 动态区证据行已同步。\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-02T19:32:04-07:00"}],"HeadCommit":{"Sha1":"4e06b62635319faead311e0b6799c2e47877f859","Message":"feat(fulfillment): 履约五模块 UI 优化——分批发运、采购看板、交接中文标签与终态确认\n\n- 契约单源:新增 SHIPMENT_STATE_LABELS 与 CONTRACT_REQUEST_STATUS_LABELS,\n 补齐发运/合同状态此前缺失的中文标签定义\n- 履约协同:订单卡显示下单时间与「当前责任人」指引(与安装页口径一致)\n- 采购协同:消费服务端 /purchase-orders/summary 聚合做看板指标(不在浏览器\n reduce,C27 口径);补加载/空态;四段进度条与明细待收展示\n- 物流发运:发运表单支持逐行下调本批数量(分批发运蓝图承诺补齐 UI 端);\n 新增「发运记录」区展示含已送达在内的全部发运单;配货清单补加载/空态\n- 技术履约:工单生成/开始/完成时间恒显(此前仅完工后可见)\n- 客服交接:发运/安装/合同状态全部改用 contracts 中文标签(消除英文枚举\n 直出);新增服务进度时间线;「关闭服务」终态动作补二次确认;显示交接备注\n\n证据:pnpm check 19 项全绿;contracts 240/240;check:ui 12/12 全绿\n(履约闭环用例覆盖新版发运表单/采购流转/交接卡;此前并行红项未复现)。\nCLAUDE.md 动态区证据行已同步。\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-02T19:32:04-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/9f1ea88bcf8f55b6a1d25f0c5e5d0e282fd09a56...4e06b62635319faead311e0b6799c2e47877f859","Len":1}...
|
1785724341
|
Edit
Delete
|
|
20363
|
5
|
5
|
7
|
72
|
0
|
0
|
refs/heads/hljTest
|
0
|
|
1785740632
|
Edit
Delete
|
|
20365
|
5
|
5
|
7
|
72
|
0
|
0
|
refs/heads/hljTest
|
0
|
{"Commits":[{"Sha1":"d2c99c9fc {"Commits":[{"Sha1":"d2c99c9fc71e8a1c48d1aa3684c30ead3eb110c0","Message":"chore(dockerfile): drop leftover debug prints from prisma cp step\n\n前次 commit 进了 set -x + 多处 ls -la 调试输出。功能等价,纯粹清理。\ncp 失败时 echo message 从 \"cp ok for X\" 改成 \"cp prisma client for X: ok/FAIL\"\n以匹配同文件其他步骤的可读风格。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"hlj@g-hi.com","AuthorName":"huangliujian","CommitterEmail":"hlj@g-hi.com","CommitterName":"huangliujian","Timestamp":"2026-08-03T15:03:44+08:00"},{"Sha1":"f97d67dbc0d53eeb203d205164c493a7b30a1116","Message":"chore: ship deployment integration + VM env layering\n\n合并远端 main 36 个新提交,并把本机部署整合的 basePath + BASE 双链路方案\n一起入仓;VM 镜像里 apt 镜像源抽成 APT_MIRROR build-arg,业务层/环境层解耦。\n\n业务/部署整合(来自 basePath 整合方案,对应 CLAUDE.md 真源地图「整合到\ngeneration.g-hi.com 的地址口径」):\n\n- apps/web/next.config.ts: basePath '/jh'(与 songGeneration/voicePrintRecognition\n 同款),同时取并集远端的 output: 'standalone' + outputFileTracingRoot\n- apps/web/src/hooks/useRealtime.ts: SSE_BASE = BASE;defaultWebSocketUrl 区分\n 绝对 URL / 相对前缀两种情况\n- apps/web/src/lib/api.ts: BASE = NEXT_PUBLIC_API_URL || NEXT_PUBLIC_BASE_PATH ||\n ''(必须 || 不是 ??,build 时 env 是字符串 '' 非 null,?? 不会把空串当\n fallback),USER_ROLE 改 runtime(data-user-role)来自 layout.tsx\n- deploy/vm/nginx.conf: gateway 加 = /jh/ws { proxy_pass http://api-nestjs:3001/ws; }\n (显式 URI 剥 /jh 前缀,转发到 NestJS WsAdapter 实际注册路径 /ws);/api/ /sse/\n 保留以便本机直连 8180(无前缀)调试\n- deploy/vm/compose.yaml: 与远端取并集(migrate 拆 target / non root / 资源\n 上限 / 日志轮转)\n- scripts/vm-deploy.sh: 显式注入 NEXT_PUBLIC_BASE_PATH 给 web 构建;与远端的\n IMAGE_TAG 时间戳机制取并集\n\n环境分层(Mac / 海外 CI / 国内 GFW 互不影响):\n\n- deploy/vm/Dockerfile: 硬编码的 aliyun 源 sed 抽成 ARG APT_MIRROR=;\n vm-deploy.sh 自动从 .env 注入所有 build target;空 = 走 deb.debian.org 官方源\n- deploy/vm/.env.example: 文档化 APT_MIRROR 留空行为\n- 理由:之前硬编码 mirrors.aliyun.com 在 Mac/海外 CI 上 build 会做无用 sed\n 且让外人看不出这是环境特化;现在抽成 build-arg 任何环境只需一行配置\n\n部署 bug 修(不分环境,本机 pnpm 9.15 行为差异):\n\n- deploy/vm/Dockerfile: 删 @repo/contracts 的 prepare 钩子(pnpm 9.15 跨\n workspace install 找不到子目录的 tsconfig.json)\n- deploy/vm/Dockerfile: 删 pnpm deploy --legacy(pnpm 9.7+ 已删除该 flag)\n- deploy/vm/Dockerfile: deploy 之后 cp -a --remove-destination 强制覆盖 .prisma/\n client;deploy 阶段的 @prisma/client postinstall 会把真实 client 还原成\n 2KB 占位,缺 17MB libquery_engine 二进制,runtime 抛 \"did not initialize\"\n- deploy/vm/Dockerfile: web 镜像 public 目录用 RUN mkdir -p 兜底(apps/web 当前\n 无 public 资产,但 next standalone 仍可能因目录存在与否决定路由)\n- deploy/vm/Dockerfile: runtime-base 阶段加 APT_MIRROR 注入(同 build 阶段)\n- packages/contracts/package.json: 删 prepare 字段(Dockerfile 显式\n pnpm --filter @repo/contracts build 触发)\n\nCLAUDE.md 同步(远端 36 提交 + 治理增量 +222 行)。\n\n验证:\n- pnpm check exit 0(5/5 packages,绿;ownTests=19, ownTestCases=227)\n- 部署到本机 tag 20260803T055150Z 后 /api/health 200,DB+Redis 探活正常\n- 5 个 juhai-quotation 容器 healthy(postgres/redis/api-nestjs/web/gateway)\n\n未覆盖:22 个 reports/*.latest.json(机器产物,commit 后下次 check 即变 dirty)\n与 packages/contracts/node-compile-cache/(node 22 编译缓存)未入 commit。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"hlj@g-hi.com","AuthorName":"huangliujian","CommitterEmail":"hlj@g-hi.com","CommitterName":"huangliujian","Timestamp":"2026-08-03T14:34:32+08:00"}],"HeadCommit":{"Sha1":"d2c99c9fc71e8a1c48d1aa3684c30ead3eb110c0","Message":"chore(dockerfile): drop leftover debug prints from prisma cp step\n\n前次 commit 进了 set -x + 多处 ls -la 调试输出。功能等价,纯粹清理。\ncp 失败时 echo message 从 \"cp ok for X\" 改成 \"cp prisma client for X: ok/FAIL\"\n以匹配同文件其他步骤的可读风格。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"hlj@g-hi.com","AuthorName":"huangliujian","CommitterEmail":"hlj@g-hi.com","CommitterName":"huangliujian","Timestamp":"2026-08-03T15:03:44+08:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/7619cf79681794bf28a8dceeb606c9dcfec21a5c...d2c99c9fc71e8a1c48d1aa3684c30ead3eb110c0","Len":2}...
|
1785740632
|
Edit
Delete
|
|
20407
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/multi-role-assignment
|
0
|
|
1785765661
|
Edit
Delete
|
|
20408
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/multi-role-assignment
|
0
|
{"Commits":[{"Sha1":"744d532b8 {"Commits":[{"Sha1":"744d532b80ba3fd239cfbdd2b3700e3d73c08675","Message":"test(governance): 三级门禁全绿——runtime 396/396、UI 12/12,合同与多角色补齐验收\n\n本轮把欠了整条线的运行态证据补齐:静态 → 真实 DB → 浏览器三级全部实跑通过。\n\n## 合同域门禁(5 条断言 + 自检固化)\n`check:module-integration` 新增:渲染必须调 contracts 纯函数、清单必须走 contracts\n清洗、定稿必须校验金额、激活必须要求合同已定稿、合同列表必须有自己的读模型。\n5 条负向注入逐条实跑判红,并固化进 `scripts/lib/contract-integration.test.mjs`\n(fixture 隔离,不碰真文件;配「未注入 → 绿」基线用例,防止所有\"期望红\"\n以完全错误的理由通过)。gateSelftests 地板 6 → 7。\n\n其中「合同列表必须有自己的读模型」防的是真实发生过的缺陷:\n借用「待激活申请」options(只返 REQUESTED)会让财务一激活,刚签的合同就从界面消失。\n\n## 状态机不变量逼出的设计修正\n`contractMachine` 原本是 `DRAFT ⇄ FINALIZED` 的**无终态环**——合同永远走不完,\n而\"订单已激活则不可修改\"只是运行时守卫。contracts 的状态机不变量测试\n(\"从任一状态都能走到终态\")当场判红。加终态 `EFFECTIVE`,由财务激活时迁入:\n`revise` 从 EFFECTIVE 出发在**状态机层面就不存在**,运行时检查降为第二层。\n\n## 修掉一个我自己制造的严重问题\n早先用批量正则把测试里的 `role:` 改成 `roles:`,**误伤 153 处 `api()` helper 入参**。\n该 helper 收的是 `role`,`roles` 被静默忽略并回落成默认 ADMIN——\n**测试照样绿,但所有「某角色应当 403」的负例都退化成「ADMIN 当然 200」**。\n已全部回退。教训:批量正则改测试比改源码危险得多,源码有编译器兜底,测试没有。\n\n## 修掉一处静默降级(用户反馈两次)\n`draft()` 里 `template ? render(...) : \"\"`——取不到默认模板就给空正文,不报错不提示。\n而\"合同建出来了、正文是空的\"在界面上与\"模板没生效\"完全一样。改为:\n默认模板优先,没设默认但只有一个模板时直接采用;仍取不到时界面明确说明原因。\n另补「套用模板」选择器——此前 `templateId` 存了却从不重渲染,已有合同换不了模板。\n\n## 版式\n按反馈调整为:套用模板 → 合同正文 → 合同清单。\n打印改为只打这一份合同:`@media print` 内 5 条规则(全页隐藏 → 放行合同卡 →\n正文去滚动上限并允许换行 → 按钮与留证快照不打印),沿用配货单打印同一模式,\n含「window.print() 必须在原始 click 内同步调用」那条硬约束。\n\n## 三级门禁证据(本地工作区)\n- `pnpm check` exit 0;门禁自检 8 组\n- `pnpm check:runtime` **396/396**(地板抬至 380),双后端 HTTP 各 45/45\n- `pnpm check:ui` **12/12**,unexpected 0(地板抬至 12)\n- 新增双后端 HTTP 验收:未定稿激活 409 CONTRACT_NOT_FINALIZED、\n 改金额定稿 409 CONTRACT_TOTAL_MISMATCH、合同清单只有对客七列且不含 cost\n- 新增 E2E 真实界面链路:建模板 → 生成合同 → 断言正文按模板渲染 →\n 定稿 → 只读预览 → 激活\n\n## 顺带修复的真账\n多角色改造遗留:两后端测试夹具签单值 role、fromRole/toRole 断言、\nNestJS api() helper 签发、E2E 建账号入参、合同外键 Restrict 导致的清理顺序。\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-03T06:17:45-07:00"},{"Sha1":"0e13b856b65685957296087a642c46a9b57c8d0a","Message":"feat(contract): 合同管理界面——模板维护、清单改写、预览与定稿\n\n## 界面三段\n- **模板维护**:正文编辑 + 可用变量清单(真源是 contracts 白名单,不手抄);\n 默认模板里乙方那几行留了「请填写」占位提示,免得有人以为忘了配变量\n- **合同面板**:清单可改名称/规格/单位/备注,**单价与金额只读**——\n 金额与报价锁死,改写法不改钱,只读比\"改了再被服务端拒绝\"少一次挫败\n- **定稿后**:正文转只读预览 + 打印 + 撤回定稿;留证快照可展开查看\n\n## 浏览器验证时抓到一个真实缺陷\n最初用「待激活申请」的 options 驱动合同列表——而它只返 `status=REQUESTED`。\n结果是**财务一激活,刚签的合同就从界面上凭空消失**。\n补了 `GET /api/contracts` 作为合同自己的读模型(两后端对等),\n视图改为「已有合同 + 尚未起草的待激活申请」两段驱动,后者按 contractRequestId 去重。\n\n这个 bug 单测抓不到,也不会有任何红盘——它只在\"数据恰好处于某状态\"时显形,\n而 dev 库里正好一条 REQUESTED 都没有,所以一打开就是空白。\n\n## 前端不做第二份渲染\n预览直接显示服务端渲染好的 `body`,前端**没有任何占位符处理**。\n在前端补一套 replace 会变成第二份渲染实现(G17 漂移形态):\n两边看起来一样,直到某天对空值的处理不同,界面上好好的合同打印出来金额是空的。\n\n## 证据(本地工作区)\n- `pnpm check` exit 0;全仓 typecheck 0 错误\n- 浏览器实测:模板维护渲染、合同面板 1 个、状态「已定稿」、清单 1 行、\n 合计 ¥660000.00、预览/撤回定稿/打印按钮齐全(截图见会话)\n- `GET /api/contracts` 返回真实合同(编号、报价号、客户名、合计、定稿时间)\n\n## 未做\nE2E 用例、两后端 HTTP 验收、`check:module-integration` 合同断言均未补(#31)。\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-02T23:19:14-07:00"},{"Sha1":"ef882dee3f2d670038bff1151a04e6cb4a4d3d45","Message":"feat(contract): 合同管理落地——模板、清单清洗、预览、定稿快照与激活前置\n\n动手前先证伪了「优化完善」这个词:**本仓此前没有合同**。\n`ContractRequest` 只是流程凭证(状态 + 报价引用 + 拒绝留痕),没有正文、条款、\n甲乙方、签署;「合同管理」视图做的是财务把报价激活成订单,本质是审批队列。\n所以这是新建业务对象,不是加按钮。\n\n## 用户裁决(五条)\n模板+变量填充 / 定稿即快照 / 仅内部预览打印 / 乙方写进模板正文 / 定稿为激活前置。\n另就清单追加两条:只改写法金额锁死 / PUBLIC 附件归入主行备注。\n\n## 合同清单与报价清单是两份不同明细\n报价行 26 列里 9 列是内部的——成本价、底价、成本合计、低于底价标记、调价原因、\n标准价、三种折扣构成。投影**重建对象字面量**而不是删字段,成本列在结构上进不来\n(单测断言 Object.keys 恰为七列,并扫描序列化结果确认成本数字不出现)。\n最隐蔽的一条是 `QuoteLineAccessory.visibility=INTERNAL`:「别显示成本」人人记得,\n「别显示内部附件」没人会想到,因为它藏在子表里。\n\n**金额锁死**先验了算术前提才敢定:dev 库 `sum(quote_lines.total_cents)` 恒等于\n`quote_options.total_cents`,差额全为 0,否则这条不变量会一上线就误杀所有合同(C1 同型)。\n校验放在**定稿**而非每次保存——草稿期正在合并行,中间态必然对不上,\n每次都校验会让「合并两行」这个动作根本做不完。\n\n## 渲染两层 fail-closed\n未知变量、取不到值的变量都抛错并点名。理由:一份看起来正常、金额那行却空白的合同\n被打印去签,比当场报错危险一个量级。另单独检查未闭合 `{{`——它会被正则整段跳过,\n不专门查就会原样印进合同且无人报错。\n\n## 真实链路验证抓出两个 bug\n① `variablesSnapshot` 恒为空:起草时正文已渲染,到定稿时里面早没有 `{{}}` 可提取。\n 改为快照**全部可解析变量**——留证本就该全记,不该取决于模板作者当初印没印。\n② Fastify 路由多写了 `/api` 前缀(注册作用域已带 prefix),实际成了 `/api/api/...`。\n\n## 又踩了一次同字面量陷阱\nNestJS 激活前置最初插错位置——「仓库不存在」那段在文件里出现多次,\n`replace` 只换第一处,守卫落进了 `createStocktake`(盘点)。\n已改为断言锚点唯一性后再插入。CLAUDE.md 点名过两次的坑,我这是第三次踩。\n\n## 证据(本地工作区,Fastify 真实链路)\n- `pnpm check` exit 0;contracts 单测 302(新增 22 条合同用例)\n- 模板:经理可建 201 / 销售 403 / 未登记变量 409 点名「甲方开户行」\n- 合同:起草生成 1 行清单(七列,含\"含:附件\"备注)、正文变量全替换、大写金额正确\n- 定稿:改金额 → 409 报出差额 -10000.00 元;销售定稿 403;正确金额 → FINALIZED\n- 撤回定稿:订单已激活 → 409 ORDER_ALREADY_ACTIVATED\n- 缺税号时起草 → 409 MISSING_VALUE 点名「甲方税号」(真实数据触发,非构造)\n- 留证快照 12 项(缺签署人两项因该报价未走客户接受,属正确 fail-closed)\n\n## 未做(不隐藏)\n前端界面(模板维护/预览/修改)未做;两后端 HTTP 验收测试与 E2E 未补;\n`check:runtime`/`check:ui` 本轮未跑;激活前置只在代码与单测层验证,\n未构造完整「未定稿→拒绝激活」的真实 HTTP 流程(dev 库无待激活申请,\n且 quotation_id 唯一约束挡住了造数)。\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-02T23:07:37-07:00"},{"Sha1":"e0b780d18833713a675d8903f407bd7afd55e650","Message":"feat(auth): 多角色并存——一个账号可持多个角色,能力取并集,互斥组合硬禁止\n\n用户裁决两条:① 多角色并存(而非租户自定义角色);② 互斥组合硬禁止(而非仅警示)。\n\n## 为什么是多角色而不是自定义角色\n备选方案是「租户自建角色 + 勾选任意 capability 子集」。不选它只有一条理由但足够硬:\n那会把「谁能做什么」的真源从 ROLE_PERMISSION_MAP **搬进数据库**,\n`check:role-permissions` 里「数据库不另存权限副本」这条核心断言随即失效,\n排查越权要同时看代码和数据。多角色只在 DB 存**角色分配**,真源不动,门禁继续有牙。\n代价是表达力:拼不出「销售但能看成本」这种现有角色组合不出的能力集。**已知取舍,非遗漏。**\n\n## 互斥矩阵为什么按角色对而不是 capability 对\n按能力对看着更本质,实则注册即红:`FINANCE` 单角色本身就同时持有 `payment.record`\n与 `payment.confirm`,门禁上线当天就判自己违规(C1 同型:扫到自身定义源)。\n故定义在角色层,并由 `it.each(MANAGED_USER_ROLES)`「每个单角色都不触发冲突」常驻断言守住。\n三组:采购三责分离 / 收款与发货放行 / 财务发起与审批;ADMIN 须单独持有。\n**这挡不住 ADMIN**——它持全部 capability,三责分离对它本就不成立,\n所以审批动作仍按 actor 硬校验 `approver !== requester`,两层缺一不可。\n\n## 删掉 requestRole 是本次最关键的决定\n`hasRolePermission` 改为同时接受单角色与角色集,好让 185 处调用点逐字不变。\n但这带来一个隐患:若保留 `requestRole`/`RequestRole` 单值访问器,\n调用点会**照常编译通过、静默只按主角色授权**——并集悄悄失效,零红盘。\n删掉它,编译器逼出 122+29 个错误,一个都跑不掉。\n\n编译器抓不到的恰是最危险的一处:`lockActiveAdmins` 的裸 SQL `\"role\" = 'ADMIN'`。\n它绕过 Prisma 类型检查;若当初保留 role 列做「兼容」,这句会静默匹配陈旧数据——\n**最后管理员保护看似还在,锁的却是错的行**。这是坚持 DROP COLUMN(并按门禁写\n`migration-allow` 留痕)的真正理由。计数侧同理:数组列上等值查询恒不匹配,\n`count` 恒为 0 会让保护永远放行。\n\n## 顺带修掉一个存量 bug\n两后端 `reverseAllocation` 把**角色名当操作人**存进 `reversedBy`(`reversedBy: role` → \"FINANCE\")。\n列是 String? 所以静默收下,反核销记录追不到人。多角色改造让它变成类型错误才暴露,已改传 actor。\n\n## token 与失效语义\nclaims `role` → `roles`,**无兼容分支**:留着它,只带 `role` 的旧令牌会走进「按单角色授权」\n的窄路径而调用方以为拿到并集,这种不一致比直接 401 难查得多。旧令牌一律 401 CLAIMS。\n受管理令牌回查用 `sameRoleSet` **集合语义**——按数组逐位比会让管理员调整勾选顺序\n就把在线用户全部踢下线(同 sameTagSet 那条教训)。\n\n## 迁移\n两端对等:users.role → users.roles、user_events 角色快照转集合,先回填再删旧列。\nDROP COLUMN 与 CREATE INDEX 均按门禁要求加 `-- migration-allow:` 留痕。\n\n## 前端\n账号页角色由单选下拉改多选勾选,实时显示并集能力数与互斥冲突——\n预览调 contracts 同一个 `previewRoleAssignment`,不在前端另写一套 if。\n**前端只提示不裁决**:绕过 UI 直接 curl 照样 400。\n\n## 证据(本地工作区)\n- `pnpm check` exit 0;contracts 273/273(新增 33 条多角色单测);全仓 typecheck 0 错误\n- 门禁负向已实跑判红:注入「退化成只按主角色授权」→ check:role-permissions exit 1;\n 注入「待办中心放行全集」→ check:module-integration exit 1;均恢复后转绿\n- 迁移在 dev 库实跑:单角色→单元素集合、0 个空角色集账号、旧列已删\n- 真实 HTTP 四条对抗:仓库+采购 400 点名冲突/空集 400/ADMIN 混搭 400/销售+技术 201\n- 浏览器:多选控件 10 角色、并集能力数实时更新、互斥冲突当场提示\n\n## 未做(不隐藏)\n`check:runtime` 与 `check:ui` 本轮未实跑;两后端多角色 HTTP 验收与 E2E 用例尚未补齐。\n动态区 runtime/UI 证据行仍是上一轮日期,未据本次改动更新。\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-02T22:26:56-07:00"}],"HeadCommit":{"Sha1":"744d532b80ba3fd239cfbdd2b3700e3d73c08675","Message":"test(governance): 三级门禁全绿——runtime 396/396、UI 12/12,合同与多角色补齐验收\n\n本轮把欠了整条线的运行态证据补齐:静态 → 真实 DB → 浏览器三级全部实跑通过。\n\n## 合同域门禁(5 条断言 + 自检固化)\n`check:module-integration` 新增:渲染必须调 contracts 纯函数、清单必须走 contracts\n清洗、定稿必须校验金额、激活必须要求合同已定稿、合同列表必须有自己的读模型。\n5 条负向注入逐条实跑判红,并固化进 `scripts/lib/contract-integration.test.mjs`\n(fixture 隔离,不碰真文件;配「未注入 → 绿」基线用例,防止所有\"期望红\"\n以完全错误的理由通过)。gateSelftests 地板 6 → 7。\n\n其中「合同列表必须有自己的读模型」防的是真实发生过的缺陷:\n借用「待激活申请」options(只返 REQUESTED)会让财务一激活,刚签的合同就从界面消失。\n\n## 状态机不变量逼出的设计修正\n`contractMachine` 原本是 `DRAFT ⇄ FINALIZED` 的**无终态环**——合同永远走不完,\n而\"订单已激活则不可修改\"只是运行时守卫。contracts 的状态机不变量测试\n(\"从任一状态都能走到终态\")当场判红。加终态 `EFFECTIVE`,由财务激活时迁入:\n`revise` 从 EFFECTIVE 出发在**状态机层面就不存在**,运行时检查降为第二层。\n\n## 修掉一个我自己制造的严重问题\n早先用批量正则把测试里的 `role:` 改成 `roles:`,**误伤 153 处 `api()` helper 入参**。\n该 helper 收的是 `role`,`roles` 被静默忽略并回落成默认 ADMIN——\n**测试照样绿,但所有「某角色应当 403」的负例都退化成「ADMIN 当然 200」**。\n已全部回退。教训:批量正则改测试比改源码危险得多,源码有编译器兜底,测试没有。\n\n## 修掉一处静默降级(用户反馈两次)\n`draft()` 里 `template ? render(...) : \"\"`——取不到默认模板就给空正文,不报错不提示。\n而\"合同建出来了、正文是空的\"在界面上与\"模板没生效\"完全一样。改为:\n默认模板优先,没设默认但只有一个模板时直接采用;仍取不到时界面明确说明原因。\n另补「套用模板」选择器——此前 `templateId` 存了却从不重渲染,已有合同换不了模板。\n\n## 版式\n按反馈调整为:套用模板 → 合同正文 → 合同清单。\n打印改为只打这一份合同:`@media print` 内 5 条规则(全页隐藏 → 放行合同卡 →\n正文去滚动上限并允许换行 → 按钮与留证快照不打印),沿用配货单打印同一模式,\n含「window.print() 必须在原始 click 内同步调用」那条硬约束。\n\n## 三级门禁证据(本地工作区)\n- `pnpm check` exit 0;门禁自检 8 组\n- `pnpm check:runtime` **396/396**(地板抬至 380),双后端 HTTP 各 45/45\n- `pnpm check:ui` **12/12**,unexpected 0(地板抬至 12)\n- 新增双后端 HTTP 验收:未定稿激活 409 CONTRACT_NOT_FINALIZED、\n 改金额定稿 409 CONTRACT_TOTAL_MISMATCH、合同清单只有对客七列且不含 cost\n- 新增 E2E 真实界面链路:建模板 → 生成合同 → 断言正文按模板渲染 →\n 定稿 → 只读预览 → 激活\n\n## 顺带修复的真账\n多角色改造遗留:两后端测试夹具签单值 role、fromRole/toRole 断言、\nNestJS api() helper 签发、E2E 建账号入参、合同外键 Restrict 导致的清理顺序。\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-03T06:17:45-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/4e06b62635319faead311e0b6799c2e47877f859...744d532b80ba3fd239cfbdd2b3700e3d73c08675","Len":4}...
|
1785765661
|
Edit
Delete
|
|
20409
|
5
|
5
|
5
|
72
|
0
|
0
|
refs/heads/feat/multi-role-assignment
|
0
|
{"Commits":[{"Sha1":"7a0c7fc47 {"Commits":[{"Sha1":"7a0c7fc472beebeaac8232aaff49889c3cb97fa0","Message":"chore(governance): 刷新报告与证据行,清理 tsconfig 临时产物残留\n\n- `apps/web/tsconfig.json`:C28 工作区卫生自愈剥掉 `.next-ui-14845` 残留\n (check:ui 的独立 distDir 会把该路径写进被跟踪的 tsconfig)\n- `reports/*.latest.json`:本轮 `pnpm check` 重跑产物,provenance 绑定当前工作区\n- `CLAUDE.md` 证据行:**含并行会话在同一 checkout 的更新**(客户勘测提交相关,\n 非本会话产出),已与本轮交付培训内容合并,未覆盖任何一方\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-03T10:40:43-07:00"},{"Sha1":"167d026f912b2b17c9e4380a5eb18d1d50130751","Message":"feat(fulfillment): 交付培训成为硬卡点——安装完成前使用培训、服务关闭前运维培训\n\n用户裁决两条:培训是**硬卡点**而非记录;由各自角色做、记录客方参加人,\n不引入客户在线签收(本仓无实名认证,凭链接签收只能是具名降级)。\n\n## 卡点做成结构约束,不是运行时 if\n- 安装:`IN_PROGRESS --deliver_training--\u003e TRAINING --complete--\u003e COMPLETED`\n- 客服:`IN_SERVICE --deliver_operation_training--\u003e OPERATION_TRAINING --close--\u003e CLOSED`\n\n⚠️ **安装工单此前根本没有状态机**——三个状态靠 `updateMany` 的 where 条件硬走,\n不受 assertTransition 与状态机不变量守护。本轮补齐 `installationMachine` 并登记进\n不变量表(11 条结构断言自动生效)。否则新卡点又是一条只靠 if、没有结构保障的规则。\n\n客服侧相反:它本来就走 assertTransition,所以在 contracts 插入 `OPERATION_TRAINING`\n态之后,「服务中直接关单」**一行服务端代码都不用改**就自动变成 INVALID_TRANSITION。\n这个对比正是状态机单源的价值。\n\n## 参加人必填是刻意的\n`attendees` 记的是**客方名单**——培训的价值在于「谁被教会了」,不是「谁去讲了」。\n讲师取已验签 actor,不由客户端填,免得填错或冒名。一场没有参加人的培训等于没培训,\n允许留空就等于允许走过场。\n\n## 过程中挖出两处真问题\n1. **动作白名单是双真源**:两端各有一份手抄的 `[\"accept\",\"start\",\"close\"]`,\n 与 contracts 的 `SERVICE_HANDOFF_EVENTS` 平行维护。加了新事件而拷贝不知道,\n 表现是 400「Unsupported action」——排查时很难联想到「白名单没跟上」。\n 已改为两端都读 contracts 事件全集。\n2. **Zod 静默剥字段**:`acceptServiceHandoffSchema` 没登记 `attendees`,\n 请求里填了也会被剥掉,服务端于是说「没填参加人」——**调用方明明填了却被告知没填**,\n 这类不一致最难自查。已在 contracts 补登记并注明。\n\n## 一处「类型绿但数据没出来」\n客服读模型是**白名单重建对象字面量**(C20:不得泄漏成本/毛利/审批意见),\n返回类型是推断的,漏投影新字段**不会有任何编译错误**。安装侧是泛型透传所以自动带上,\n客服侧必须显式列出。已补,并由 `handoff-training-projected-*` 断言守住。\n\n## 界面\n安装工单进度四段(待开始/现场安装/使用培训/客户验收),段位改为按状态映射——\n原来是硬编码 if 链,新增状态会静默停在旧段位。安装中显示培训表单,\n完工表单挪到培训之后;客服服务中显示运维培训表单,关单挪到培训之后。\n\n## 门禁(4 条断言 + 4 条负向注入)\n`installation-training-required-*`、`training-attendees-required-*`、\n`handoff-action-allowlist-single-source-*`、`handoff-training-projected-*`。\n另收紧一条既有断言:完工表单必须挂在 **TRAINING** 段(原来锚的是两个标记的邻近性,\n插入培训表单后被撑开而误报;真正该守的是「完工在培训之后」,锚这个既更强也不怕插内容)。\n自检从 5 条扩到 9 条,均实跑判红。\n\n## 三级门禁证据(本地工作区,PG:55432 + Redis:6382)\n- `pnpm check` exit 0;门禁自检 9 条负向注入\n- `pnpm check:runtime` **413/413**(地板 380→400)\n- `pnpm check:ui` **12/12**,unexpected 0;E2E 断言「安装中不应出现完工按钮」\n ——卡点只在服务端而界面仍能诱导用户走错路,同样是缺陷\n\n## 未做\n客服交接的 E2E 里没有关单步骤,**运维培训卡点缺浏览器级证据**(HTTP 层已覆盖)。\n本次 runtime 增量含并行会话在同一 checkout 写入的约 400 行 HTTP 验收,非本会话产出。\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-03T10:36:05-07:00"}],"HeadCommit":{"Sha1":"7a0c7fc472beebeaac8232aaff49889c3cb97fa0","Message":"chore(governance): 刷新报告与证据行,清理 tsconfig 临时产物残留\n\n- `apps/web/tsconfig.json`:C28 工作区卫生自愈剥掉 `.next-ui-14845` 残留\n (check:ui 的独立 distDir 会把该路径写进被跟踪的 tsconfig)\n- `reports/*.latest.json`:本轮 `pnpm check` 重跑产物,provenance 绑定当前工作区\n- `CLAUDE.md` 证据行:**含并行会话在同一 checkout 的更新**(客户勘测提交相关,\n 非本会话产出),已与本轮交付培训内容合并,未覆盖任何一方\n\nCo-Authored-By: Claude Fable 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"luoguoguo@gmail.com","AuthorName":"luoguoguo","CommitterEmail":"luoguoguo@gmail.com","CommitterName":"luoguoguo","Timestamp":"2026-08-03T10:40:43-07:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/744d532b80ba3fd239cfbdd2b3700e3d73c08675...7a0c7fc472beebeaac8232aaff49889c3cb97fa0","Len":2}...
|
1785778851
|
Edit
Delete
|
|
20362
|
7
|
5
|
7
|
72
|
0
|
0
|
refs/heads/hljTest
|
0
|
|
1785740632
|
Edit
Delete
|
|
20364
|
7
|
5
|
7
|
72
|
0
|
0
|
refs/heads/hljTest
|
0
|
{"Commits":[{"Sha1":"d2c99c9fc {"Commits":[{"Sha1":"d2c99c9fc71e8a1c48d1aa3684c30ead3eb110c0","Message":"chore(dockerfile): drop leftover debug prints from prisma cp step\n\n前次 commit 进了 set -x + 多处 ls -la 调试输出。功能等价,纯粹清理。\ncp 失败时 echo message 从 \"cp ok for X\" 改成 \"cp prisma client for X: ok/FAIL\"\n以匹配同文件其他步骤的可读风格。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"hlj@g-hi.com","AuthorName":"huangliujian","CommitterEmail":"hlj@g-hi.com","CommitterName":"huangliujian","Timestamp":"2026-08-03T15:03:44+08:00"},{"Sha1":"f97d67dbc0d53eeb203d205164c493a7b30a1116","Message":"chore: ship deployment integration + VM env layering\n\n合并远端 main 36 个新提交,并把本机部署整合的 basePath + BASE 双链路方案\n一起入仓;VM 镜像里 apt 镜像源抽成 APT_MIRROR build-arg,业务层/环境层解耦。\n\n业务/部署整合(来自 basePath 整合方案,对应 CLAUDE.md 真源地图「整合到\ngeneration.g-hi.com 的地址口径」):\n\n- apps/web/next.config.ts: basePath '/jh'(与 songGeneration/voicePrintRecognition\n 同款),同时取并集远端的 output: 'standalone' + outputFileTracingRoot\n- apps/web/src/hooks/useRealtime.ts: SSE_BASE = BASE;defaultWebSocketUrl 区分\n 绝对 URL / 相对前缀两种情况\n- apps/web/src/lib/api.ts: BASE = NEXT_PUBLIC_API_URL || NEXT_PUBLIC_BASE_PATH ||\n ''(必须 || 不是 ??,build 时 env 是字符串 '' 非 null,?? 不会把空串当\n fallback),USER_ROLE 改 runtime(data-user-role)来自 layout.tsx\n- deploy/vm/nginx.conf: gateway 加 = /jh/ws { proxy_pass http://api-nestjs:3001/ws; }\n (显式 URI 剥 /jh 前缀,转发到 NestJS WsAdapter 实际注册路径 /ws);/api/ /sse/\n 保留以便本机直连 8180(无前缀)调试\n- deploy/vm/compose.yaml: 与远端取并集(migrate 拆 target / non root / 资源\n 上限 / 日志轮转)\n- scripts/vm-deploy.sh: 显式注入 NEXT_PUBLIC_BASE_PATH 给 web 构建;与远端的\n IMAGE_TAG 时间戳机制取并集\n\n环境分层(Mac / 海外 CI / 国内 GFW 互不影响):\n\n- deploy/vm/Dockerfile: 硬编码的 aliyun 源 sed 抽成 ARG APT_MIRROR=;\n vm-deploy.sh 自动从 .env 注入所有 build target;空 = 走 deb.debian.org 官方源\n- deploy/vm/.env.example: 文档化 APT_MIRROR 留空行为\n- 理由:之前硬编码 mirrors.aliyun.com 在 Mac/海外 CI 上 build 会做无用 sed\n 且让外人看不出这是环境特化;现在抽成 build-arg 任何环境只需一行配置\n\n部署 bug 修(不分环境,本机 pnpm 9.15 行为差异):\n\n- deploy/vm/Dockerfile: 删 @repo/contracts 的 prepare 钩子(pnpm 9.15 跨\n workspace install 找不到子目录的 tsconfig.json)\n- deploy/vm/Dockerfile: 删 pnpm deploy --legacy(pnpm 9.7+ 已删除该 flag)\n- deploy/vm/Dockerfile: deploy 之后 cp -a --remove-destination 强制覆盖 .prisma/\n client;deploy 阶段的 @prisma/client postinstall 会把真实 client 还原成\n 2KB 占位,缺 17MB libquery_engine 二进制,runtime 抛 \"did not initialize\"\n- deploy/vm/Dockerfile: web 镜像 public 目录用 RUN mkdir -p 兜底(apps/web 当前\n 无 public 资产,但 next standalone 仍可能因目录存在与否决定路由)\n- deploy/vm/Dockerfile: runtime-base 阶段加 APT_MIRROR 注入(同 build 阶段)\n- packages/contracts/package.json: 删 prepare 字段(Dockerfile 显式\n pnpm --filter @repo/contracts build 触发)\n\nCLAUDE.md 同步(远端 36 提交 + 治理增量 +222 行)。\n\n验证:\n- pnpm check exit 0(5/5 packages,绿;ownTests=19, ownTestCases=227)\n- 部署到本机 tag 20260803T055150Z 后 /api/health 200,DB+Redis 探活正常\n- 5 个 juhai-quotation 容器 healthy(postgres/redis/api-nestjs/web/gateway)\n\n未覆盖:22 个 reports/*.latest.json(机器产物,commit 后下次 check 即变 dirty)\n与 packages/contracts/node-compile-cache/(node 22 编译缓存)未入 commit。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"hlj@g-hi.com","AuthorName":"huangliujian","CommitterEmail":"hlj@g-hi.com","CommitterName":"huangliujian","Timestamp":"2026-08-03T14:34:32+08:00"}],"HeadCommit":{"Sha1":"d2c99c9fc71e8a1c48d1aa3684c30ead3eb110c0","Message":"chore(dockerfile): drop leftover debug prints from prisma cp step\n\n前次 commit 进了 set -x + 多处 ls -la 调试输出。功能等价,纯粹清理。\ncp 失败时 echo message 从 \"cp ok for X\" 改成 \"cp prisma client for X: ok/FAIL\"\n以匹配同文件其他步骤的可读风格。\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n","AuthorEmail":"hlj@g-hi.com","AuthorName":"huangliujian","CommitterEmail":"hlj@g-hi.com","CommitterName":"huangliujian","Timestamp":"2026-08-03T15:03:44+08:00"},"CompareURL":"luoanwu/juhai-quotation-system/compare/7619cf79681794bf28a8dceeb606c9dcfec21a5c...d2c99c9fc71e8a1c48d1aa3684c30ead3eb110c0","Len":2}...
|
1785740632
|
Edit
Delete
|