mongrade 0.1.0a1__tar.gz

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 (33) hide show
  1. mongrade-0.1.0a1/MANIFEST.in +12 -0
  2. mongrade-0.1.0a1/PKG-INFO +79 -0
  3. mongrade-0.1.0a1/README.md +55 -0
  4. mongrade-0.1.0a1/docs/design.md +78 -0
  5. mongrade-0.1.0a1/docs/guide.md +95 -0
  6. mongrade-0.1.0a1/docs/publishing.md +39 -0
  7. mongrade-0.1.0a1/pyproject.toml +79 -0
  8. mongrade-0.1.0a1/setup.cfg +4 -0
  9. mongrade-0.1.0a1/src/mongrade/__init__.py +26 -0
  10. mongrade-0.1.0a1/src/mongrade/__main__.py +7 -0
  11. mongrade-0.1.0a1/src/mongrade/_version.py +4 -0
  12. mongrade-0.1.0a1/src/mongrade/catalog.py +228 -0
  13. mongrade-0.1.0a1/src/mongrade/cli.py +189 -0
  14. mongrade-0.1.0a1/src/mongrade/context.py +97 -0
  15. mongrade-0.1.0a1/src/mongrade/executor.py +312 -0
  16. mongrade-0.1.0a1/src/mongrade/legacy.py +158 -0
  17. mongrade-0.1.0a1/src/mongrade/models.py +58 -0
  18. mongrade-0.1.0a1/src/mongrade/planner.py +171 -0
  19. mongrade-0.1.0a1/src/mongrade/py.typed +0 -0
  20. mongrade-0.1.0a1/src/mongrade/state.py +357 -0
  21. mongrade-0.1.0a1/src/mongrade.egg-info/PKG-INFO +79 -0
  22. mongrade-0.1.0a1/src/mongrade.egg-info/SOURCES.txt +31 -0
  23. mongrade-0.1.0a1/src/mongrade.egg-info/dependency_links.txt +1 -0
  24. mongrade-0.1.0a1/src/mongrade.egg-info/entry_points.txt +2 -0
  25. mongrade-0.1.0a1/src/mongrade.egg-info/requires.txt +7 -0
  26. mongrade-0.1.0a1/src/mongrade.egg-info/top_level.txt +1 -0
  27. mongrade-0.1.0a1/test/check_artifacts.py +40 -0
  28. mongrade-0.1.0a1/test/fakes.py +150 -0
  29. mongrade-0.1.0a1/test/test_cli.py +84 -0
  30. mongrade-0.1.0a1/test/test_distribution.py +48 -0
  31. mongrade-0.1.0a1/test/test_engine.py +623 -0
  32. mongrade-0.1.0a1/test/test_real.py +268 -0
  33. mongrade-0.1.0a1/test/test_review_regressions.py +201 -0
