Conversation
- keys.ts:generateInstanceKeyPair(ES256/P-256,PKCS8+SPKI+公钥 JWK+ RFC 7638 thumbprint kid)、deriveSigningRuntime(私钥 PEM 同源派生 JWKS, kid 与 JOSE 头天然一致)、toPublicJwks(白名单字段,剥除私钥材料) - GET /.well-known/jwks.json 返回真实公钥;未配置 JWT_PRIVATE_KEY 回 503 - POST /api/admin/bootstrap-keygen:生成密钥对交部署者写入 wrangler secret (进程不持 CF 凭证、不落盘,§5.5) - 测试:PEM 互恢复、同源派生、JWKS 验签 jose 闭环、无私钥泄漏、换钥生效 Refs #2 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
- oidc.ts:OIDC discovery(15min 缓存 + issuer 校验)、授权码 PKCE(S256) 授权 URL 组装、callback 换 token + state/iss/aud/nonce/exp 全量校验 - session.ts:会话 token(payload.b64 + HMAC-SHA256,密钥由 JWT_PRIVATE_KEY 经 HKDF 派生,不引入第二个 secret)、HttpOnly/SameSite=Lax/Secure Cookie - 路由:GET /api/auth/login(302 整页跳 IdP)、GET /api/auth/callback (JIT 建档 + 签会话,next 白名单只收站内路径)、POST /api/auth/logout、 GET /api/me(会话态查询,shell 用) - OIDC 配置生产读 instance_config 表(setup 向导录入,§5.5), 开发可用 OIDC_ISSUER 等环境变量兜底;迁移 0002 补表 - 测试:PKCE S256 向量、fake IdP 全链路(login→callback→me→logout)、 state/nonce/iss/aud/exp 拒绝路径、会话篡改/过期/换钥拒绝 Refs #5 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
- setup.ts:token 生成/入库/消费(用后即焚)、setup_done 封死位、 首个管理员提权、审计留痕 - 路由:POST /api/admin/setup-token(部署脚本打印一次性链接, 已激活 409)、GET /api/setup/status(#10 向导页状态查询)、 POST /api/setup/activate(token+会话双校验 → 登记管理员 → 永久封死) - 未登录激活回 401 + loginUrl(next 带回 token,整页跳 OIDC 动线 §6.5) - 测试:激活全链路(生成→未登录 401→激活→admin 诞生→重放被拒→ 封死后拒发新 token→审计留痕)、伪造 token 403、缺 token 400 Refs #6 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
ModuleStorage 实装工厂 createD1Storage: - 模块 Worker 持 MODULES_DB 绑定 + moduleId,SDK 收口访问,模块拿不到裸连接 - 单表 module_kv(module_id, key, value, PK(module_id, key)),module_id 列隔离, SQL 恒带 moduleId 条件;键守卫:key 含保留分隔符 ':'(other:xxx / evil:: / 自带本模块前缀等注入形态)一律 throw - db 用最小结构类型(prepare/bind/first/all/run),不引入 @cloudflare/workers-types - get 返回 string|null;put 覆盖写(ON CONFLICT DO UPDATE);delete 幂等; list(prefix?) 前缀 LIKE(通配符转义)按 key 排序,返回模块子域裸键 hello 迁移:modules 库共享表 module_kv + 索引(0001_module_kv.sql)。 测试:内存假 D1(Map 后备)覆盖正常路径/覆盖写/list 前缀与通配符/跨前缀抛错/ 多模块隔离/非法 moduleId 与表名。 验收对照(issue #9):hello 写 hello_counter 的页面集成归 #13; 本 PR 提供守卫实装与单测,SDK 拒绝任何跨前缀写入(试写别人前缀被拒)。 Refs #9 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
- ready(): 向壳发 {type:'ready'} 握手;waitForToken() 仅接受 event.origin ===
coreOrigin 的 token 消息,同源但形状不符(非 token / token 非字符串)一律忽略
- 安全默认收紧:未配置 coreOrigin 时 waitForToken / startTokenLoop 直接
reject / throw(不再沿用 '*' 通配语义),注释写明理由;出站消息保留
'*' 兜底(握手惯例:发向宿主页无法预知对方 origin)
- navigate(path) / notify(title, body?) / theme(mode) 按 messages.ts 契约
发消息;theme mode 编译期限定 'light' | 'dark',运行时二次校验
- 静默续期循环:按 exp-iat 计算 TTL,到期前 min(2 分钟, TTL/3) 复用
ready 握手触发壳换发新 token(约定:壳收 ready → POST
/api/modules/:id/token → postMessage 回发新 token);startTokenLoop /
stopTokenLoop 幂等,新 token 经 onToken 回调并重新调度;已过期 token
立即重握手
- decodeContext(token) 保留(ModuleTokenClaimsSchema.parse,不验签)
- 测试:握手 / origin 与形状拒绝 / 四类消息发送 / 静默续期(vitest fake
timers:10 分钟与 3 分钟 token 的触发时点、续期后重排、非信任 origin
不重排、stop 摘除)/ decodeContext
Refs #8
Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
- contracts 新增 ModuleLifecycle / ExportBundle 类型与 ExportBundleSchema (PRODUCT_SPEC §5.4:export() 按表前缀全量导出,purge() 彻底清除) - ModuleManifestSchema 增可选 icon 字段:^[a-z0-9-]+$,对应 Lucide 图标名 (§5.1/§6.5:拒绝 emoji,壳白名单映射渲染) - module-sdk 存储接口注释与 lifecycle 契约对齐,新增 ModuleContext 形状 - 测试:icon 合法/缺省/emoji/非法字符,ExportBundleSchema 合法/非法用例, ModuleLifecycle 实现与 schema 校验闭环 Refs #1 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
…-pick 残留同前) Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
- registry.ts:ModuleRegistrationSchema(zod,manifest 快照经 ModuleManifestSchema 校验)、upsertModule(幂等 upsert,deploy 重跑收敛)、 toggleModule(RETURNING 原子翻转)、listModules、rowToEntry - 路由:POST /api/admin/modules(注册/upsert,201)、PATCH /api/admin/modules/:id/enabled(启停翻转,404/400 兜底)、GET /api/admin/modules(全量,管理端)、GET /api/modules(成员侧仅 enabled, #12 边栏数据源);requireAdmin 守卫(无会话 401/非 admin 403) - 迁移 0003:idx_module_registry_enabled(enabled, id) 补启停/列表扫描索引 - 启停语义闭环:enabled=0 → 边栏过滤(#12 读 GET /api/modules)+ 新 token 拒发(/api/modules/:id/token 的 checkTokenGate 读同表)+ 存量 token 10 分钟自然过期(§5.2/§5.5) - 管理动作写 audit_log(module_upserted / module_enabled / module_disabled) - core-api 增加 zod 依赖(registry 请求体校验,与 contracts 同版本) - 测试:注册/幂等 upsert/翻转/404/成员视角过滤/401+403+400 拒绝/审计留痕 Refs #7 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
- SetupView:三字段表单(OIDC issuer / Client ID / Client Secret)+
[测试连接] 现场拉发现文档,失败就地报错(§6.5 动线)
- 无令牌访问:仅提示「需要部署时输出的激活链接」,不可配置
- [保存并激活]:一次性 token 调 /api/setup/activate;401 时整页跳 OIDC
(next 带回 token),成功后 router.replace('/') 直接进工作台不停留
- 已激活后 /setup 一律重定向:router.beforeEach + decideSetupAccess
纯函数守卫(已登录→工作台,未登录→登录页),页面不复存在
- setup-api.ts:setup 状态/激活/测试连接客户端;错误人话化
(§6.5 三层透传:人话 + request id + 技术详情折叠)
- 基元落地 packages/ui:UButton/UInput/UCard/UErrorCard(查找序
beUI→shadcn-vue→手写;beUI 为 React/motion 无 Vue 等价,动效参数
照抄语义以 CSS transition/keyframes 等价实现);tokens.css 单一来源,
Tailwind @theme 映射;图标 lucide-vue-next
- shell 引入 vue-router(/、/login 占位、/setup);中文、亮色单主题、
手机可用(单列自适应 + 触控目标 ≥40px)
Refs #10
Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
PR #35 把 ui 的 vite.config.ts 从 lib 模式改成了纯 client 模式, 导致 pnpm -r build 找不到 index.html(CI run 34117407034 失败根因)。 保留 vue() 插件与 vitest 配置,恢复 library 构建; vue 与 lucide-vue-next 标记 external,不进产物。 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
- LoginView:实例名 + 单按钮「使用工作账号登录」,无密码框 (密码永远发生在 OIDC 提供方页面,§6.5);整页跳转 /api/auth/login?next=… - state/PKCE 校验失败等异常:人话「登录暂时不可用,联系管理员」 (UErrorCard + 重新登录按钮),不暴露原始报错 - 落地规则 landing.ts 纯函数:next 回原目标(防开放重定向,只收站内 绝对路径);直访落第一个启用模块;全部停用落工作台空态 (模块清单由 #12 工作台消费) - 会话态:installAuthGuard 全局 401 拦截 → 整页跳登录并带 next (排除 /api/auth/*、/api/me、/api/setup/* 防循环);已登录访问 /login 自动进工作台;登出按钮在 #12 侧栏用户区(调 POST /api/auth/logout 清会话) - 全仓 build 验证通过(含 packages/ui lib 模式,遵守 #36 教训) - 测试:landing 9 断言(防开放重定向/回原模块/第一启用模块/空态回退) Refs #11 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
- App.vue 重写为工作台壳('/' 路由组件,main.ts 改 RouterView 根): 桌面 = 220px 左栏(顶部实例名 / 中部模块列表 / 底部用户区+退出), 无顶栏无首页;窄屏 = 底部标签栏(同一份 nav 数据两个渲染器,nav.ts 保持纯函数,isHostView/firstEnabledModule 新增) - 边栏数据源 = GET /api/modules(registry-api.ts,成员视角仅 enabled, 停用即消失 §5.5);直访 / 落第一个启用模块(#11 landing 规则), 全部停用显示「还没有启用任何模块」空态 - iframe 装载器:manifest.entry → moduleFrameSrc(同域相对路径 / 第三方绝对 URL,§5.3 逃生口);module-bridge.ts 壳侧 SDK 握手桥 (ready → POST /api/modules/:id/token → postMessage targetOrigin 锁定 frame origin;#8 SDK 静默续期重发 ready 自动续供) - 模块图标:manifest icon → lucide-vue-next 白名单映射(module-icon.ts, 16 个常用图标名),缺省/未知回退模块名首字;无 emoji(§6.5) - 六种异常态(§6.5 异常规范):会话检查/清单加载/握手 15s 超时骨架屏、 清单失败卡、模块停用卡、加载失败卡(人话 + request id + 技术详情折叠 + 重新加载) - 登出按钮在侧栏底部用户区(POST /api/auth/logout 清会话回登录页,#11) - 权限真值只在服务端会话:进壳先 fetchMe,未登录 302 /login?next=… - 全仓 pnpm -r typecheck / test / build 六包全绿(含 ui lib 构建) - 测试:nav 7(双形态数据源/停用消失/落地第一模块)+ registry-api 4 + module-bridge 4(origin 解析与防注入)+ module-icon 5(白名单/首字回退) Refs #12 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
后端: - Bearer 验签面:jose createRemoteJWKSet + CORE_JWKS_URL(真值来源 core-api /.well-known/jwks.json)+ aud=hello 锁定(防跨模块重放 §5.2)+ claims schema 校验;失败回 401 人话 + requestId(§6.5 不泄 jose 原始错误) - GET/POST /api/count:经 @unself/module-sdk createD1Storage(#9 前缀守卫) 读写 module_kv(module_id='hello', key='counter'),与 hello_counter 表 语义对齐(0001_init.sql);无 token/aud 错一律拒 - GET /life/export:返回 contracts ExportBundle 形状(version/moduleId/ exportedAt/tables/files);POST /life/purge:SDK 子域清空(§5.4 生命周期契约) 页面(最简,§6.5 hello 动线): - 身份行(token claims 姓名/邮箱)+ 计数显示 + [+1] 按钮并排; 样式 27 行全取 tokens 变量;viewport 手机可用 - 接 @unself/module-sdk:ready 握手 → waitForToken → startTokenLoop 静默续期(token 更新自动跟随) - module-sdk 补公开导出 createD1Storage/D1Minimal*(#9 实装的契约面, hello 作为第一消费方) 测试:假 JWKS(真 ES256 对)+ 内存 module_kv,9 例覆盖 401 人话/aud 锁定/ 计数持久/子域落键/claims 数据面/export 形状/purge 清空/UI 骨架 Refs #13 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
…1 步) - pnpm-workspace 增 deploy/*(#14 装配引擎进 workspace,三件套覆盖) - deploy/cloudflare 包骨架:package.json / tsconfig / README(九步映射表) - 仓库根 unself.config.jsonc 样例:domain 空→workers.dev、modules=[hello]、storage=r2 (OIDC 凭证不进本文件,§5.5 拍板;setup 向导录入存 core 库) - .gitignore 增 .deploy/(脚本生成的装配产物目录) 依据:PRODUCT_SPEC §5.5 装配引擎 = wrangler 幂等脚本,读唯一配置文件 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
- config.ts:零依赖 JSONC 剥离(注释/尾逗号,字符串感知)+ zod 校验 (domain 缺省空→workers.dev 回退;storage r2/s3 判别联合;模块 id 白名单正则) - wrangler.ts:执行器接口(run/tryRun)——真实实现 spawn 参数数组不走 shell; 测试注入录制型 fake;非零退出抛可读 WranglerError - es256.ts:generateInstanceKeyPair deploy 侧最小副本(jose ES256 + RFC 7638 kid), 不 import core 源码避免 Workers 类型泄漏进 Node 类型空间 - keypair.ts:secret 探测/写入(幂等关键:已有 JWT_PRIVATE_KEY 绝不重生成) - 单测 7 例:JSONC 剥离、s3/r2 变体、坏输入拒绝、manifest id 提取 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
- provision.ts:步骤① D1 list 查漏 → create(幂等);步骤⑥ R2 桶同构; s3 分支只校验外部参数不碰 CF(§5.5 ⑥「建 R2 桶或接收外部 S3 参数」) - registry.ts:步骤⑤ 与 core-api #7 同构 upsert 语义(ON CONFLICT DO UPDATE); manifest 快照 entry 重写为实例 URL(/m/<id>/,§5.3 单域名路径制); 未选模块 disable(enabled=0 = not_deployed);bind 参数化防注入 - assemble.ts:core 部署配置(真实 database_id + SPA fallback + run_worker_first API/JWKS/setup)与模块配置(/m/<id>/* 路由 + MODULES_DB 绑定 + CORE_JWKS_URL vars); 模块 wrapper 源码(挂载前缀剥除 + ASSETS 回退);shell dist 复用与拷贝 - 单测 13 例:幂等分支(已存在零创建)、快照 schema 校验、SQL 转义、 配置确定性(同输入字节级同输出,幂等前提) Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
- steps.ts:九步串接(PRODUCT_SPEC §5.5)—— ① D1 查漏 ② core+选中模块迁移 ③ shell 构建/配置/上传 ④ 模块打包部署/路由绑定 ⑤ registry upsert/disable ⑥ R2 查漏 ⑦ OIDC 无操作(setup 向导录入) ⑧ POST /api/admin/setup-token(409=已封死,摘要记 sealed)⑨ /api/health + /m/<id>/api/health 签名密钥:secret list 探测,缺失才本地生成一次并 stdin put(不落盘不进 argv), 已有则沿用(幂等核心:重生成会使存量 token/会话失效) - smoke.ts:setup token 拉取(人话错误)+ 冒烟(超时/非 200/缺 ok:true 均失败) - main.ts:CLI 入口,部署输出末尾打印一次性 setup 激活链接(§5.5 ⑧) - 单测 24 例:录制型 fake wrangler 连跑两次收敛(二跑零 create/零 secret put/ registry 终态一致)、九步顺序断言、冒烟失败报错、setup sealed 路径 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
页面 fetch('/api/count') 与 import '/sdk/module-sdk.js' 是根相对路径:
单域名路径制下模块挂载在 /m/hello/ 子路径,根相对路径会逃逸出模块前缀
打到实例根(404)。改为相对路径后同一路径制下部署(/m/hello/)与
开发期根路径两处皆可。
- 页面三处 fetch/import 改相对(api/count、./sdk/module-sdk.js)
- 补测试:断言页面无根相对 API/SDK 路径(防回归)
Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
… 服务端化(#44) - oidc.ts:接 issuer JWKS(jose createRemoteJWKSet + jwtVerify)验签 id_token, 算法白名单 RS/ES/PS 256/384/512,缺 jwks_uri / JWKS 拉取失败一律 fail-closed; 删除 decodeIdToken 伪解码路径,发现缓存 + JWKS 集合按 issuer 缓存(resetOidcCaches 供测试) - activate:核实确认此前未持久化向导录入的 OIDC 字段(登录只走 env 兜底); 现接受可选 body { issuer, clientId, clientSecret, scope? },zod 逐字段校验, UPSERT 写 instance_config(oidc_issuer 等 4 键,与 getOidcConfig 读取一致)+ 审计留痕 - 新增 POST /api/oidc/test-connection:服务端代理探测 issuer 发现文档, 浏览器直连被 IdP CORS 拦(Stalwart 实测),复用 discover() 失败统一 502 不透传内部细节 - shell:向导三字段随激活请求真正发出(此前落空),testOidcConnection 改走服务端代理, 跳登录前表单(除 secret)侧记 sessionStorage 回来恢复 - 测试:假 IdP 全部换真 RS256 签发 + JWKS 端点;新增验签通过/坏签名拒绝/ jwks_uri 缺失拒绝/持久化写库+审计/test-connection 200·400·502 等用例 验证:pnpm -r typecheck / test(184 全绿)/ build 全仓通过 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
真实账户首跑暴露的 v4 契约漂移,逐项修正: - d1 create:--json 不复存在,改解析 stdout uuid;「已存在」竞态回查列表自愈 - r2 bucket list / secret list:v4 无 --json,改文本解析 - 生成配置相对路径:wrangler v4 的 main/assets 相对「配置文件目录」, 不再相对仓库根(modules/<id>/worker.js 等) - run_worker_first '**/*':v4 要求路径以 / 开头,改布尔 true(语义等价) - d1 execute:--param 不复存在,binds 内联为 SQL 字面量(单引号翻倍) - d1 migrations apply:--local false 会被解析成 local=true,改 --remote; 包内 wrangler.jsonc 的 REPLACE_ME 无法迁移,新增迁移专用生成配置 - resolveBaseUrl:dry-run 输出无 workers.dev URL(#45③), 改为重放一次幂等 deploy 抓 URL - module worker 的 CORE_JWKS_URL:domain 空时用步骤③回填的真实 URL - module_registry 注册表写入目标库改为 unself-core(迁移建在 core 库) - wrapper/入口分离:worker.js=wrapper(main),app.js=bundle, wrapper 幂等重写;core 入口相对路径按生成目录推导 - WranglerError:stderr 为空时回显 stdout(v4 把 API 错误打到 stdout) - setup-token:fetch failed 给出 workers.dev 可达性提示 测试:25 用例全绿(新增迁移配置用例);全仓 190 用例通过。 Signed-off-by: huangyinghui <18666119673@163.com>
handywote.top(CF active zone)已就位;OAuth 认证方案对 zones/.../workers/routes 仅授予 Workers Custom Domains 路径 (workers_routes:write),PUT zone 路由报 10405。 - core:domain 有值 -> custom_domains:[domain] - 模块:domain 有值 -> custom_domains:[<id>.<domain>](子域式) - wrangler 自动建 DNS 记录与边缘证书,本机即可直连完成第 8/9 步 测试断言同步更新;25 用例全绿。Ref #15 Signed-off-by: huangyinghui <18666119673@163.com>
- 前缀剥除 wrapper:Hono 实例是对象非函数,直接调用在 workerd 抛 TypeError: index_default is not a function(模块 API 全 500), 改 worker.fetch(...),与 core 入口同款约定;真机 500→200 - 冒烟:custom domain 模式下模块在 <id>.<域名>(与 custom_domains 绑定一致),workers.dev 模式维持主域路径路由 - 恢复 #51 rebase 时丢失的 #50 合成(迁移配置生成、wrapper/入口 分离、jwksUrl 回填),测试断言同步 custom_domains 真机九步全通:双域名 health 200,setup token 签发成功。 Signed-off-by: huangyinghui <18666119673@163.com>
线上 404 定性(两处叠加): 1. run_worker_first 的 '/setup' 精确规则只对 pathname 全等生效, '/setup?token=…' 不匹配(本地 workerd 探针复现)→ 改 '/setup*'。 真机证据:/setuptest、/setup.js 由 Worker 接管(Hono 404), /mysetup、/x/setup 走 SPA 200。 2. core 入口只 app.fetch,无 ASSETS 兜底:页面导航 GET /setup 在 Hono 无路由 → text/plain 404。与生成注释「ASSETS 兜底」不符。 现在:404 且非 API/life/.well-known 且 GET → ASSETS 回 SPA; API 语义 404 保持 JSON。装 /setup 真文件也 404 的旁证排除。 真机:/setup 与 /setup?token=… 均 200;/api/health 200; /api/nope 保持 JSON 404;二跑输出一致(幂等)。 Signed-off-by: huangyinghui <18666119673@163.com>
wrangler v4 无顶层 custom_domains 字段(schema 无此定义,真机报
Unexpected fields found in top-level field,仅警告但未生效换绑)。
官方形态:routes:[{ pattern, custom_domain: true }]。
真机换绑 demo 三级域验证:wrangler 自动解绑旧域、签发新证书、
unself.demo.handywote.top 与 hello.unself.demo.handywote.top 均 200。
测试断言同步。Ref #15
Signed-off-by: huangyinghui <18666119673@163.com>
线上 /api/modules、/api/admin/modules 全 500:listModules SELECT 了 registered_at,但迁移 0001 的 module_registry 从未有此列(D1 报 no such column: registered_at)。单测的内存假 D1 只认 SQL 子串、 行结构自带 registered_at —— 两张皮掩盖了真实列集合,56 用例全绿照 样上线即炸。 修复:查询列严格取迁移 0001 的列集合;rowToEntry 本就把缺列映射为 registeredAt: '',消费面不变。测试假 D1 行结构同步对齐迁移,防再犯。 Ref #55 Signed-off-by: huangyinghui <18666119673@163.com>
真机(已激活实例)验证两处:
- 页面 import './sdk/module-sdk.js' 请求 /m/<id>/sdk/...,旧 wrapper
找 assets/<id>/sdk/...(多了 assets/ 段)必 404 → 对齐部署期资产
布局 sdk/module-sdk.js(探针:ASSETS.fetch('/sdk/x') 200)
- /m/<id>/ 与 /m/<id>(模块页本体)不能落资产分支(模块无静态
index.html,/ 由 Hono 渲染)→ 仅「前缀+更多路径」走资产
真机:/m/hello/ 200(Hono HTML)、sdk/module-sdk.js 200(JS)、
api/health 200;/m/hello 404 保持。Ref #57
Signed-off-by: huangyinghui <18666119673@163.com>
- moduleWranglerConfig:模块 routes 改 <domain>/m/<id>/*(无 custom_domain 标记 = zone 路径路由),与 core 主域同域按前缀分发;core 主域 custom_domain 保留不动 - smokeCheck:删 moduleSubdomain 参数与子域分支,模块探测回归 <baseUrl>/m/<id>/api/health(domain/workers.dev 两模式同形) - steps:删 moduleSubdomain flag;步骤④日志成真(绑 <domain>/m/<id>/* 路由); http 注入口标注 @internal 仅测试用(生产走真实 smokeCheck) - 修正失实注释:10405 背景(OAuth 对 zone 路由授权不足,需 API Token 带 Zone: Workers Routes Edit);删「绝对 URL 头重写」虚构表述 - assemble 抽 buildModuleSdkAssets(IIFE + ESM 双产物,供页面 ESM 具名导入) Ref #59 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
- three-way-consistency:同一 config 下「registry entry 装载点 == wrangler route 路由点 == 步骤⑨冒烟探测点」host+前缀一致;core 主域 custom_domain、模块 zone 路径路由形态整对象断言(修复前 HEAD 上红) - smoke.test.ts:真实 smokeCheck + stub fetch,domain/workers.dev 两模式 URL 整数组断言 + ok/detail 行为断言(响应体缺 ok、HTTP 503、不可达) - assemble.test.ts:模块 routes 改整对象 toEqual;新增 buildModuleSdkAssets 回归测(ESM 产物可 import 具名 createModuleSDK,IIFE 无顶层 export) Ref #59 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
装配产物中 sdk/module-sdk.js 是 esbuild IIFE,无顶层 export——浏览器 ESM 具名导入 报 SyntaxError、脚本死、握手失败(线上实锤)。module-sdk.esm.js 是同包二次打包的 ESM 产物(assemble.ts 已生成),具名 createModuleSDK 可用;测试断言同步。 Ref #59 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
- 部署凭证以 API Token 为主路径:Account: Workers Scripts/D1/R2 Edit; Zone(config.domain 所属 zone): Workers Routes Edit - 补 10405 背景:wrangler login 的 OAuth 对 zone 路由授权不足,必须用 API Token - 注记待真机验证的假设:zone 路由 pattern 直接用 config.domain 全值, zone 解析交由 wrangler 按 pattern 匹配(多级子域推不出 zone,故不引入 zone 字段) Ref #59 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
审核 T1:core-api 四套假 D1 无 schema 概念,SQL 子串匹配命中即回手造行, 放过 #56 幻影列类错误。新建 test-factory 用 Node 内置 node:sqlite(零新依赖) 加载 migrations/core/*.sql 真建表,D1 适配器对齐真 D1 形状 (all() 回 results 数组、run() 回 meta.changes/last_row_id、datetime 文本语义、 first() 无行 null、行对象普通原型)。 registry.test.ts 首先换真库并立守护范式: - 种子用户/模块走真 SQL,断言直查真表 - 守护:module_registry 行结构 == 建表列(幻影列/漏列即红) - 守护:真 schema 约束(users UNIQUE(issuer,sub)、manifest_json NOT NULL) Ref #60 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
- setup.test:删可记账内存 stub,激活链路走真 instance_config/setup_tokens/ users/audit_log;断言 used_at 为 SQLite datetime 文本(非 ISO) - token.test:删 makeDb 子串匹配 stub;门禁查真 module_registry; 修审核 G1『已停用模块在启停后可再取』——改为同一真库 enabled 0→1 翻转 - auth-routes.test:no-op CORE_DB 换真库,callback JIT 建档真 INSERT users 表, 同 issuer+sub 二次 callback 断言复用同一行(UNIQUE 真生效) - 每处补『查询列↔建表列』守护用例(错位即红,已实证注入幻影列必失败) Ref #60 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
审核 T8:storage.test 的 MemoryStatement 恒用 params[0] 当 moduleId 过滤, SDK SQL 漏 WHERE module_id = ? 时假替身仍替被测代码『背锅』过滤 → 测试绿、 真机跨模块数据泄漏。改为真 SQLite 加载 modules 统一迁移 modules/hello/migrations/hello/0001_module_kv.sql 真建表。 - packages/module-sdk/test/test-factory.ts:独立小工厂(与 core-api 版互不 import) - storage.test:删 MemoryStatement/MemoryDatabase/likeToSource,LIKE/ESCAPE 由 真 SQLite 裁决;新增 T8 守护(同 key 两模块直插后 list/get 只回本模块, 漏 WHERE 即红——已实证注入必失败)+ PK 真约束 + 列结构守护 - hello.test:假 D1 换真 module_kv,计数落库断言直查真表 Ref #60 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
审核 T2:requires/capabilities 是 YAML list,旧逐行正则只读标量, 快照硬编码 ['identity']/['demo'] → 模块自定义 capabilities 时 token caps 授牌错误。 先写红测(非默认值 list manifest 全字段断言,修复前 3 条红), 再以最小解析(标量正则 + list 项归属最近空值顶层 key)修正,并让 description 进快照。 Ref #60 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
审核 T4:wrangler v4 `r2 bucket list` 输出 formatLabelledValues 文本 (name: 对齐 + creation_date,无 --json),旧 fake 只喂 JSON → 真实解析分支零测试, 真机格式一变就重复 create(already exists)。 抽 parseR2BucketList 并直测文本/空/旧 JSON/去重/ANSI/CRLF,steps fake 同步改文本。 Ref #60 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
审核 §2.2 G1: - ui.test.ts 删「导出存在性 + 版本号」恒真,改 vue/server-renderer 真渲染 (UButton 变体/disabled、UErrorCard requestId/详情折叠、UInput、UCard); - sdk.test.ts 无 window 用例不再只断言 not.toThrow,改为观测不得触达 parent, 装 window 后投递仍生效(证明守卫是运行时条件而非 no-op 桩); - hello.test.ts 身份行自证循环改为验签行为断言:正签 token 200、同签名篡改 payload → 401(claims 只能来自签发方签过的 token)。 Ref #60 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
审核 D8/T6:壳侧三处消费响应头 x-request-id(ErrorCard 展示 request id), core-api 全源码零产出 → requestId 恒 undefined,死功能。 新增全局中间件每请求生成 req-<16hex> 并回写所有响应(含 401/403/503)。 审核 §2.3 OIDC 失败态:callback 换 token/验签抛错此前未捕获 → 500。 改为清流程 Cookie + 302 /login?error=<短码>(state 不匹配/过期/IdP error 各有码), Cookie 缺失/损坏仍 400;成功链路不变。 新增路由级用例:request-id.test.ts(auth/registry/setup/token 四类路由 + id 唯一/格式)、 oidc-callback-failures.test.ts(5 条失败态)。CORE_DB 复用共享 test-factory 真 sqlite。 Ref #60 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
#59 真机部署前发现:Cloudflare 语义是 Custom Domain 优先于同 host 的路径 路由,core 挂 Custom Domain 会吞掉模块的 <domain>/m/<id>/*(模块路由拿不到 任何流量)。§5.3 正解:core 与模块全部 zone 路径路由(core=<domain>/*, 模块=<domain>/m/<id>/*,最长前缀胜出)。 - assemble.ts:core routes 改 {pattern: '<domain>/*'}(去 custom_domain) - dns.ts(新):部署期 DNS 自愈——core 改路由形态后 Custom Domain 解绑会 删 CF 自建 DNS 记录,部署脚本幂等补建代理 A 记录(192.0.2.1 占位), zone 逐级上溯探测(无需 config zone 字段,#59 待定案①定案) - steps.ts:core 部署后立即 ensureDns(先于模块部署与冒烟),测试注入口 ensureDns + configOverride - 测试:assemble/三方一致性断言同步 zone 形态;dns.test.ts 5 用例(上溯/ 幂等/无 token/未找到/创建);steps.test 顺序断言(deploy → ensureDns → registry);README 权限表补 DNS Edit、路由形态章节、删假设段 Signed-off-by: huangyinghui <18666119673@163.com>
真机首次部署被 wrangler schema 拦下:routes[] 元素形态是
{ pattern, custom_domain, zone_id | zone_name, ... },zone 路由无
zone_id/zone_name 直接拒收(#61 的测试断言了不合法形态——T5 假
Wrangler 类盲区实证)。修法:
- assemble.ts:core/module 两个配置生成器接受 zoneName,routes 对象
带 zone_name(wrangler 按 zone 名解析,无需 zone_id)
- steps.ts:部署前经 API 逐级上溯探测 zone(resolveZone 注入口),
探测失败硬报错(zone 路由必需);zone 结果复用给 DNS 自愈步骤,
避免二次 API 调用
- 测试:assemble/三方一致性断言同步 zone_name 形态;steps 测试全面
改用 configOverride(隔离部署者本地 domain 状态——此前 domain 填充
导致非 domain 用例误走 zone 探测路径,一并修掉)
Signed-off-by: huangyinghui <18666119673@163.com>
真机第二发现:wrangler 成功写入 zone 路径路由(core <domain>/* 与 模块 <domain>/m/<id>/* 均已生效),但旧 Custom Domain 绑定原样保留 (workers/domains 实测 core 主域与 hello 子域两条都在)——同 host 上 Custom Domain 优先于路径路由,模块路由被吞、冒烟 module:hello 失败, DNS 自建还撞 81062(Workers 管理的记录仍占位)。 - dns.ts:findAccountId(GET /accounts 首个)+ removeLegacyCustomDomains (幂等删 hostname==domain 或 *.domain 的绑定,CF 同步删自建 DNS 记录) - steps.ts:core 部署前清理(cleanupCustomDomains 注入口),顺序: Custom Domain 清理 → core deploy → DNS 自建 → 模块部署 → registry - 测试:dns 新增 4 用例(账户发现 2、清理 2);steps 顺序断言更新 Signed-off-by: huangyinghui <18666119673@163.com>
真机第三发现:解绑 Custom Domain 的 DELETE 请求返回 200 空 body, res.json() 抛 'Unexpected end of JSON input' 中断部署(core 主域的 解绑已生效、hello 的未执行,半清理状态)。toResult 统一处理:空体按 状态码判定,非 JSON 体降级为带说明的错误。 Signed-off-by: huangyinghui <18666119673@163.com>
真机第四发现:unself.demo.handywote.top 是三级子域,Universal SSL
只覆盖 apex + 一级通配(*.handywote.top),代理 A 记录建好后 TLS
handshake failure(sslv3 alert handshake failure)。修法:部署脚本
在 zone 探测后幂等 POST zones/{z}/acm/total_tls enabled:true,为全部
代理主机名逐个签发证书;README 权限表补 SSL and Certificates Edit。
Signed-off-by: huangyinghui <18666119673@163.com>
needsTotalTls(domain, zoneName) 纯函数 + 三用例:一级子域/apex 不 需要、多级需要。用户拍板实例域名回 unself.handywote.top(免费证书 覆盖),部署日志不再每次报 1450。 Signed-off-by: huangyinghui <18666119673@163.com>
根因①:模块 Worker 运行时跨 Worker 拉 core JWKS 被 CF 同 zone 禁令拦截 → /api/count 恒 401。 B 方案:verifyModuleToken 改为入参 CORE_JWKS_JSON 字符串 + createLocalJWKSet 本地验签, 删除远程 JWKS 路径(唯一使用方 hello);hello Bindings 由 CORE_JWKS_URL 改 CORE_JWKS_JSON, 缺注入回 503 jwks not provisioned(不再谎报 401)。 测试:真 keypair 签 token→200;坏 token/aud 不符/篡改 payload→401;缺 CORE_JWKS_JSON→503; 破坏性红测:全局 fetch 抛错下合法 token 仍 200 且 fetch 零调用。 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
moduleWranglerConfig 增必填 jwksJson → vars.CORE_JWKS_JSON,删除 jwksUrl/workers.dev 占位分支; keypair 导出 publicJwksJson(与 core toPublicJwks 同形状);步骤④前解析公钥: 本运行新生成 keypair → 内存公钥(不抓公网);否则 GET <baseUrl>/.well-known/jwks.json; 失败硬报错并提示 DNS/路由未就绪可重跑。README 补模块 vars、换钥流程、Docker 等价注记。 测试:装配产物 CORE_JWKS_JSON shape 合法且无 CORE_JWKS_URL;steps 内存/公网/失败三分支。 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
根因②:watch(activeModule) 默认 pre-flush,回调时 iframe 未进 DOM → frameEl 为 null → 误判「模块入口配置无效」;retry 不重载 iframe,模块 ready 已发出无人接收(概率性失败)。 改 flush:'post' + nextTick 兜底,iframe 未挂载则等模板 ref 就位(不再静默 failed); retry 自增 frameReload 并入 frameKey,强制 iframe 重挂使模块重新发 ready。 测试:jsdom 组件测试断言桥在 iframe 挂载后 attach、ready→token→帧就位、retry 替换 iframe DOM。 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
provisionAll 中 apps/shell/dist 一旦存在即跳过 vite build、直接 cp 复用旧产物, 源码变更被静默丢弃(真机实证:PR #72 合并重部署后线上 bundle hash 未变化)。 - 删除 dist 复用捷径:每次部署无条件重建 shell,部署器职责=始终搬运当前源码树; - 新增 buildShell 注入口(同 steps.ts putSecret/resolveZone 模式),测试注入 fake 避免真实 vite; - runNineSteps 转发 buildShell;steps.test.ts 注入快速 fake(真实 vite ~4.4s/次会使套件超时); - 修正 provisionAll JSDoc 与文件头注释中 FORCE_BUILD/dist 复用措辞; - 新增 provisionAll 用例:预置陈旧 dist + stale.marker,断言新产物入 assets/shell 且无陈旧残留。 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
- .shell-logout 永不收缩、文案单行,超宽压力转嫁用户名 ellipsis 截断 - apps/shell/vite.config.ts 开 test.css:true,jsdom getComputedStyle 可级联 scoped CSS - App.test.ts 新增长/短用户名布局契约用例(破坏性回验:移除任一属性即红) Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
- steps:步骤④′ 对 config.modules 未列出的已存在模块删除 zone 路由 /m/<id>/*, 幂等(不存在不报错)、只删路由(不删 Worker、不动 D1,数据保留);步骤日志记录删除动作 - dns:新增 removeModuleRoutes(CF API GET/DELETE /zones/<id>/workers/routes, 精确匹配 pattern,查询/删除失败只记日志不中断,风格同 removeLegacyCustomDomains) - config:modules 允许空数组(移除唯一模块可表达;§6.5 全停用空态) - test:dns 5 用例 + steps 2 用例(未选模块删除动作 + 注册表 disable 并存、无 domain 跳过)+ config 空列表 Signed-off-by: HandyWote <huangyinghui01@corp.netease.com>
Signed-off-by: Unself <unself@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
M0 垂直切片完整交付(对应 milestone「M0 · 垂直切片」,已全绿收官)。此前曾直接 push main(流程违规),现已重置 main 至 merge 前(2f8e80d),本 PR 以正规流程重新合入。
交付内容(M0 全量)
packages/contracts:模块契约/manifest/协议packages/module-sdk:token 验签(本地 JWKS 零运行时网络)+ D1 存储services/core-api:OIDC 登录(Stalwart IdP)、setup 激活、模块注册表/启停、token 门禁、审计apps/shell:登录/工作台/模块 iframe 桥(flush 时机 + 重试自愈)modules/hello:验签 + 计数读写(真机实证 T3 修复)deploy/cloudflare:幂等九步编排、zone 路径路由、Total TLS 跳过一级子域、未选模块路由删除、每次重建 shell验收对照(#16 七步剧本真机全绿)
合并后
m1/*分支开发;m0/dev 归档请审查合并(建议 rebase 策略)。