create-dwy 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/package.json +1 -1
  3. package/src/cli-help.js +11 -13
  4. package/src/sync-all.js +86 -11
  5. package/src/sync-plan.js +9 -1
  6. package/templates/ai-tools/CHANGELOG.md +13 -0
  7. package/templates/ai-tools/VERSION +1 -1
  8. package/templates/ai-tools/skills/Android/dwy-android-architecture/SKILL.md +10 -0
  9. package/templates/ai-tools/skills/Android/dwy-android-development/SKILL.md +16 -0
  10. package/templates/ai-tools/skills/Apple/dwy-apple-appkit/SKILL.md +17 -0
  11. package/templates/ai-tools/skills/Apple/dwy-apple-architecture/SKILL.md +12 -0
  12. package/templates/ai-tools/skills/Apple/dwy-apple-architecture/references/extraction.md +9 -0
  13. package/templates/ai-tools/{rules/iOS/dwy-apple-layering.md → skills/Apple/dwy-apple-architecture/references/layering.md} +2 -10
  14. package/templates/ai-tools/skills/Apple/dwy-apple-architecture/references/pitfalls.md +3 -0
  15. package/templates/ai-tools/skills/Apple/dwy-ios-development/SKILL.md +14 -0
  16. package/templates/ai-tools/skills/Apple/dwy-ios-development/references/appearance.md +6 -0
  17. package/templates/ai-tools/{rules/iOS/dwy-ios-layout-adaptation.md → skills/Apple/dwy-ios-development/references/layout.md} +0 -6
  18. package/templates/ai-tools/{rules/iOS/dwy-ios-pitfalls.md → skills/Apple/dwy-ios-development/references/pitfalls.md} +1 -9
  19. package/templates/ai-tools/skills/Apple/dwy-ios-development/references/presentations.md +11 -0
  20. package/templates/ai-tools/skills/Apple/dwy-macos-development/SKILL.md +14 -0
  21. package/templates/ai-tools/skills/Apple/dwy-macos-development/references/pitfalls.md +9 -0
  22. package/templates/ai-tools/skills/Apple/dwy-macos-development/references/presentations.md +5 -0
  23. package/templates/ai-tools/skills/Apple/dwy-macos-development/references/ui-adaptation.md +6 -0
  24. package/templates/ai-tools/skills/Apple/dwy-swift-development/SKILL.md +16 -0
  25. package/templates/ai-tools/{rules/iOS/dwy-swift-core.md → skills/Apple/dwy-swift-development/references/conventions.md} +2 -8
  26. package/templates/ai-tools/skills/Apple/dwy-swift-development/references/pitfalls.md +5 -0
  27. package/templates/ai-tools/skills/Apple/dwy-swift-development/references/presentations.md +8 -0
  28. package/templates/ai-tools/skills/Docker/dwy-docker/SKILL.md +2 -0
  29. package/templates/ai-tools/skills/Flutter/dwy-flutter-development/SKILL.md +15 -0
  30. package/templates/ai-tools/skills/HarmonyOS/dwy-harmony-development/SKILL.md +15 -0
  31. package/templates/ai-tools/skills/Python/dwy-apple-notify/SKILL.md +24 -0
  32. package/templates/ai-tools/skills/Python/dwy-apple-notify/references/v0/0.1.6.md +81 -0
  33. package/templates/ai-tools/skills/Python/dwy-auth/SKILL.md +24 -0
  34. package/templates/ai-tools/skills/Python/dwy-auth/references/v0/0.2.0.md +163 -0
  35. package/templates/ai-tools/skills/Python/dwy-device-fingerprint/SKILL.md +19 -0
  36. package/templates/ai-tools/skills/Python/dwy-device-fingerprint/references/v0/0.1.1.md +90 -0
  37. package/templates/ai-tools/skills/Python/dwy-eapi/SKILL.md +1 -1
  38. package/templates/ai-tools/skills/Python/dwy-eapi/versions.yml +3 -3
  39. package/templates/ai-tools/skills/Python/dwy-email-notify/SKILL.md +19 -0
  40. package/templates/ai-tools/skills/Python/dwy-email-notify/references/v0/0.1.0.md +31 -0
  41. package/templates/ai-tools/skills/Python/dwy-notify/SKILL.md +24 -0
  42. package/templates/ai-tools/skills/Python/dwy-notify/references/v0/0.3.2.md +128 -0
  43. package/templates/ai-tools/skills/Python/dwy-payment/SKILL.md +19 -0
  44. package/templates/ai-tools/skills/Python/dwy-payment/references/v0/0.3.0.md +90 -0
  45. package/templates/ai-tools/skills/Python/dwy-python-api/SKILL.md +8 -0
  46. package/templates/ai-tools/skills/Python/dwy-python-architecture/SKILL.md +8 -0
  47. package/templates/ai-tools/skills/Python/dwy-python-development/SKILL.md +17 -0
  48. package/templates/ai-tools/{rules/Python → skills/Python/dwy-python-development/references}/dwy-python-base-lib.md +3 -3
  49. package/templates/ai-tools/skills/Python/dwy-webhooks/SKILL.md +24 -0
  50. package/templates/ai-tools/skills/Python/dwy-webhooks/references/v0/0.6.2.md +238 -0
  51. package/templates/ai-tools/skills/Python/dwy-wechatpay-notify/SKILL.md +19 -0
  52. package/templates/ai-tools/skills/Python/dwy-wechatpay-notify/references/v0/0.1.1.md +101 -0
  53. package/templates/ai-tools/skills/Vue/dwy-admin-kit/SKILL.md +12 -0
  54. package/templates/ai-tools/skills/Vue/dwy-admin-kit/references/v0/0.3.0.md +176 -0
  55. package/templates/ai-tools/skills/Vue/dwy-auth-web/SKILL.md +10 -0
  56. package/templates/ai-tools/skills/Vue/dwy-ekit/SKILL.md +2 -3
  57. package/templates/ai-tools/skills/Vue/dwy-eui/SKILL.md +1 -1
  58. package/templates/ai-tools/skills/Vue/dwy-eui/versions.yml +2 -2
  59. package/templates/ai-tools/skills/Vue/dwy-vue-architecture/SKILL.md +8 -0
  60. package/templates/ai-tools/skills/Vue/dwy-vue-development/SKILL.md +16 -0
  61. package/templates/ai-tools/{rules/Vue → skills/Vue/dwy-vue-development/references}/dwy-vue-base-lib.md +3 -3
  62. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-architecture/SKILL.md +7 -7
  63. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/SKILL.md +2 -0
  64. package/templates/ai-tools/skills//346/225/260/346/215/256/345/272/223/dwy-alembic/SKILL.md +8 -0
  65. package/templates/ai-tools/skills//346/225/260/346/215/256/345/272/223/dwy-postgres/SKILL.md +10 -0
  66. package/templates/ai-tools/skills//346/225/260/346/215/256/345/272/223/dwy-redis/SKILL.md +8 -0
  67. package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-dependency-choice/SKILL.md +1 -1
  68. package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-development-review/SKILL.md +2 -0
  69. package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-git-commit/SKILL.md +1 -1
  70. /package/templates/ai-tools/{rules/Android → skills/Android/dwy-android-architecture/references}/dwy-android-layering.md +0 -0
  71. /package/templates/ai-tools/{rules/Android → skills/Android/dwy-android-architecture/references}/dwy-android-mvp.md +0 -0
  72. /package/templates/ai-tools/{rules/Android → skills/Android/dwy-android-development/references}/dwy-android-core.md +0 -0
  73. /package/templates/ai-tools/{rules/Android → skills/Android/dwy-android-development/references}/dwy-android-pitfalls.md +0 -0
  74. /package/templates/ai-tools/{rules/Android → skills/Android/dwy-android-development/references}/dwy-android-support-only.md +0 -0
  75. /package/templates/ai-tools/{rules/Docker → skills/Docker/dwy-docker/references}/dwy-docker.md +0 -0
  76. /package/templates/ai-tools/{rules/Flutter → skills/Flutter/dwy-flutter-development/references}/dwy-flutter-core.md +0 -0
  77. /package/templates/ai-tools/{rules/Flutter → skills/Flutter/dwy-flutter-development/references}/dwy-flutter-network.md +0 -0
  78. /package/templates/ai-tools/{rules/Flutter → skills/Flutter/dwy-flutter-development/references}/dwy-flutter-router.md +0 -0
  79. /package/templates/ai-tools/{rules/Flutter → skills/Flutter/dwy-flutter-development/references}/dwy-flutter-testing.md +0 -0
  80. /package/templates/ai-tools/{rules/HarmonyOS → skills/HarmonyOS/dwy-harmony-development/references}/dwy-arkts-strict.md +0 -0
  81. /package/templates/ai-tools/{rules/HarmonyOS → skills/HarmonyOS/dwy-harmony-development/references}/dwy-arkts-ui-build.md +0 -0
  82. /package/templates/ai-tools/{rules/HarmonyOS → skills/HarmonyOS/dwy-harmony-development/references}/dwy-arkts-ui-design.md +0 -0
  83. /package/templates/ai-tools/{rules/HarmonyOS → skills/HarmonyOS/dwy-harmony-development/references}/dwy-harmony-pitfalls.md +0 -0
  84. /package/templates/ai-tools/{rules/Python → skills/Python/dwy-python-api/references}/dwy-python-backend.md +0 -0
  85. /package/templates/ai-tools/{rules/Python → skills/Python/dwy-python-architecture/references}/dwy-python-layering.md +0 -0
  86. /package/templates/ai-tools/{rules/Python → skills/Python/dwy-python-development/references}/dwy-python-core.md +0 -0
  87. /package/templates/ai-tools/{rules/Python → skills/Python/dwy-python-development/references}/dwy-python-pitfalls.md +0 -0
  88. /package/templates/ai-tools/{rules/Python → skills/Python/dwy-python-development/references}/dwy-python-testing.md +0 -0
  89. /package/templates/ai-tools/{rules/Vue → skills/Vue/dwy-vue-architecture/references}/dwy-vue-layering.md +0 -0
  90. /package/templates/ai-tools/{rules/Vue → skills/Vue/dwy-vue-development/references}/dwy-vue-core.md +0 -0
  91. /package/templates/ai-tools/{rules/Vue → skills/Vue/dwy-vue-development/references}/dwy-vue-pinia.md +0 -0
  92. /package/templates/ai-tools/{rules/Vue → skills/Vue/dwy-vue-development/references}/dwy-vue-pitfalls.md +0 -0
  93. /package/templates/ai-tools/{rules/Vue → skills/Vue/dwy-vue-development/references}/dwy-vue-testing.md +0 -0
  94. /package/templates/ai-tools/{rules//345/274/200/345/217/221/346/265/201/347/250/213 → skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references}/dwy-prod-branch.md" +0 -0
  95. /package/templates/ai-tools/{rules//346/225/260/346/215/256/345/272/223 → skills//346/225/260/346/215/256/345/272/223/dwy-alembic/references}/dwy-db-migration.md" +0 -0
  96. /package/templates/ai-tools/{rules//346/225/260/346/215/256/345/272/223 → skills//346/225/260/346/215/256/345/272/223/dwy-postgres/references}/dwy-postgres.md" +0 -0
  97. /package/templates/ai-tools/{rules//346/225/260/346/215/256/345/272/223 → skills//346/225/260/346/215/256/345/272/223/dwy-redis/references}/dwy-redis.md" +0 -0
  98. /package/templates/ai-tools/{rules//345/274/200/345/217/221/346/265/201/347/250/213 → skills//351/200/232/347/224/250/dwy-dependency-choice/references}/dwy-dependency-freshness.md" +0 -0
  99. /package/templates/ai-tools/{rules//345/274/200/345/217/221/346/265/201/347/250/213 → skills//351/200/232/347/224/250/dwy-dependency-choice/references}/dwy-oss-commercial-license.md" +0 -0
  100. /package/templates/ai-tools/{rules//345/274/200/345/217/221/346/265/201/347/250/213 → skills//351/200/232/347/224/250/dwy-development-review/references}/dwy-code-craft.md" +0 -0
  101. /package/templates/ai-tools/{rules//345/274/200/345/217/221/346/265/201/347/250/213 → skills//351/200/232/347/224/250/dwy-git-commit/references}/dwy-git-commit.md" +0 -0
@@ -7,6 +7,8 @@ description: "Docker 工程规范检查与镜像版本管理。触发场景:
7
7
 
8
8
  涵盖镜像版本选择 + Dockerfile / docker-compose / k8s manifest 工程结构。
9
9
 
10
+ 按需阅读 [镜像、容器资源和配置变更](references/dwy-docker.md),核实项目实际容器运行方式;已有项目遵循用户要求和现有约定,不为套用示例擅自修改运行环境。
11
+
10
12
  **脚本目录:** `./scripts/`(以下简称 `{scripts}`)
11
13
 
12
14
  **参考文件:** `./references/`
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: dwy-flutter-development
3
+ description: 开发、测试或排查 Flutter 和 Dart 应用时使用,按需覆盖状态、网络、路由和界面适配。
4
+ ---
5
+
6
+ # Flutter 开发
7
+
8
+ 先核实项目 Flutter/Dart 版本、依赖和现有状态、网络与路由方案。参考资料中的 Riverpod、Dio 和 GoRouter 只适用于已使用这些库或用户明确选择的项目;不自动引入或替换依赖。
9
+
10
+ - [Dart、项目结构与界面](references/dwy-flutter-core.md)
11
+ - [网络与错误](references/dwy-flutter-network.md)
12
+ - [路由](references/dwy-flutter-router.md)
13
+ - [测试](references/dwy-flutter-testing.md)
14
+
15
+ 按任务阅读对应资料,核实真实运行平台、安全区域及必要的交互测试。
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: dwy-harmony-development
3
+ description: 开发、测试或排查 HarmonyOS ArkTS 与 ArkUI 应用时使用,覆盖工程、界面和设备问题。
4
+ ---
5
+
6
+ # HarmonyOS 开发
7
+
8
+ 先检查项目实际 SDK、API 版本、状态管理方式、依赖与目标设备。编写 `.ets` 时使用当前环境可用且适用的 HarmonyOS 官方开发技能和文档;没有时按官方 SDK 文档继续,不自动安装工具。
9
+
10
+ - [ArkTS 严格语法](references/dwy-arkts-strict.md)
11
+ - [ArkUI 构建与运行](references/dwy-arkts-ui-build.md)
12
+ - [界面设计与安全区](references/dwy-arkts-ui-design.md)
13
+ - [常见踩坑](references/dwy-harmony-pitfalls.md)
14
+
15
+ 资料中的版本特定规则先核实当前 SDK;构建通过不能代替目标设备的运行和交互验证。
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: dwy-apple-notify
3
+ description: 使用或排查 dwy-apple-notify Apple 服务端通知能力时使用;先核实项目安装版本及公开 API。
4
+ ---
5
+
6
+ # dwy-apple-notify
7
+
8
+ Apple 购买、审核、评论及经营报表通知;核实来源配置、签名验证、投递和平台 extra。
9
+
10
+ ## 查找与使用
11
+
12
+ 项目使用 0.1.6 时可阅读 [0.1.6 接入和示例](references/v0/0.1.6.md);其他版本先查该版本的资料和代码,不把此快照当作其他版本的合同。
13
+
14
+ 1. 检查项目锁文件和当前 Python 环境;用 `importlib.metadata.version("dwy-apple-notify")` 查询实际发行版本,依赖范围不等于实际安装版本。未安装时不得假设可用或自动安装。
15
+ 2. 在实际环境中检查 `dwy_apple_notify` 的公开接口、类型及项目已有调用;editable 安装以实际源码为准。说明、实现或安装版本不一致时以运行代码为准。
16
+ 3. 本索引基于仓库 0.1.6,能力与签名可能随小版本变化。精确版本源码位于仓库 `backend/packages/apple-notify`,文档为该目录的 `README.md`;只在确认其版本与消费项目一致时使用。缺少安装源码时查对应 `dwy-apple-notify@版本` 标签,不能用默认分支冒充。
17
+ 4. 在已有调用和该版本文档中核对配置、必需依赖、输入校验、失败处理及生命周期后接入。没有足够证据时说明缺口,不猜 API。
18
+
19
+ ## 0.x 已核实差异
20
+
21
+ - 0.1.0 提供购买、审核、评论和经营报表通知;0.1.5 新增可选 ratings extra 和评分日报。
22
+ - 0.1.6 才支持通过已验签的交易查询首次获取 App 的平台;该平台不代表本次内购设备。
23
+
24
+ 不要因为发现此库就改动项目依赖;新增依赖遵循项目选型规则。关联 FastAPI 或其他自有包时同时参考可用的对应 Skill。
@@ -0,0 +1,81 @@
1
+ <!-- 来源:dwy-apple-notify@0.1.6 的 backend/packages/apple-notify/README.md;文中相对链接相对原仓库路径。 -->
2
+
3
+ # dwy-apple-notify
4
+
5
+ Apple 通知集成:购买通知验签与文案、App Store Connect 审核事件、评论监控、经营日报和周报。通知投递统一使用 `dwy-notify`;本包不处理应用账户与权益。
6
+
7
+ ## 按需安装
8
+
9
+ ```bash
10
+ pip install 'dwy-apple-notify[iap,asc,reviews,analytics,ratings]==0.1.6'
11
+ ```
12
+
13
+ `iap` 安装 Apple Server SDK;`asc`、`reviews`、`analytics` 分别安装现有 ASC HTTP/JWT/加密依赖。只使用审核、评论或报表时不需要 Apple Server SDK,也不需要 Resend。
14
+
15
+ ## 模块入口
16
+
17
+ - `dwy_apple_notify.apple`:已验签通知与交易模型、购买文案。
18
+ - `dwy_apple_notify.config`:`AppleNotifyConfig`、各 Apple 来源配置。
19
+ - `dwy_apple_notify.fastapi`:`install_apple_notifications`、`AppleNotifySinks`、`AppleNotifyRuntime`。
20
+ - `dwy_apple_notify.analytics`:官方报表客户端、快照、存储与 monitor。
21
+ - `dwy_apple_notify.reviews`:评论客户端、存储与 monitor。
22
+ - `dwy_apple_notify.ratings`:公开 Lookup 评分采集、独立每日调度、日周月变化与持久日报(当前源码新增,尚未发布)。
23
+
24
+ 配置由宿主显式注入,本包不读取环境变量、不建数据库、不执行迁移。数据库会话、表和生命周期由宿主管理,已有队列表名、来源标识与目标指纹保持兼容。
25
+
26
+ `apple_original_platform(client, verify, transaction_id)` 使用 Apple 官方客户端查询 App 交易,并通过宿主提供的异步验签函数验证签名后返回平台。调用方负责超时和错误处理;可将结果传入 `VerifiedAppleNotification.original_platform` 显示。该平台表示首次获取 App 的平台,不能用于判断本次内购的设备,iOS 也不区分 iPhone 与 iPad。
27
+
28
+ ## 经营统计日期
29
+
30
+ 默认北京时间 `Asia/Shanghai` 每天 08:30,内部固定查询统计时区日期的前两天,例如 30 日查询 28 日、31 日查询 29 日。Apple 报表生成存在延迟,因此不暴露 `lag_days` 配置。周一附加截至前两天的连续七天周报。日报和周报固定展示全部指标与地区、来源 Top 3:有值显示实际数值,获取不到显示“无数据”,真实零值保留为零。快照仍保留原始可用性状态,不把缺失数据写成零,不自动重复补发历史日报。
31
+
32
+ ## 名称与头像
33
+
34
+ 购买默认“购买通知”,审核默认“应用审核通知”,日报周报默认“经营报表”,评论默认“系统通知”。各模块 `appearance.py` 公开 `DEFAULT_APPEARANCE`;应用与单次发送可逐字段覆盖,最终名称必填,头像可省略或显式清除以使用平台默认。
35
+
36
+ Logo 沿用 GitHub 固定资产提交 `091ad4fea18ed3840fec83dc833f18b759256ca7`,无需应用部署图片服务。资产与 Lucide 许可维护于 `backend/packages/webhooks/assets/`,固定历史引用不受本次 Python 包迁移影响。
37
+
38
+ ## 每日评分日报
39
+
40
+ 从 `0.1.5` 起提供 `ratings` extra,仅使用现有固定版本 HTTPX;旧版 `0.1.4` 不包含该入口。开发时也可从仓库根安装源码:
41
+
42
+ ```bash
43
+ pip install './backend/packages/apple-notify[ratings]'
44
+ ```
45
+
46
+ 宿主提供会话工厂、通知发送器和迁移,库不读取 `.env`、不创建数据库,不需要 Apple 账号或私钥:
47
+
48
+ ```python
49
+ from dwy_apple_notify.ratings import (
50
+ AppleRatingsConfig,
51
+ RatingApp,
52
+ build_rating_tables,
53
+ install_rating_monitor,
54
+ )
55
+
56
+
57
+ def configure_ratings(app, sessions, metadata, sender):
58
+ """宿主须先通过自身迁移创建 tables 对应的表,再启动应用。"""
59
+ config = AppleRatingsConfig(
60
+ apps={"1234567890": RatingApp(name="Demo")},
61
+ territories=("cn", "us"),
62
+ timezone="Asia/Shanghai",
63
+ send_hour=8,
64
+ send_minute=30,
65
+ retention_days=90,
66
+ )
67
+ tables = build_rating_tables(metadata)
68
+ return install_rating_monitor(app, config, sessions, tables, sender=sender)
69
+ ```
70
+
71
+ 也可配置 `notifications` 使用 `dwy-notify` 的已有目标。默认展示名称为“评分日报”。运行时位于 `app.state.dwy_rating_monitor`,公开 `monitor.run_once()` 与 `queue.process_pending()`;可注入 `client`、`sender` 和 `clock` 离线运行。采集器协议为 `async get_rating(app_id, territory) -> AppleRating(count=..., average=...)`,预期采集失败抛 `RatingCollectionError`;外部客户端由宿主关闭。
72
+
73
+ 默认表为 `app_store_ratings_state`、`app_store_ratings_snapshots`、`app_store_ratings_deliveries`;`build_rating_tables(metadata, prefix=...)` 支持同库独立监控器。三表必须在同一数据库,保证有效租约下快照与 outbox 原子提交。库每分钟检查到期计划;每天只生成一份跨应用、地区日报,长文按 2000 UTF-16 单位无损分段。
74
+
75
+ 读取地址为 `https://itunes.apple.com/lookup?id=1234567890&country=cn`,只使用 `userRatingCount` 与 `averageUserRating` 全版本字段。日/周基准分别为昨天和七天前,月基准为上月同日(月末截断);按时区隔离历史,不跨地区相加。均分保留原始返回精度,展示累计均分之差;不足展示精度的非零变化明确标记,不伪造新增评分平均分。
76
+
77
+ 缺失历史、基准日失败和当前采集失败分别展示;零值只来自有效零评分响应。当日采集失败也定稿日报,同日不再拉取;发送失败仅重试已生成内容。停机只采集当前已到期日期,不补造历史。历史默认保留 90 天,最少 32 天;切换时区不改写旧快照,已经生成的旧时区日报仍可投递。发送成功但本地确认前中断可能重复发送,不承诺严格一次投递。公开聚合数包含仅打星和文字评价附带的星级,无法识别逐条无文字评分。
78
+
79
+ 每份日报的全部分段同时固定保存应用 ID 名单。投递前若发现其中任一应用已从当前配置撤销,整份日报标记为 `suppressed` 并清空内容,不重算或发送其中部分内容;完整停用后重新启用子集也执行此检查。只调整时区而未撤销应用时,旧时区待发日报仍正常投递。
80
+
81
+ 采集跨过配置时区的午夜时,停止后续地区请求,整轮定稿为“采集跨日,本轮数据不可用”,包括丢弃午夜前已取得的部分成功值,避免把跨日数据写作昨日成功基线。失败快照的 `day` 为计划日期,`collected_at` 为实际失败完成时间;日报显示实际时间。计划日已完成后不重拉,次日仍按配置时刻执行。当前不保存逐项采集时间,需保留跨日部分成功值时再扩展该能力。
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: dwy-auth
3
+ description: 使用或排查 dwy-auth 认证与会话能力时使用;先核实项目安装版本及公开 API。
4
+ ---
5
+
6
+ # dwy-auth
7
+
8
+ 认证主体、邮箱验证码、会话和管理端认证;核实适配器、配置、存储及 HTTP 路由由宿主如何装配。
9
+
10
+ ## 查找与使用
11
+
12
+ 项目使用 0.2.0 时可阅读 [0.2.0 接入和示例](references/v0/0.2.0.md);其他版本先查该版本的资料和代码,不把此快照当作其他版本的合同。
13
+
14
+ 1. 检查项目锁文件和当前 Python 环境;用 `importlib.metadata.version("dwy-auth")` 查询实际发行版本,依赖范围不等于实际安装版本。未安装时不得假设可用或自动安装。
15
+ 2. 在实际环境中检查 `dwy_auth` 的公开接口、类型及项目已有调用;editable 安装以实际源码为准。说明、实现或安装版本不一致时以运行代码为准。
16
+ 3. 本索引基于仓库 0.2.0,能力与签名可能随小版本变化。精确版本源码位于仓库 `backend/packages/auth`,文档为该目录的 `README.md`;只在确认其版本与消费项目一致时使用。缺少安装源码时查对应 `dwy-auth@版本` 标签,不能用默认分支冒充。
17
+ 4. 在已有调用和该版本文档中核对配置、必需依赖、输入校验、失败处理及生命周期后接入。没有足够证据时说明缺口,不猜 API。
18
+
19
+ ## 0.x 已核实差异
20
+
21
+ - 0.1.0 提供独立认证运行时、Google 和可选邮箱认证;0.1.1 增加 Apple 登录验证与撤销队列。
22
+ - 0.2.0 增加可选滑动刷新与凭据代次状态;升级涉及宿主数据库迁移。按实际版本文档核实迁移与会话配置,不将这些能力用于 0.1.x。
23
+
24
+ 不要因为发现此库就改动项目依赖;新增依赖遵循项目选型规则。关联 FastAPI 或其他自有包时同时参考可用的对应 Skill。
@@ -0,0 +1,163 @@
1
+ <!-- 来源:dwy-auth@0.2.0 的 backend/packages/auth/README.md;文中相对链接相对原仓库路径。 -->
2
+
3
+ # dwy-auth
4
+
5
+ New integrations use `AuthRuntime` with application-owned users and a replaceable `SessionBackend`. See [runtime integration](../../../docs/auth-runtime-integration.md). The authentication core is independent of EAPI and FastAPI; the legacy service, HTTP and administrator interfaces below require EAPI when used.
6
+
7
+ Python distribution `dwy-auth==0.2.0`, import `dwy_auth`. Install the independent core from public PyPI with `pip install "dwy-auth==0.2.0"`. For EAPI integration and compatibility interfaces, install `pip install "dwyeapi[email]==0.12.1" "dwy-auth==0.2.0"`; the older `dwyeapi[auth]==0.12.1` aggregate extra pins authentication to 0.1.1. Source is available in the public repository; registry releases use `.github/workflows/publish-auth.yml` with GitHub Actions OIDC. The Google user entry and administrator Cookie entry are separate. No dependency on app-cloud or cloud-kernel; no environment variables or global database are read by this package.
8
+
9
+ Upgrading runtime hosts from 0.1.1 requires a host-owned Alembic revision: add the `auth_runtime_credential_versions` table from `build_runtime_tables`, plus nullable `subject` and `credential_version` columns on `auth_runtime_email_challenges`. Snapshot these definitions in the revision; do not import a future builder inside historical migrations. Existing accounts and sessions remain intact; application admission remains opt-in.
10
+
11
+ When upgrading a preview integration, update both the dependency name and Python imports. The new wheel exports only `dwy_auth`; no legacy namespace alias is shipped. The source package is `backend/packages/auth` and release tags use `dwy-auth@VERSION`.
12
+
13
+ User authentication uses the existing Google subject identity, app-scoped SQL tables, 15-minute opaque access tokens, rotating refresh families, device revocation and transactional account deletion. The application owns its engine/session factory, metadata and Alembic history. No migrations are created or run automatically. Google configuration accepts one explicit Web OAuth audience; a missing configuration closes login.
14
+
15
+ ```python
16
+ from fastapi import FastAPI
17
+ from dwyeapi.exceptions import register_exception_handlers
18
+ from dwy_auth import AuthService, GoogleConfig, OfficialGoogleVerifier, build_auth_tables, install_auth_routes
19
+
20
+ # metadata and sessions are owned by this application.
21
+ service = AuthService("myapp", sessions, build_auth_tables(metadata), OfficialGoogleVerifier(
22
+ GoogleConfig(client_id=google_client_id)
23
+ ))
24
+ app = FastAPI()
25
+ register_exception_handlers(app)
26
+ install_auth_routes(app, service, authorization_limit=8192)
27
+ ```
28
+
29
+ `dwyeapi.identity.Principal` and `IdentityVerifier` are the canonical identity contract. `create_identity_dependency(app_id, verifier, authorization_limit=...)` binds business routes to an explicitly chosen application/verifier. `install_auth_routes` uses its AuthService by default and accepts an explicitly injected trusted `identity_verifier` for existing host adapters. The host must apply request-body limits before parsing (the Google token schema separately limits token length to 16384 characters). Validation errors installed by this package omit submitted credentials.
30
+
31
+ ```python
32
+ from dwy_auth.admin import AdminAuthConfig, install_admin_auth
33
+
34
+ admin = install_admin_auth(app, AdminAuthConfig(
35
+ username=admin_username,
36
+ password=admin_password,
37
+ secure_cookie=True,
38
+ origin="https://admin.example.com",
39
+ ))
40
+ # Protect host-owned business routes with Depends(admin.require_admin).
41
+ # Also add Depends(admin.same_origin) to host-owned mutation routes.
42
+ ```
43
+
44
+ Admin paths remain `/api/admin/auth/login`, `/me`, `/logout`. Cookies remain HttpOnly, SameSite=Strict, scoped to `/api/admin`; production callers explicitly enable Secure and enforce their deployment password policy. A configured origin is matched exactly. Without one, an Origin header must match the request scheme/host; forwarded headers are not trusted. Requests without Origin preserve the existing API contract. Standalone admin installation bounds auth request bodies at 4096 bytes and forbids caching. A host that already protects every admin path can pass `install_security=False` and retain that middleware.
45
+
46
+ Admin state is in memory: restarting revokes every session, with one host worker required for this existing implementation. Sessions, rate limits and credentials are independent of user access/refresh tokens. The binding grants no user Principal and contains no business queries or configuration storage.
47
+
48
+ Run `pytest tests` from this directory with the dev extra installed. `tests/packaging_smoke.py` additionally runs against installed wheels in a clean environment and asserts the host, kernel and business packages are absent. PostgreSQL concurrency cases need an explicitly provided `APP_CLOUD_TEST_POSTGRES_URL`.
49
+
50
+ ## Email/password authentication
51
+
52
+ Email/password is opt-in. Email accounts have stable random UUIDs and an independent unique `(app_id,email)` credential; email normalization only trims and lowercases. A Google account with the same email is never merged. New/reset passwords require at least 15 Unicode code points and at most 72 UTF-8 bytes; login accepts nonempty existing passwords up to 72 bytes. Passwords are never trimmed or truncated. EAPI bcrypt executes in a bounded worker pool (4 concurrent, at most 32 queued/running per service); cancellation keeps running work charged to this budget.
53
+
54
+ ```python
55
+ from dwy_auth import EmailAuthConfig, EapiEmailCodeSender, SessionPolicy
56
+ from dwyeapi.providers.email import make_email_provider
57
+
58
+ # email_settings and challenge_secret come from the host's validated settings.
59
+ # Every worker must share the same secret (at least 32 bytes). Never commit it.
60
+ provider = make_email_provider(email_settings)
61
+ service = AuthService(
62
+ "myapp", sessions, build_auth_tables(metadata), google_verifier,
63
+ email_sender=EapiEmailCodeSender(provider),
64
+ email_config=EmailAuthConfig(challenge_secret=challenge_secret),
65
+ session_policy=SessionPolicy(refresh_ttl=180 * 86400, rolling_refresh=True),
66
+ )
67
+ install_auth_routes(app, service)
68
+ ```
69
+
70
+ Use the matching EAPI version containing `EmailProviderBase.send_verification_code`. This public transport-only method reuses existing Resend templates/domain policy and does not access Redis. Match the provider template TTL with `EmailAuthConfig.code_ttl`. The host owns the email sender/provider lifecycle. Without email configuration all email routes fail closed with `EMAIL_NOT_CONFIGURED`.
71
+
72
+ Both authentication HTTP installers accept an optional `Accept-Language` header (maximum 512 characters) for email codes. Any valid `zh`/`zh-*` range with nonzero quality selects Chinese; missing, malformed, excluded (`zh;q=0`), or other languages select English. The subject, HTML, and plain-text body use the same language. The current Apple client explicitly sends `zh` for a Chinese first preferred language and `en` otherwise. Language is passed per call and never changes code identity or resend limits.
73
+
74
+ Custom senders must accept `send(email, code, purpose, *, language="en")`; custom EAPI providers must accept `_send(target, code, *, language="en")`. Programmatic calls may pass `send_email_code(..., language="zh")`; only `zh` and `en` are supported. Configure `resend.subject` for Chinese and `resend.subject_en` for English. English brand taglines/slogans use `brand_tagline_en`/`brand_slogan_en` (empty by default), without falling back to Chinese promotional copy. Brand names remain as configured.
75
+
76
+ | POST path | JSON input | SUCCESS data |
77
+ |---|---|---|
78
+ | `/api/v1/auth/email/code` | `email,purpose` (`register` or `password_reset`) | `challenge_id,expires_in,resend_after,code_length` |
79
+ | `/api/v1/auth/register` | `email,password,code,challenge_id,device_id` | Existing `TokenResponse`; automatically logged in |
80
+ | `/api/v1/auth/login` | `email,password,device_id` | Existing `TokenResponse` |
81
+ | `/api/v1/auth/password/reset` | `email,password,code,challenge_id` | `{"status":"completed"}`; all existing user sessions revoked |
82
+
83
+ Codes are six digits, expire after 300 seconds and have a 60-second resend cooldown by default. Response `expires_in` and `resend_after` report actual remaining database deadlines after delivery; a delivery that outlasts the challenge fails and removes that challenge. Only a successful delivery activates a challenge; a new delivered challenge invalidates older challenges for the same app/email/purpose. The database stores an HMAC bound to app/email/purpose/challenge, never the code. Conditional updates make consumption single-use; failed-attempt counters commit independently so business rollback cannot replenish attempts. Each challenge allows at most five attempts. Atomic SQL budgets apply per app, peer IP, purpose/email and action; send limits are 50/IP/hour, 1000/app/hour, 10/email/purpose/hour; verification is 100/IP/15min and 20/email/purpose/15min; login is 100/IP/15min and 10/email/15min. Expired counters/challenges are pruned on use. Configure trusted proxy middleware at the host if deployed behind a proxy; request forwarding headers are not read directly. Continue enforcing host request-body and ingress limits.
84
+
85
+ Errors never echo submitted secrets. Public codes are `EMAIL_INVALID`, `PASSWORD_INVALID`, `EMAIL_PURPOSE_INVALID`, `EMAIL_CODE_INVALID` (wrong/expired/used/exhausted), `EMAIL_ALREADY_REGISTERED`, `CREDENTIALS_INVALID`, `EMAIL_RATE_LIMITED`, `EMAIL_NOT_CONFIGURED`, `EMAIL_DELIVERY_FAILED`. Schema validation uses the existing `VALIDATION_ERROR` envelope. Failed delivery remains charged to resend budgets to prevent abusive retries. Delivery to existing and unknown accounts follows the same public send contract.
86
+
87
+ The default `SessionPolicy()` retains the existing fixed 30-day refresh deadline and 15-minute access lifetime. Explicit rolling policy extends refresh validity to 180 days on successful refresh. Old refresh digests remain replay evidence for at most `replay_retention` (default 30 days) and expire independently, so normal 15-minute refreshes do not accumulate a lifetime 4096 cap. The 4096 limit still bounds *unexpired* evidence (normal 30-day use is 2880); an abusive burst gets 429 without invalidating the current token. Refresh replay, device revocation and account deletion remain enforced. Account deletion removes email credentials/challenges transactionally; it preserves the anonymized UUID for independent purchase ledgers.
88
+
89
+ ### Host Alembic migration
90
+
91
+ Fresh applications include all six auth tables via `build_auth_tables(metadata)` in their own migration. Existing Google-only databases must migrate before enabling the new package; `metadata.create_all` does not alter the old non-nullable subject column. Add a revision to the **host** Alembic history:
92
+
93
+ ```python
94
+ from alembic import op
95
+ from dwy_auth.migrations import upgrade_email_auth
96
+
97
+ # revision/down_revision belong to the host migration history.
98
+ def upgrade():
99
+ upgrade_email_auth(op)
100
+ ```
101
+
102
+ The helper makes `auth_users.google_subject` nullable and creates `auth_password_credentials`, `auth_email_challenges`, `auth_rate_limits`; existing UUIDs, sessions and Google subjects remain untouched. PostgreSQL alters the column in place. SQLite uses Alembic batch mode; hosts with foreign keys enabled must follow Alembic's SQLite foreign-key PRAGMA procedure outside the transaction. Downgrade is intentionally host-owned: email-only users have null Google subjects, so restoring NOT NULL requires an explicit data-retention decision. Do not run this helper at application startup. `tests/test_email_auth.py` migrates a populated legacy Google schema and verifies both existing sessions and new email registration afterward.
103
+
104
+ ## Apple 原生登录(dwy-auth 0.1.1 / dwyeapi 0.12.1)
105
+
106
+ 已通过 GitHub Actions OIDC 发布到公开 PyPI;独立配置与迁移步骤见 [Apple 后端接入](../../../docs/apple-auth-integration.md)。
107
+
108
+ 使用 `dwy_auth.apple.AppleSignInConfig`、`AppleGateway`、`dwy_auth.apple_auth.AppleAuthService` 和 `dwy_auth.apple_storage.build_apple_tables(metadata)`。多步骤 Apple 授权使用独立挑战/登录接口;不能把 Apple ID token 塞进通用 external credential 接口绕过 nonce 和授权码验证。
109
+
110
+ 宿主提供自己的 AuthRuntime、业务主体、会话撤销回调及 Alembic 迁移,并在现有 `install_runtime_auth` 后安装 `dwyeapi.auth_apple.install_apple_auth`。把 service 注册为 runtime 的 apple provider,驱动 `process_revocations()`,在业务删号事务内调用 `prepare_delete(db, subject)`;其返回值为撤销任务 ID,可用于区分本地删除完成与上游撤销 pending。签名 Key 与 Fernet token_encryption_key 只保留服务端,不能复用内购配置或按邮箱合并账号。
111
+
112
+ 原生 Swift 接入使用共享 AuthCore 与 AuthAppleIOS,说明见仓库根 `ios/Docs/APPLE_AUTH.md`。每个应用独立配置 client_id、密钥、数据库与业务 UUID,代码共享不代表跨产品 SSO。
113
+
114
+ ## 已登录账号的邮箱与登录方式管理
115
+
116
+ `AuthRuntime.send_email_update_code(principal, email, *, reauthentication, remote_key="local", language="en")`
117
+ 消费 `purpose="email_update"` 的 `VerifiedIdentity`,将验证码绑定当前 app、subject、规范化目标邮箱和凭据代次。
118
+ `AuthRuntime.update_email(principal, email, code, challenge_id, *, new_password=None, remote_key="local")`
119
+ 在宿主事务中消费挑战、更新邮箱凭据与已验证主邮箱、撤销所有旧会话并签发同一 subject 的新会话。
120
+ 首次添加密码必须提供符合共享密码政策的新密码;已有密码可保留。Google issuer/sub 不变,邮箱不用于自动合并。
121
+ 公开 `send_email_code` 不接受 `email_update`,因此注册/重置验证码不能绕过重新认证。
122
+
123
+ 启用变更的宿主适配器实现 `EmailSubjectAdapter.update_email(db, subject, email)`,与挑战/凭据/会话共同提交,不能自行提交或更换 subject。
124
+ 未实现此接口的宿主在发码前返回 `EMAIL_UPDATE_NOT_CONFIGURED`,既有注册/登录不受影响。
125
+ 主邮箱经用户验证后,宿主不得用 Google profile 邮箱覆盖它。
126
+
127
+ EAPI `install_runtime_auth` 额外提供:
128
+
129
+ - POST `/api/v1/auth/email/update/code`:`email` 与 `reauthentication`,需要当前 Bearer。
130
+ - POST `/api/v1/auth/email/update`:`email/code/challenge_id/new_password?`,需要当前 Bearer,返回既有 token/user 信封。
131
+ - GET `/api/v1/auth/identities`:当前主体可用入口的 `LoginIdentity` 数组,仅含 `kind/value/provider/issuer`;密码、版本摘要与 token 不进入响应。
132
+
133
+ 重新认证 `method` 仍为 `email/username/external`;Google 使用 `method=external, identifier=google, credential=<ID token>`。
134
+ 已有外部绑定/解绑接口继续执行主体证明、唯一归属与最后一种可用登录方式保护。
135
+
136
+ 宿主 Alembic revision 在启用前升级 `build_runtime_tables` 使用的实际前缀:
137
+
138
+ - 已有 `email_challenges` 增加可空 `subject VARCHAR(255)`、`credential_version VARCHAR(64)`。
139
+ - 新建 `credential_versions`,`app_id VARCHAR(32)` 与 `subject VARCHAR(255)` 为联合主键,`generation VARCHAR(36) NOT NULL`。
140
+
141
+ 旧挑战两列保持 NULL,不补认旧验证码。代次由首次凭据变更创建;改邮箱、改密、重置、绑定和解绑都会同事务轮换代次并核销未完成挑战。
142
+ 凭据内容恢复成旧值也不会恢复旧挑战,发信中的旧快照同样失效。共享包不在启动时执行迁移。
143
+
144
+
145
+ ## 可选应用准入
146
+
147
+ - `AuthRuntime(..., admission=...)` 接受 `AuthAdmission`:`prepare(proof)` 在数据库事务外验证本次请求证明,`admit(db, subject, prepared)` 在主体、凭据、会话共用事务内验证或写入业务归属。准入不得自行提交事务。仅配置准入时,在 `prepare` 外网工作前复用独立 `admission-ip` 预算(每个可信来源 900 秒最多 100 次);refresh 使用与登录相同的来源解析。默认不配置,不强制业务资格。
148
+ - 对外登录调用 `runtime.login(method, ..., admission_proof=...)`;注册调用 `runtime.register_email_session(...)`。配置准入后,独立 `register_email`、登录用途 `authenticate`、`issue` 拒绝绕过;内部 `_db` 参数只供受信运行时事务组合,不能暴露给请求。
149
+ - `refresh(..., admission_proof=...)` 必须提供本次证明。会话后端需实现可选 `before_lock(db, principal)` 纯主体锁检查(CAS 前,保持 account→session 锁序)和 `before_rotate(db, principal)` 准入写入回调,并在原有刷新事务内、CAS 与配额检查成功后且生成新 token 前调用,准入失败必须回滚尚未生效的轮换。未配置准入仍兼容原会话实现;自定义后端启用准入前必须实现这个合同。
150
+ - EAPI 认证请求总预算为 163840 字节;`admission_proof` 最多 131072 个 UTF-8 字节,日志和模型表示不包含原文。邮箱发码、忘记密码不签发会话,不要求准入证明;变更邮箱会签发新会话,因此同样要求证明。
151
+
152
+ Apple 专用 `AppleAuthService.login(..., admission_proof=...)` 使用同一准入配置:先验证证明,再消费 Apple 挑战与授权码;准入归属、账号、授权与会话共同提交,拒绝时回滚并持久化上游撤销任务。
153
+
154
+ ### Apple-only 账号的管理重新认证
155
+
156
+ 先用当前 Bearer 向 `/api/v1/auth/apple/challenges` 提交 `purpose=reauth_email_update`、`reauth_bind` 或 `reauth_unlink`;服务端将挑战绑定当前 subject 与具体操作。原生授权使用服务端原始 nonce。
157
+ 管理请求的 `reauthentication` 使用 `method=external, identifier=apple`,其 `credential` 为包含 `challenge_id`、`identity_token`、`authorization_code` 的 JSON 字符串(最多 32768 个 UTF-8 字节)。EAPI 通过 `AppleAuthService.reauthenticate` 兑换一次性 code 并生成当前主体、限用途、单次消费的 proof;跨主体、跨操作或重放均拒绝。通用 Apple 登录仍拒绝单凭据路径,不能恢复旧 `AppleIdentityProvider` 或客户端自建 nonce 方案。
158
+
159
+ ## JWT 会话滑动有效期
160
+
161
+ `JWTSessionBackend(..., rolling_refresh=True)` 在每次刷新成功时,把会话期限延到当时起 `refresh_ttl` 秒(默认 30 天);第 25 天刷新后到期日为第 55 天。默认 `False` 保持签发起固定期限,不影响已有调用方。普通业务请求不会续期;已经过期或撤销的会话不能恢复。客户端继续使用现有刷新接口和返回的轮换凭据,无需修改协议或数据库结构。
162
+
163
+ 滑动模式下,每个已消费 refresh 摘要从消费时起再保留 `refresh_ttl` 秒用于重放撤销,刷新成功时只清理同应用、同 family 的到期摘要。4096 条上限约束有效证据窗口,不再累计终身刷新次数;短时间超额仍返回 `REFRESH_QUOTA_EXCEEDED` 并撤销当前 family,需重新登录。CAS 轮换、期限更新与准入写入共同提交,失败回滚不会消费旧凭据或延长期限。
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: dwy-device-fingerprint
3
+ description: 使用或排查 dwy-device-fingerprint 设备指纹能力时使用;先核实项目安装版本及公开 API。
4
+ ---
5
+
6
+ # dwy-device-fingerprint
7
+
8
+ 设备信息采集和状态;检查目标平台支持范围、权限和失败结果,不假定采集值始终存在。
9
+
10
+ ## 查找与使用
11
+
12
+ 项目使用 0.1.1 时可阅读 [0.1.1 接入和示例](references/v0/0.1.1.md);其他版本先查该版本的资料和代码,不把此快照当作其他版本的合同。
13
+
14
+ 1. 检查项目锁文件和当前 Python 环境;用 `importlib.metadata.version("dwy-device-fingerprint")` 查询实际发行版本,依赖范围不等于实际安装版本。未安装时不得假设可用或自动安装。
15
+ 2. 在实际环境中检查 `dwy_device` 的公开接口、类型及项目已有调用;editable 安装以实际源码为准。说明、实现或安装版本不一致时以运行代码为准。
16
+ 3. 本索引基于仓库 0.1.1,能力与签名可能随小版本变化。精确版本源码位于仓库 `backend/packages/device-fingerprint`,文档为该目录的 `README.md`;只在确认其版本与消费项目一致时使用。缺少安装源码时查对应 `dwy-device-fingerprint@版本` 标签,不能用默认分支冒充。
17
+ 4. 在已有调用和该版本文档中核对配置、必需依赖、输入校验、失败处理及生命周期后接入。没有足够证据时说明缺口,不猜 API。
18
+
19
+ 不要因为发现此库就改动项目依赖;新增依赖遵循项目选型规则。关联 FastAPI 或其他自有包时同时参考可用的对应 Skill。
@@ -0,0 +1,90 @@
1
+ <!-- 来源:dwy-device-fingerprint@0.1.1 的 backend/packages/device-fingerprint/README.md;文中相对链接相对原仓库路径。 -->
2
+
3
+ # dwy-device-fingerprint
4
+
5
+ eapi 体系下独立的跨平台设备指纹核心,Python 3.9+,零运行时依赖。只采集并返回结果,不联网、不记录设备日志、不执行限制策略。
6
+
7
+ ## 安装与使用
8
+
9
+ 源码仓库根目录安装:
10
+
11
+ ```bash
12
+ uv pip install ./backend/packages/device-fingerprint
13
+ ```
14
+
15
+ 安装公开 PyPI 已发布版本:
16
+
17
+ ```bash
18
+ python -m pip install dwy-device-fingerprint
19
+ ```
20
+
21
+ 完整示例只打印采集状态,不把设备指纹写入日志:
22
+
23
+ ```python
24
+ from dwy_device import collect_device
25
+
26
+
27
+ def device_headers() -> dict[str, str]:
28
+ """客户端初始化时采集一次,后续请求复用结果。"""
29
+ info = collect_device("example.app", command_timeout=2.0)
30
+ if info.fingerprint is None:
31
+ return {}
32
+ return {"X-Device-Id": info.fingerprint}
33
+
34
+
35
+ if __name__ == "__main__":
36
+ headers = device_headers()
37
+ print("available" if headers else "unavailable")
38
+ ```
39
+
40
+ `namespace` 必须为 1 至 128 个小写 ASCII 字母、数字、点、下划线或连字符,产品与其 SDK/桌面端使用同一固定值。`command_timeout` 为有限正数,最大 30 秒;非法调用参数抛 ValueError。
41
+
42
+ ### 返回结果
43
+
44
+ `DeviceInfo` 与 `SystemInfo` 是本库不可变 dataclass,状态、来源和错误使用本库 str Enum。
45
+
46
+ | 字段 | 内容 |
47
+ | --- | --- |
48
+ | fingerprint | 可用时 64 位小写十六进制,否则 None |
49
+ | scheme_version | 1 |
50
+ | source | Windows MachineGuid、macOS IOPlatformUUID 或 Linux machine-id 的明确来源 |
51
+ | status | available、unavailable、unsupported |
52
+ | error_code | 无错误为 None;否则为缺失、权限、超时、无效值、命令失败或不支持平台 |
53
+ | system | system_name、system_release、system_version、architecture |
54
+
55
+ 系统信息来自 platform 标准库,Darwin/Linux 的 release 为内核发行信息,不代表产品营销版本。原始机器身份不包含在结果中。
56
+
57
+ ### 稳定性
58
+
59
+ - Windows 使用 64 位注册表 MachineGuid;macOS 读取 ioreg 的 IOPlatformUUID;Linux 优先 /etc/machine-id,仅缺失时读取 /var/lib/dbus/machine-id。
60
+ - 规范化机器身份作为 HMAC-SHA256 key,`dwy.device.v1:` 加 namespace 作为 message。不同产品域隔离,同产品域跨账号配置、进程、项目和 venv 保持一致。
61
+ - OS 元数据和 SDK 版本不参与派生。原始系统身份、namespace 或方案版本改变会影响结果。
62
+ - 无可靠来源时返回结构化失败,不降级到随机值、MAC、hostname 或安装文件;不自动重试、不缓存到磁盘。需要缓存时由客户端实例保存结果。
63
+ - 容器、虚拟机、WSL 只识别当前系统环境,不能保证物理宿主唯一。克隆镜像、重装系统或修改客户端可以绕过,指纹不用于身份认证。
64
+ - 本接口包含同步系统 I/O;异步应用在初始化或工作线程调用,不在每个请求中运行。
65
+
66
+ ## eapi 可选门面
67
+
68
+ Python 3.11+ 且已安装匹配核心的 eapi 应用可以从 `dwyeapi.device` 导入同一入口和类型。eapi 根导入不强制加载设备模块。
69
+
70
+ EAPI 的公开版本提供 `device` extra:
71
+
72
+ ```bash
73
+ python -m pip install 'dwyeapi[device]'
74
+ ```
75
+
76
+ 源码仓库保持私有,安装包通过 `.github/workflows/publish-device-fingerprint.yml` 公开发布。SDK 可以依赖固定版本的公开包,不需要源码仓库权限,也无需维护第二份实现。
77
+
78
+ ## 测试与构建
79
+
80
+ 在仓库根目录运行:
81
+
82
+ ```bash
83
+ uv run --directory backend/packages/device-fingerprint --extra dev --frozen pytest
84
+ uv run --directory backend/packages/device-fingerprint --extra dev --frozen ruff check src tests
85
+ uv build --project backend/packages/device-fingerprint --default-index https://pypi.org/simple
86
+ ```
87
+
88
+ macOS/Windows/Linux 原生读取测试需要设置 `DWY_DEVICE_NATIVE_TEST=1`;常规测试使用受控系统边界模拟,不打印真实原始身份。
89
+
90
+ 运行时仅使用标准库。开源参考和许可见 THIRD_PARTY_NOTICES.md 与 LICENSE。
@@ -16,7 +16,7 @@ FastAPI 基础设施包。本文件只做**路由**:探测安装版本 → 选
16
16
  python -c "import dwyeapi; print(dwyeapi.__version__)"
17
17
  ```
18
18
  失败则读项目 `pyproject.toml` / `uv.lock` 里 `dwyeapi` 约束。
19
- 仍失败:按 `v0` 给脚手架,并声明「未探测到安装版本」。
19
+ 声明的依赖范围不代表实际安装版本。仍无法确定时说明缺口,不按任一版本猜测 API。
20
20
 
21
21
  2. **选线**:读同目录 `versions.yml`。`0.y.z` → `v0/`,`1.y.z` → `v1/`,`2.y.z` → `v2/`。目录不存在(例如装了 1.x 但还没有 `v1/`)→ 不要用 `v0` 冒充;按下面「读源码」拿该版本代码,并说明文档线未建。
22
22
 
@@ -5,9 +5,9 @@ tag_prefix: "dwyeapi@"
5
5
  sparse:
6
6
  - backend/src/dwyeapi
7
7
  lines:
8
- - match: ">=2.0.0"
8
+ - match: ">=2.0.0 <3.0.0"
9
9
  dir: v2
10
- - match: ">=1.0.0"
10
+ - match: ">=1.0.0 <2.0.0"
11
11
  dir: v1
12
- - match: ">=0.0.0"
12
+ - match: ">=0.0.0 <1.0.0"
13
13
  dir: v0
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: dwy-email-notify
3
+ description: 使用或排查 dwy-email-notify 邮件事件通知能力时使用;先核实项目安装版本及公开 API。
4
+ ---
5
+
6
+ # dwy-email-notify
7
+
8
+ 邮件平台事件和通知,核实供应商配置、Webhook 验证与宿主处理。
9
+
10
+ ## 查找与使用
11
+
12
+ 项目使用 0.1.0 时可阅读 [0.1.0 接入和示例](references/v0/0.1.0.md);其他版本先查该版本的资料和代码,不把此快照当作其他版本的合同。
13
+
14
+ 1. 检查项目锁文件和当前 Python 环境;用 `importlib.metadata.version("dwy-email-notify")` 查询实际发行版本,依赖范围不等于实际安装版本。未安装时不得假设可用或自动安装。
15
+ 2. 在实际环境中检查 `dwy_email_notify` 的公开接口、类型及项目已有调用;editable 安装以实际源码为准。说明、实现或安装版本不一致时以运行代码为准。
16
+ 3. 本索引基于仓库 0.1.0,能力与签名可能随小版本变化。精确版本源码位于仓库 `backend/packages/email-notify`,文档为该目录的 `README.md`;只在确认其版本与消费项目一致时使用。缺少安装源码时查对应 `dwy-email-notify@版本` 标签,不能用默认分支冒充。
17
+ 4. 在已有调用和该版本文档中核对配置、必需依赖、输入校验、失败处理及生命周期后接入。没有足够证据时说明缺口,不猜 API。
18
+
19
+ 不要因为发现此库就改动项目依赖;新增依赖遵循项目选型规则。关联 FastAPI 或其他自有包时同时参考可用的对应 Skill。
@@ -0,0 +1,31 @@
1
+ <!-- 来源:dwy-email-notify@0.1.0 的 backend/packages/email-notify/README.md;文中相对链接相对原仓库路径。 -->
2
+
3
+ # dwy-email-notify
4
+
5
+ 邮件平台官方 Webhook 事件接入与通知转发。当前提供 Resend,未来平台各自添加 provider 和 extra;本包不提供业务发信、验证码或邮件正文读取功能。
6
+
7
+ ```bash
8
+ pip install 'dwy-email-notify[resend]==0.1.0'
9
+ ```
10
+
11
+ ```python
12
+ from fastapi import FastAPI
13
+ from dwy_notify import NotificationConfig
14
+ from dwy_email_notify import EmailNotifyConfig, ResendWebhookConfig, install_email_notifications
15
+
16
+ def configure(app: FastAPI, signing_secret: str, notification_url: str) -> None:
17
+ install_email_notifications(app, EmailNotifyConfig(
18
+ notifications=NotificationConfig(targets={"operations": notification_url}),
19
+ resend=ResendWebhookConfig(secret=signing_secret),
20
+ ))
21
+ ```
22
+
23
+ 配置由宿主从安全配置中显式注入,本包不读取环境或 dotenv。Resend 只需要独立的 Webhook signing secret,无需发信 API key。默认接收路径为 `/webhooks/resend`,可由配置覆盖;只有配置来源后才加载对应 SDK。
24
+
25
+ 事件经过原始载荷大小限制、官方 SDK 验签及事件筛选后才交付。仅转发事件类型、邮件编号和通知编号,不读取或转发主题、正文、收件人。默认转发使用“邮件通知”名称及固定 GitHub PNG 头像,应用可以通过 `NotificationConfig.appearance` 覆盖名称或显式清除头像。最终发送者名称必填;本包提供默认名称。
26
+
27
+ 默认内存去重只覆盖当前进程,只有成功发送才登记。需要重启后仍可靠接收的宿主,应通过 `sink` 注入自己的持久化接收函数;返回 `True` 表示新事件被接受,`False` 表示已接受的重复事件,失败必须抛出异常,不能提前向 Resend 确认成功。
28
+
29
+ 安装器返回 `EmailNotifyRuntime`,只持有去重状态,不接管宿主数据库、异常处理器或 lifespan。同一 FastAPI 应用只安装一次。`providers.resend.webhooks.create_resend_router` 可用于自行组合路由。
30
+
31
+ 通知头像保持已有固定公开资产引用,不打包图片或拉取浮动分支:`https://raw.githubusercontent.com/danweiyuancircle/dwy-ai-experience/091ad4fea18ed3840fec83dc833f18b759256ca7/backend/packages/webhooks/assets/email.png`。
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: dwy-notify
3
+ description: 使用或排查 dwy-notify 多目标通知能力时使用;先核实项目安装版本及公开 API。
4
+ ---
5
+
6
+ # dwy-notify
7
+
8
+ 多目标通知配置、字段与发送结果;核实目标配置、失败与实际发送行为。
9
+
10
+ ## 查找与使用
11
+
12
+ 项目使用 0.3.2 时可阅读 [0.3.2 接入和示例](references/v0/0.3.2.md);其他版本先查该版本的资料和代码,不把此快照当作其他版本的合同。
13
+
14
+ 1. 检查项目锁文件和当前 Python 环境;用 `importlib.metadata.version("dwy-notify")` 查询实际发行版本,依赖范围不等于实际安装版本。未安装时不得假设可用或自动安装。
15
+ 2. 在实际环境中检查 `dwy_notify` 的公开接口、类型及项目已有调用;editable 安装以实际源码为准。说明、实现或安装版本不一致时以运行代码为准。
16
+ 3. 本索引基于仓库 0.3.2,能力与签名可能随小版本变化。精确版本源码位于仓库 `backend/packages/notify`,文档为该目录的 `README.md`;只在确认其版本与消费项目一致时使用。缺少安装源码时查对应 `dwy-notify@版本` 标签,不能用默认分支冒充。
17
+ 4. 在已有调用和该版本文档中核对配置、必需依赖、输入校验、失败处理及生命周期后接入。没有足够证据时说明缺口,不猜 API。
18
+
19
+ ## 0.x 已核实差异
20
+
21
+ - 0.2.0 起发送者名称必须有明确来源;0.2.1 提供来源无关事件合同与可选持久队列。
22
+ - 0.3.0 增加与宿主事务共用的入队和指定失败目标重放;0.3.1 增加通知字段渲染;0.3.2 调整 dwyeapi 兼容范围。较早版本不要假设这些能力存在。
23
+
24
+ 不要因为发现此库就改动项目依赖;新增依赖遵循项目选型规则。关联 FastAPI 或其他自有包时同时参考可用的对应 Skill。