@@ -0,0 +1,12 @@
1
+ include README.md
2
+ recursive-include src/mongrade *.py py.typed
3
+ include docs/guide.md docs/design.md docs/publishing.md
4
+ recursive-include test *.py
5
+ prune tmp
6
+ prune .venv
7
+ prune .github
8
+ global-exclude *.py[cod]
9
+
10
+ exclude AGENTS.md CLAUDE.md
11
+ exclude docs/00_handoff.md docs/source-provenance.json
12
+ exclude docs/delivery-*.json
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.1
2
+ Name: mongrade
3
+ Version: 0.1.0a1
4
+ Summary: 轻量、可验证、可恢复的 MongoDB 版本迁移工具
5
+ Project-URL: Repository, https://github.com/deepaint-ai/mongrade
6
+ Keywords: mongodb,migration,pymongo,database
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.7
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Database
21
+ Requires-Python: >=3.7
22
+ Description-Content-Type: text/markdown
23
+ Provides-Extra: dev
24
+
25
+ # Mongrade
26
+
27
+ 轻量、可验证、可恢复的 MongoDB 版本化迁移工具。
28
+
29
+ Mongrade 使用 PyMongo,最低支持 Python 3.7。引擎独立于服务框架,提供单链版本管理、静态迁移计划、并发互斥、后置验证、checkpoint 恢复及旧库显式承接。它是纯 Python 包,目标制品为 `py3-none-any` wheel 和 sdist。
30
+
31
+ ## 安装和使用
32
+
33
+ ```console
34
+ python -m pip install mongrade==0.1.0a1
35
+ mongrade --version
36
+ mongrade graph
37
+ mongrade diagnose
38
+ mongrade plan --evidence files/maintenance.json
39
+ ```
40
+
41
+ 首个预览版为 `0.1.0a1`,用于验证发布和接入流程,尚不承诺生产就绪;上传成功后可使用上述 PyPI 命令安装。`python -m mongrade` 与控制台入口相同。连接通过运行时环境变量 `MONGRADE_URI` 注入,URI 必须声明与项目一致的业务库及 authSource;不要将凭据写入项目或日志。
42
+
43
+ ## 公共 API
44
+
45
+ ```python
46
+ from mongrade import connect, load_catalog, plan, migrate, verify
47
+
48
+ catalog = load_catalog("mongo_migrations/project.json")
49
+ with connect(uri, catalog) as client:
50
+ approved = plan(client, catalog, evidence=evidence)
51
+ # 在独立的发布审核阶段固定 approved.sha256 及本次维护材料。
52
+ migrate(client, catalog, approved, evidence)
53
+ verify(client, catalog)
54
+ ```
55
+
56
+ 业务服务自行发布 `mongo_migrations/project.json` 和 `versions/`,脚本实现 `upgrade(context)`;诊断不导入或执行脚本。完整格式和恢复边界见 [接入指南](docs/guide.md) 与 [设计](docs/design.md)。
57
+
58
+ ## 边界
59
+
60
+ - 默认部署角色;生成模板必须显式使用 `--role author`。适配器注入角色后不允许命令行提升角色。
61
+ - 失败保留锁与运行状态,必须经过受审的恢复流程;没有自动降级或跨数据库原子事务。
62
+ - 迁移脚本是受信发布代码,上下文不是安全沙箱;禁止业务 HTTP、UAC、DataSearch、MQ 调用。
63
+ - 首版拒绝多物理业务库和分片写入,备份恢复材料由 DBA 审核,引擎复核文件摘要、时效及范围。
64
+ - 本包不包含 CommonLib 的 SQL 后端、服务启动器或混合数据库部署编排。
65
+
66
+ ## 开发与发布
67
+
68
+ ```console
69
+ python -m pip install ".[dev]"
70
+ python -m pytest
71
+ python -m ruff format --check src test
72
+ python -m ruff check src test
73
+ python -m mypy
74
+ python -m build
75
+ ```
76
+
77
+ 使用独立虚拟环境,测试临时产物统一写入 `tmp/`。发布工作流检出Release(含预发布)对应 tag,版本检查、质量检查、构建和兼容性验证全部通过后,先将同一批 wheel/sdist 上传到当次 GitHub Release,再上传 PyPI;认证和首次发布设置见 [发布指南](docs/publishing.md)。仓库私有不代表 PyPI 包私有。许可证尚待所有者确定,本次迁移不擅自授予开源许可。
78
+
79
+ 离线部署应预先为目标解释器和操作系统准备 PyMongo 依赖;已有匹配依赖时可用 `python -m pip install --no-index --no-deps ./mongrade-0.1.0a1-py3-none-any.whl`。包体跨平台不代表 PyMongo 的二进制依赖可跨平台复用。
@@ -0,0 +1,55 @@
1
+ # Mongrade
2
+
3
+ 轻量、可验证、可恢复的 MongoDB 版本化迁移工具。
4
+
5
+ Mongrade 使用 PyMongo,最低支持 Python 3.7。引擎独立于服务框架,提供单链版本管理、静态迁移计划、并发互斥、后置验证、checkpoint 恢复及旧库显式承接。它是纯 Python 包,目标制品为 `py3-none-any` wheel 和 sdist。
6
+
7
+ ## 安装和使用
8
+
9
+ ```console
10
+ python -m pip install mongrade==0.1.0a1
11
+ mongrade --version
12
+ mongrade graph
13
+ mongrade diagnose
14
+ mongrade plan --evidence files/maintenance.json
15
+ ```
16
+
17
+ 首个预览版为 `0.1.0a1`,用于验证发布和接入流程,尚不承诺生产就绪;上传成功后可使用上述 PyPI 命令安装。`python -m mongrade` 与控制台入口相同。连接通过运行时环境变量 `MONGRADE_URI` 注入,URI 必须声明与项目一致的业务库及 authSource;不要将凭据写入项目或日志。
18
+
19
+ ## 公共 API
20
+
21
+ ```python
22
+ from mongrade import connect, load_catalog, plan, migrate, verify
23
+
24
+ catalog = load_catalog("mongo_migrations/project.json")
25
+ with connect(uri, catalog) as client:
26
+ approved = plan(client, catalog, evidence=evidence)
27
+ # 在独立的发布审核阶段固定 approved.sha256 及本次维护材料。
28
+ migrate(client, catalog, approved, evidence)
29
+ verify(client, catalog)
30
+ ```
31
+
32
+ 业务服务自行发布 `mongo_migrations/project.json` 和 `versions/`,脚本实现 `upgrade(context)`;诊断不导入或执行脚本。完整格式和恢复边界见 [接入指南](docs/guide.md) 与 [设计](docs/design.md)。
33
+
34
+ ## 边界
35
+
36
+ - 默认部署角色;生成模板必须显式使用 `--role author`。适配器注入角色后不允许命令行提升角色。
37
+ - 失败保留锁与运行状态,必须经过受审的恢复流程;没有自动降级或跨数据库原子事务。
38
+ - 迁移脚本是受信发布代码,上下文不是安全沙箱;禁止业务 HTTP、UAC、DataSearch、MQ 调用。
39
+ - 首版拒绝多物理业务库和分片写入,备份恢复材料由 DBA 审核,引擎复核文件摘要、时效及范围。
40
+ - 本包不包含 CommonLib 的 SQL 后端、服务启动器或混合数据库部署编排。
41
+
42
+ ## 开发与发布
43
+
44
+ ```console
45
+ python -m pip install ".[dev]"
46
+ python -m pytest
47
+ python -m ruff format --check src test
48
+ python -m ruff check src test
49
+ python -m mypy
50
+ python -m build
51
+ ```
52
+
53
+ 使用独立虚拟环境,测试临时产物统一写入 `tmp/`。发布工作流检出Release(含预发布)对应 tag,版本检查、质量检查、构建和兼容性验证全部通过后,先将同一批 wheel/sdist 上传到当次 GitHub Release,再上传 PyPI;认证和首次发布设置见 [发布指南](docs/publishing.md)。仓库私有不代表 PyPI 包私有。许可证尚待所有者确定,本次迁移不擅自授予开源许可。
54
+
55
+ 离线部署应预先为目标解释器和操作系统准备 PyMongo 依赖;已有匹配依赖时可用 `python -m pip install --no-index --no-deps ./mongrade-0.1.0a1-py3-none-any.whl`。包体跨平台不代表 PyMongo 的二进制依赖可跨平台复用。
@@ -0,0 +1,78 @@
1
+ # Mongrade 设计与迁移来源
2
+
3
+ 本设计保留源仓的技术决策与参考调研;涉及 CommonLib、SQL 及多后端 release 的段落描述外部适配边界,不是本包提供的命令。当前独立包用法以 guide.md 为准。
4
+
5
+ 2026-10-25;实现基线 `a89aae7b50b541c48835714a096e15c14bfdaa8d`。原候选统一入口 v2.1.1、包 v1.0.1;本次独立包为 1.0.2。这是 CommonLib 源端交付,不表示下游已经切换。项目 JSON 的 format=1 / engine=v1.0.0 是仍兼容的协议标识,区别于本次包发布版本。
6
+
7
+ ## 边界和文件职责
8
+
9
+ 独立安装包是必要的导入边界:`libs/__init__.py` 等服务父包可能初始化连接、Token、消息或调度器。包只依赖标准库,建立连接时才导入 PyMongo;Mongo 分派不会加载 Alembic/SQLAlchemy。既有 SQL 治理函数和命令保持,SQL 依赖改为调用时加载。`server.py` 不变。
10
+
11
+ `catalog` 静态解析项目 JSON、Python 字面量 `MIGRATION` 及制品摘要;`planner` 只读数据库;`state` 负责互斥、归属和权威单文档账本;`context` 提供显式资源与 checkpoint;`executor` 执行/恢复/承接;`legacy` 校验切换和备份证据;`cli` 仅适配公共 API;多后端 `deployment` 编排保留在 CommonLib,不进入本包。
12
+
13
+ 业务项目及 versions 属于服务发布制品,均不进入 manifest。原候选 manifest 曾同步整包;本次仅同步 CommonLib 薄兼容层,引擎从固定版本 wheel 安装。同步器实际会替换第三行,因此公共文件按 shebang、编码、保护注释、docstring 排列;离线测试提取实际同步函数,验证首次创建、注入后可编译和业务图原字节保留。未运行真实 updater。
14
+
15
+ ## 实际参考
16
+
17
+ 只吸收接口与职责,没有复制第三方源码,也没有新增运行依赖。2026-10-23 实际阅读如下分支的核心源文件和版本声明;网络获取 commit API 失败,因此记录可核实的版本,不编造提交 SHA。
18
+
19
+ | 项目及版本声明 | 实际阅读 | 采用与舍弃 |
20
+ | --- | --- | --- |
21
+ | [pymongo-migrate 1.0.0](https://github.com/stxnext/pymongo-migrate/blob/main/pyproject.toml),Apache-2.0 | `src/pymongo_migrate/migrations.py`、`mongo_migrate.py`、`loader.py` | 采用线性 revision、API/CLI 分离、目标版本及 graph/show;不采用 loader 在发现时导入脚本、fake stamp、自动降级或其 Python>=3.8 依赖 |
22
+ | [migrate-mongo 14.0.7](https://github.com/seppevs/migrate-mongo/blob/master/package.json),MIT | `lib/actions/up.js` | 采用顺序执行、失败停止和执行后记录;不采用文件 Hash 变化重跑、Node 依赖及失败立即清锁 |
23
+ | [mongodb-migrations 1.3.1](https://github.com/DoubleCiti/mongodb-migrations/blob/master/setup.py),GPLv3 | `mongodb_migrations/cli.py` | 采用简单脚本生成和元数据位置配置;不采用时间戳隐式版本、复杂继承、发现时 import 和通用 downgrade |
24
+
25
+ ## 图、状态和计划
26
+
27
+ 每域必须单 root、单 head、单父链;禁止重复/断链/环/未知历史和已执行源码摘要改变。`.py` 只做 AST 字面量提取;`.pyc` 必须附带同名 JSON 元数据、source_sha256(编译输入原始源码 SHA)和 artifact_sha256(实际 pyc SHA),并在执行时核对当前解释器 magic。权威历史绑定源码 SHA,计划同时绑定制品 SHA,避免 pyc 编译头变化误判历史;源码到制品的对应关系由受信发布构建负责,不从不可信 sidecar 猜测。一个版本目录不能同时保留对应 py/pyc。元数据不是对任意 Python 行为的安全证明。
28
+
29
+ 状态集合每域一个 `_id=domain` 文档,保存连续 `history` 和可空 `running`。`running` 记录 revision、制品 SHA、执行者、checkpoint、证据摘要及开始时间。版本成功由单次原子更新追加历史并清除 running;只有脚本和静态后检均成功才提交。不是 Mongo 多文档事务,也没有跨后端事务。
30
+
31
+ v1.0.1 的成功历史同时登记 binding,绑定实际目标、迁移域与完整资源声明。达到安装图 head 时必须验证该绑定及协调器中的完整归属,不能因 pending 为空而省略准入。新增资源须随新的正式 revision 执行并登记,不能仅修改 project.json 就将未迁移资源视为 latest。旧 v1.0.0 未发布候选历史若缺少 binding,按未知绑定阻断,不从现有数据猜测、自动补记或重新执行;此类候选库须经单独受审的承接方案处理。
32
+
33
+ diagnose/verify 在读取状态前及后检结束后重新解析磁盘项目和版本目录,缓存 Catalog 与当前制品不符即拒绝。混合部署的 SQL 最终复验同样复核官方图,后端 A 完成后发生的文件变化不能被最终全后端检查漏过。
34
+
35
+ | 观察状态 | 含义/允许操作 |
36
+ | --- | --- |
37
+ | pending | 尚有待执行版本;可只读计划 |
38
+ | latest | 当前达到安装图 head 且后检通过;若锁仍在,verify 仍拒绝 |
39
+ | unfinished | 正在执行或失败/崩溃/取消,禁止普通 plan/migrate |
40
+ | drift/异常 | 数据后检、历史、摘要或版本图不符;拒绝写入 |
41
+
42
+ 计划包含脱敏目标身份、业务库/authSource/控制位置、完整项目与脚本摘要、目标 revision、资源、当前历史/运行记录/协调器、集合内容 SHA/数量/索引、旧账本观察及证据。执行前全量重算,拿锁后复核状态、资源和旧账本。资源摘要以 `_id` 排序的 BSON 流计算,时间/类型不通过字符串猜测;成本是 O(owned 数据量),适用于维护窗口,首版不提供无边界在线大库扫描优化。
43
+
44
+ diagnose/verify/plan/graph/show 不建集合、索引、账本或锁,不登记初始化/基线,不执行 revision。诊断可返回 pending/unfinished;verify 仅对 latest 且无锁成功。诊断中的 checkpoint 是程序化敏感状态;CLI 将其及证据内容转换为摘要,不输出 URI、业务文档或驱动/脚本原始错误。错误码区分 authentication_failed、permission_denied、index_or_unique_conflict、状态/计划漂移及失锁。
45
+
46
+ ## 互斥与唯一归属
47
+
48
+ 首版同业务库、同协调集合全局串行,是保守限制;没有 TTL 或基于时间推定执行者死亡。所有相交服务必须由发布治理固定使用**同一个物理控制库和 coordination_collection**,不得另换协调集合规避归属。控制库首版必须等于业务库;多物理业务库资源明确拒绝。authSource 可以独立;集成测试只使用获授权的单库,分离认证库使用替身。
49
+
50
+ 协调器持久保留 domain→ledger 绑定和集合/索引责任方。集合级声明与该集合任何索引相交;索引级责任方只能创建/删除所声明索引,不能写集合数据。不同索引可以分属不同域,仍串行执行;不按服务独占整个 `db_common`。同域切换账本拒绝。控制配置和资源归属必须由发布审查统一确认,数据库无法发现有人故意使用另一协调命名空间;新锁也不能证明旧执行者停止。
51
+
52
+ 所有已登记域的账本、协调集合及已登记的旧来源账本都是保留元数据。校验双向生效:业务集合/索引不能覆盖任何域的这些账本,新账本也不能征用已登记业务集合。锁前只读校验并在 CAS 得锁后复查,避免仅保护本域账本的漏洞。
53
+
54
+ 锁身份包含随机 token、domain、主机、PID、开始时间。每个 context 操作/checkpoint/版本提交前后复核身份、发布制品、维护时效和旧账本观察;失锁后停止发出后续写操作。已经提交到 Mongo 的在途命令不能靠 Python 撤回,因此恢复必须先停止旧进程并排空在途写入。受信脚本不得缓存裸连接绕过 guard;上下文不是 OS 沙箱,静态分析不能识别任意动态行为。
55
+
56
+ ## 执行、恢复和旧库
57
+
58
+ 普通执行只调用 `upgrade(context)`。异常、KeyboardInterrupt、崩溃、失锁、后检失败、版本登记失败均保留现场和锁;不清账本、不直接重跑、不补 done。受审恢复脚本声明 `resume=True` 并实现 `resume(context)`,按业务数据与 checkpoint 共同判断恢复点,不能只相信 checkpoint。可能已落库但 checkpoint 未保存,或数据成功而版本登记失败,恢复必须处理这两种窗口。
59
+
60
+ recover 要求绑定当前完整观察摘要、旧锁身份和目标的确认材料,确认停止旧进程、排空写入并审查 checkpoint,配合新鲜维护证据 CAS 取得同域旧锁。只执行专用 resume,再后检提交。若提交成功仅清锁失败,显式 `release_verified_lock` 只释放已验证且无 running 的本域锁,不修改版本。首次拿锁后但 begin 前失败也用此流程;状态不符拒绝。
61
+
62
+ 旧 `upgrade_log` 保持原样:来源库/集合/过滤器、domain、tag→revision 映射和接受的无 status tag 由发布制品明确声明。done 可参与承接;无 status 必须逐 tag 显式批准;failed/running、未知/重复 tag、多 tag 映射同 revision、缺账本/空账本、非精确连续前缀、后检不符均拒绝。adopt 不执行脚本,所有前缀后检通过后单文档登记来源摘要及历史。新库仅允许 owned 集合不存在,或 index-only 声明的索引不存在;不根据 MODE、库名或某个集合为空猜测新库。
63
+
64
+ adopt 在最终数据后检结束后、登记前再次核对发布制品、维护时效、旧账本观察及资源归属;登记后再复核一次再释放锁。后检期间旧账本出现 running、证据过期或发布图变化时,不能登记成功并返回 latest。登记后的复查失败则保留锁并返回失败,服务保持停止,不宣称两个集合之间存在事务。
65
+
66
+ ## 风险、备份和切换
67
+
68
+ 首版风险只有 additive/destructive;其它类型拒绝。additive 上下文允许插入/建索引,删除/更新/替换/drop 需要 destructive。发布审查还必须识别通过动态 Python 绕过声明的风险;不宣称静态识别所有破坏操作。跨服务历史搬迁保持独立显式在线任务,不进入版本链,也不变成离线导入。
69
+
70
+ 任何写入要求维护 ID、reviewer、完整 executor 清单停止、禁用旧链、写入静止和最多15分钟的新鲜观察。每次 guard 重新检查;长于有效窗口的任务应分批 checkpoint 后停下,更新证据并恢复。旧账本内容摘要变化即视为旧活动;未写账本的外部写入依靠运维停写契约,不能用锁代替事实核实。
71
+
72
+ destructive 还要求发布审查、目标/完整资源范围相符的备份文件和恢复演练回执,实际核对两文件 SHA、存在性、24小时内时效及恢复回执对备份 SHA 的绑定。complete/verified 是受信 DBA/发布审核结论;引擎不假装能够解析并验证任意备份格式或证明任意恢复正确。本轮没有备份平台或自动恢复工具,缺受审恢复材料即拒绝业务/DDL写入。备份和恢复回执不得位于业务迁移目录或包含连接凭据。
73
+
74
+ ## 部署约束及保留范围
75
+
76
+ 统一 `release` 按服务发布契约枚举必需后端,全部前检通过才开始写入;每后端迁移后验证,再全部复验,全部成功才输出 start_allowed。任一失败/部分完成必须保持服务停止。发布清单不能遗漏服务实际使用的数据库,完整性由服务发布审查负责。MySQL 复用原部署预检凭据和执行器;混合部署应从 `migration.run_deploy_with_preflight()` 调用,不可用普通 JSON 冒充 SQL 基础设施事实。
77
+
78
+ 不承诺跨库原子事务,不自动降级,不启动业务服务,不安装依赖,不改下游。本轮未修改 `server.py`,未实现或验收启动版本门控;后续可通过 diagnose/verify 查询 current/head/state,另行增加只检查不执行的启动阻断。本轮“先迁移后启动”由显式部署流程负责。
@@ -0,0 +1,95 @@
1
+ # 使用指南
2
+
3
+ Mongrade 1.0.2,最低 Python3.7。项目协议仍为 format=1 / engine=v1.0.0,协议标识不是包版本。
4
+
5
+ ## 项目与脚本
6
+
7
+ 服务负责发布以下文件,引擎不会自动创建、覆盖或激活业务图:
8
+
9
+ ```text
10
+ service/
11
+ mongo_migrations/
12
+ project.json
13
+ versions/
14
+ r001.py
15
+ ```
16
+
17
+ ```json
18
+ {
19
+ "format": 1, "engine": "v1.0.0", "domain": "orders",
20
+ "database": "app_data", "auth_source": "authentication",
21
+ "control_database": "app_data", "ledger": "migration_state",
22
+ "coordination_collection": "_commonlib_migration_coordination",
23
+ "resources": {"orders": {"database": "app_data", "collection": "orders", "owner": "orders"}},
24
+ "executors": ["old_startup", "background", "overlapping_online_tasks"]
25
+ }
26
+ ```
27
+
28
+ 认证库与业务库分别声明,URI 中的业务库/authSource 必须一致。控制库首版必须等于业务库;所有相交域必须固定同一物理控制命名空间。默认协调集合名称保留旧协议,避免迁出后产生另一把互不相识的锁。资源可以声明 index 以只管理共享集合中的特定索引;集合/索引责任方必须明确,不能按服务独占 db_common。
29
+
30
+ ```python
31
+ # coding=utf-8
32
+ """业务自有版本,后检必须能证明数据完成。"""
33
+ MIGRATION = {
34
+ "revision": "r001", "parent": None,
35
+ "resources": ["orders"], "risk": "additive", "resume": True,
36
+ "checks": [{"resource": "orders", "filter": {"_id": "schema"}, "count": 1}],
37
+ }
38
+
39
+ def upgrade(context):
40
+ context.resource("orders").insert_one({"_id": "schema", "version": 1})
41
+ context.save_checkpoint("schema_created")
42
+
43
+ def resume(context):
44
+ orders = context.resource("orders")
45
+ if orders.find_one({"_id": "schema"}) is None:
46
+ orders.insert_one({"_id": "schema", "version": 1})
47
+ context.save_checkpoint("schema_created")
48
+ ```
49
+
50
+ MIGRATION 为唯一顶层字面量,恰好声明 revision/parent/resources/risk/checks/resume。每域只能单 root、单 head、单父链。目录发现与计划只静态解析,不执行脚本。拒绝重复、断链、环、未知历史以及已执行源码摘要变化。
51
+
52
+ check 支持 exists、filter+count/min_count、普通 index(name/key/unique)。context 提供 find_one/count_documents/index_information、insert_one/insert_many/create_index;destructive 才允许 update_one/update_many/replace_one/delete_one/delete_many/drop_index/drop。index-only 责任方不能写集合数据,只能创建/删除所声明索引。其它操作和索引选项首版拒绝,未知风险类型同样拒绝。脚本受信,但资源封装不是沙箱,静态分析无法证明任意 Python 行为安全。
53
+
54
+ pyc-only 脚本必须提供同名 JSON:原 MIGRATION 加 source_sha256(编译输入源码 SHA)及 artifact_sha256(实际 pyc SHA)。发布构建负责两者对应关系,运行时检查制品及解释器 magic。历史绑定源码摘要,计划绑定制品摘要;不因编译头变化重跑历史,同名 py/pyc 不允许同时发布。
55
+
56
+ ## 命令与角色
57
+
58
+ 在服务根目录运行;程序不会将 site-packages 误当业务目录。默认 deploy,可显式使用 `--role author` 生成后续 revision 模板;也读取 MIGRATION_EXECUTION_ROLE。不会自动生成首个 root 或登记基线。
59
+
60
+ ```console
61
+ mongrade graph
62
+ mongrade diagnose
63
+ mongrade verify
64
+ mongrade plan --target head --evidence files/maintenance.json
65
+ mongrade migrate --target head --evidence files/maintenance.json --confirm-plan-sha256 <已审SHA>
66
+ mongrade create --role author --revision-id r002
67
+ mongrade adopt --target r001 --evidence files/maintenance.json --confirm-plan-sha256 <已审SHA>
68
+ mongrade recover --evidence files/maintenance.json --confirmation files/recovery.json
69
+ ```
70
+
71
+ `show` 等价 diagnose,`tui` 提供只读 diagnose/verify/plan 菜单。`python -m mongrade` 与控制台入口一致。连接从 MONGRADE_URI 或 `--uri-env` 指定的变量读取;不在命令行、日志或配置示例中记录真实 URI。适配器可向 `mongrade.cli.main(argv, project_root=..., execution_role=...)` 注入已判定角色,命令行不能提升该角色。
72
+
73
+ diagnose 返回 current/head/state;只有 verify 确认 latest 且无锁才通过。CLI 成功为0,失败为非0;diagnose 成功不代表数据库已经 latest。CLI 隐藏 checkpoint、旧记录和证据正文;公共 API 返回的原始 observed 供受保护诊断使用,禁止原样写公共日志。
74
+
75
+ ## 计划、执行权和恢复
76
+
77
+ 计划绑定目标、资源、脚本/项目摘要、当前历史、资源内容/索引、旧账本观察及准入证据。执行前重算,得锁后复核;观察漂移使计划失效。内容摘要全量扫描 owned 资源,首版适用于维护窗口,尚无生产量级扫描性能承诺。
78
+
79
+ 执行锁包含 token/domain/host/PID/开始时间,没有 TTL 自动接管。每次 context 操作前后复核锁和证据;已发往数据库的在途命令不能撤回,所以失锁/失败恢复前必须实际停止旧进程并排空写入。失败、取消、崩溃、部分落库、checkpoint/版本登记失败均保留现场,不直接重跑或补 done;只有脚本及后检成功后才单文档提交历史。没有通用 downgrade、多文档或跨库原子事务。
80
+
81
+ 成功历史 binding 绑定目标、迁移域与完整资源。达到 head 必须核对绑定和归属,空计划也不跳过;新增资源随正式新 revision 迁移。协调器记录的所有账本、旧来源账本与控制集合均为保留位置,禁止业务资源覆盖,也禁止新账本征用已登记业务集合。旧候选历史缺 binding 时阻断,不猜测或自动补登记。
82
+
83
+ 维护证据字段:target=diagnose.target、catalog=catalog.sha256、resources=完整 project.resources、observed_at=当前带时区UTC ISO8601、stopped_executors=已实际停止且与 project.executors 一致的清单、legacy_disabled=true、writers_quiesced=true、reviewer、maintenance_id、legacy_snapshot=digest(legacy.legacy_snapshot(client,catalog))。无旧来源时绑定 digest(None)。证据最多15分钟有效,每次操作复核;长任务分批保存 checkpoint,在有效期内停止,更新证据后显式恢复。
84
+
85
+ 切换前必须列出并停止旧启动升级、后台及相交在线任务,禁用旧自动升级入口并静止相交写入。新锁不证明旧执行者已经退出;未确认、过期、旧账本活动或归属冲突都阻断。迁移脚本不得调用业务 HTTP、UAC、DataSearch、MQ;跨服务历史搬迁保持独立显式在线任务,不进入版本链。
86
+
87
+ destructive 还要求 destructive_reviewed=true、backup、restore_test。两个材料都需 target、完整 resources、complete=true、verified=true、created_at(24小时内)、本地 path、实际 sha256;restore_test.backup_sha256 必须绑定 backup.sha256。引擎读取并校验文件,不替 DBA 证明任意备份格式或恢复正确性;缺材料、过期、错目标、不完整或摘要变化在业务/DDL写入前拒绝。
88
+
89
+ project.legacy 显式声明 database/collection/filter/mapping,默认 tag_field=upgrade_tag、status_field=status。mapping 为旧 tag 到正式 revision 的一对一映射;done 可参与承接,无 status 必须逐 tag 列入 accepted_no_status_tags。未知/重复 tag、failed/running、账本缺失、非精确连续前缀及数据不完整不自动完成。adopt 保留旧记录,后检前后以及登记前后重新核对来源/证据,再登记连续历史。
90
+
91
+ 恢复 confirmation 需 previous_owner=当前 registry.owner、executor_stopped=true、inflight_writes_drained=true、checkpoint_reviewed=digest(完整 observed)、data_observation=digest(planner.resource_snapshot(client,catalog))、catalog、target。脚本声明 resume=True 并提供 resume(context),必须结合真实数据处理“数据已写、checkpoint未存”及“数据成功、版本未登记”窗口,不能仅看进度机械重跑。若 owner 被清空而归属登记仍完整,还需 stopped_owner=running.owner;协调器归属也丢失则拒绝自动恢复。无 running 且仅清锁失败时,显式 action=release_verified_lock 可核对后释放本域锁,不补版本。
92
+
93
+ ## CommonLib 集成边界
94
+
95
+ CommonLib 保留其 migration.py、author/deploy 角色及 MySQL/Mongo 统一 release 编排;本包不导入 CommonLib,也不提供混合数据库 release 命令。CommonLib 适配器继续传递旧 URI 变量名和服务目录,服务所有数据库迁移并验证成功后才启动。任一失败保持服务停止。服务须自行退出 upgrade_lib/service_upgrade_lib 启动钩子,不让两套链管理相同资源。server.py 的启动版本检查仍是 CommonLib 后续独立工作,本包不执行服务启动。
@@ -0,0 +1,39 @@
1
+ # 构建和发布
2
+
3
+ 版本唯一来源为 `src/mongrade/_version.py`,pyproject 静态读取该值,不导入业务模块。最低 Python3.7,开发工具固定兼容版本;构建依赖根据解释器的 Requires-Python 约束解析。
4
+
5
+ ```console
6
+ python -m pip install ".[dev]"
7
+ python -B -m pytest
8
+ python -m ruff format --check src test
9
+ python -m ruff check src test
10
+ python -m mypy
11
+ python -m build
12
+ ```
13
+
14
+ wheel 应为 `mongrade-<版本>-py3-none-any.whl`,包含 mongrade 包、py.typed 和标准 dist-info,不包含测试、服务代码、私密配置或迁移图。sdist 包含源码和公开测试/文档;其内容同样公开,不能把私有仓库可见性当作包的保密性。首次发布前由所有者确定许可证,本次迁移不擅自选择开源协议。
15
+
16
+ ## GitHub Actions
17
+
18
+ `.github/workflows/release.yml` 在 push/PR 上执行测试和构建,在 Release published 时(含预发布)检出相应 tag 并核对 `v<包版本>`。构建检查后,由同一个 wheel 执行 Python3.7 两种驱动、Python3.8至3.14、Windows/Linux/macOS 的安装与回归矩阵。仅构建和矩阵全部通过后允许发布,预发布也按相同顺序上传 Release 资产和 PyPI,由 PEP440 的 a/b/rc 版本标明阶段。
19
+
20
+ **强制发布顺序:** 发布 Release → 检出对应 tag → 检查版本号、测试、构建及兼容性验证 → 全部通过后先将 wheel、sdist 上传为当次 GitHub Release 资产 → 上传同一批制品到 PyPI。
21
+
22
+ release_assets 下载本次构建的 python-dist,使用 GitHub CLI 上传到事件指定的 Release;该任务独占 contents: write 权限。publish 显式依赖 release_assets 成功,继续下载同一个 python-dist,不重新构建。资产上传失败或同名资产已存在时停止,不自动覆盖或删除现有资产;应先核对该次 Release 的状态再恢复发布。CI 不注入真实数据库凭据,真实 Mongo 测试必须显式在已授权本地目标执行。
23
+
24
+ 当前采用 PyPI API Token:
25
+
26
+ 1. 用户已在 GitHub Secrets 设置 PYPI_API_TOKEN;publish 使用 environment: pypi,建议将该 Secret 放入 pypi 环境。Token 值不进入仓库或日志。
27
+ 2. publish 上传步骤通过 password: ${{ secrets.PYPI_API_TOKEN }} 引用 Secret,不再申请 id-token: write。GitHub Release 资产仍使用独立的 GitHub 自动令牌,不使用 PyPI Token。
28
+ 3. 项目尚未创建时,首次上传需要账号范围 Token;首次发布后更换为仅限 mongrade 项目的 Token 并撤销临时账号级 Token。
29
+ 4. GitHub Secret 的值和 PyPI 权限无法通过本地检查确认,需在正式发布任务中验证;本次 commit/push 不等于已验证上传。
30
+
31
+ 如以后切换免 Token 的 Trusted Publishing,在 PyPI 配置 owner=deepaint-ai、repository=mongrade、workflow=release.yml、environment=pypi,移除上传步骤 password,再为 publish 设置 id-token: write。
32
+
33
+ 本文描述已准备的配置;本地通过不等于 GitHub 矩阵或 PyPI 上传已经执行。参考 [PyPA 打包](https://packaging.python.org/en/latest/tutorials/packaging-projects/) 和 [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/using-a-publisher/)。
34
+
35
+ GitHub Release 资产命令依据:[gh release upload 官方文档](https://cli.github.com/manual/gh_release_upload)。
36
+
37
+ ## Release 说明规范(用户强制要求)
38
+
39
+ 每次用户要求发布,都使用 gh 创建/更新当次 GitHub Release 的正文;说明必须有适量 emoji,中文为主,优雅、简洁、排版清晰,说明版本定位、主要变化及安装方式。正文直接写在 Release 中,不以仓库 Markdown 文档替代。为保留真实换行,可以先写 tmp 下的临时正文文件,再用 --notes-file 上传,不提交该临时文件。预览版使用 --prerelease;首次版本为 v0.1.0a1。
@@ -0,0 +1,79 @@
1
+ [build-system]
2
+ requires = ["setuptools>=67.8", "wheel>=0.42"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "mongrade"
7
+ dynamic = ["version"]
8
+ description = "轻量、可验证、可恢复的 MongoDB 版本迁移工具"
9
+ readme = "README.md"
10
+ requires-python = ">=3.7"
11
+ dependencies = ["pymongo>=3.7.1,<5"]
12
+ keywords = ["mongodb", "migration", "pymongo", "database"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Developers",
16
+ "Operating System :: OS Independent",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "Programming Language :: Python :: 3.7",
20
+ "Programming Language :: Python :: 3.8",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Topic :: Database",
28
+ ]
29
+
30
+ [project.urls]
31
+ Repository = "https://github.com/deepaint-ai/mongrade"
32
+
33
+ [project.scripts]
34
+ mongrade = "mongrade.cli:main"
35
+
36
+ [project.optional-dependencies]
37
+ dev = ["build==0.10.0", "mypy==1.4.1", "pytest==7.4.4", "ruff==0.8.6"]
38
+
39
+ [tool.setuptools.dynamic]
40
+ version = {attr = "mongrade._version.__version__"}
41
+
42
+ [tool.setuptools.packages.find]
43
+ where = ["src"]
44
+
45
+ [tool.setuptools.package-data]
46
+ mongrade = ["py.typed"]
47
+
48
+ [tool.pytest.ini_options]
49
+ testpaths = ["test"]
50
+ addopts = "-ra -p no:cacheprovider"
51
+
52
+ [tool.ruff]
53
+ target-version = "py37"
54
+ line-length = 88
55
+ cache-dir = "tmp/ruff-cache"
56
+
57
+ [tool.ruff.lint]
58
+ select = ["E", "W", "F", "I", "D"]
59
+ # 中文文档使用中文句号,保留语言约定而非追加英文标点。
60
+ ignore = ["D400", "D415"]
61
+
62
+ [tool.ruff.lint.pydocstyle]
63
+ convention = "google"
64
+
65
+ [tool.ruff.lint.per-file-ignores]
66
+ "test/*.py" = ["E402", "D"]
67
+
68
+ [tool.mypy]
69
+ python_version = "3.7"
70
+ files = ["src/mongrade"]
71
+ check_untyped_defs = true
72
+ disallow_untyped_defs = true
73
+ no_implicit_optional = true
74
+ warn_unused_ignores = true
75
+ cache_dir = "tmp/mypy-cache"
76
+
77
+ [[tool.mypy.overrides]]
78
+ module = ["pymongo.*", "bson.*"]
79
+ ignore_missing_imports = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,26 @@
1
+ # coding=utf-8
2
+ """Mongrade 的稳定公共 API;导入时不建立连接或加载业务模块。"""
3
+
4
+ from ._version import __version__
5
+ from .catalog import load_catalog
6
+ from .executor import adopt, migrate, preflight, recover
7
+ from .models import Catalog, MigrationError, Plan, Revision
8
+ from .planner import diagnose, plan, verify
9
+ from .state import connect
10
+
11
+ __all__ = [
12
+ "Catalog",
13
+ "MigrationError",
14
+ "Plan",
15
+ "Revision",
16
+ "__version__",
17
+ "adopt",
18
+ "connect",
19
+ "diagnose",
20
+ "load_catalog",
21
+ "migrate",
22
+ "plan",
23
+ "preflight",
24
+ "recover",
25
+ "verify",
26
+ ]
@@ -0,0 +1,7 @@
1
+ # coding=utf-8
2
+ """支持 python -m mongrade,行为与安装后的控制台入口一致。"""
3
+
4
+ from .cli import main
5
+
6
+ if __name__ == "__main__":
7
+ raise SystemExit(main())
@@ -0,0 +1,4 @@
1
+ # coding=utf-8
2
+ """包版本的唯一声明位置,供运行时和标准构建后端静态读取。"""
3
+
4
+ __version__ = "0.1.0a1"