perceptkit 0.2.2__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 (106) hide show
  1. perceptkit-0.2.2/.github/workflows/release.yml +136 -0
  2. perceptkit-0.2.2/.gitignore +6 -0
  3. perceptkit-0.2.2/CHANGELOG.md +117 -0
  4. perceptkit-0.2.2/LICENSE +202 -0
  5. perceptkit-0.2.2/NOTES-packaging.md +197 -0
  6. perceptkit-0.2.2/NOTES-quickstart.md +145 -0
  7. perceptkit-0.2.2/PKG-INFO +439 -0
  8. perceptkit-0.2.2/README.md +226 -0
  9. perceptkit-0.2.2/docs/PerceptKit-/344/272/247/345/223/201/347/233/256/346/240/207/344/270/216/345/275/223/345/211/215/345/256/236/347/216/260/345/267/256/350/267/235/345/217/215/351/246/210.md +1476 -0
  10. perceptkit-0.2.2/docs/PerceptKit-/346/204/237/347/237/245/345/255/227/346/256/265/344/270/216/345/255/230/345/202/250-/345/267/245/347/250/213/345/257/271/351/275/220/350/241/245/345/205/205/350/256/250/350/256/272.md +841 -0
  11. perceptkit-0.2.2/docs/reference-storage-mapping.md +128 -0
  12. perceptkit-0.2.2/examples/end_to_end.py +141 -0
  13. perceptkit-0.2.2/examples/ios_adapter.py +171 -0
  14. perceptkit-0.2.2/examples/quickstart.py +130 -0
  15. perceptkit-0.2.2/pyproject.toml +27 -0
  16. perceptkit-0.2.2/src/perceptkit/__init__.py +85 -0
  17. perceptkit-0.2.2/src/perceptkit/algorithms/__init__.py +40 -0
  18. perceptkit-0.2.2/src/perceptkit/algorithms/attribution.py +147 -0
  19. perceptkit-0.2.2/src/perceptkit/algorithms/glance.py +236 -0
  20. perceptkit-0.2.2/src/perceptkit/algorithms/history.py +663 -0
  21. perceptkit-0.2.2/src/perceptkit/algorithms/identity.py +43 -0
  22. perceptkit-0.2.2/src/perceptkit/algorithms/observation.py +44 -0
  23. perceptkit-0.2.2/src/perceptkit/algorithms/streaks.py +111 -0
  24. perceptkit-0.2.2/src/perceptkit/algorithms/trend_models.py +184 -0
  25. perceptkit-0.2.2/src/perceptkit/algorithms/wake.py +149 -0
  26. perceptkit-0.2.2/src/perceptkit/catalog.py +252 -0
  27. perceptkit-0.2.2/src/perceptkit/conformance/__init__.py +28 -0
  28. perceptkit-0.2.2/src/perceptkit/conformance/memory.py +364 -0
  29. perceptkit-0.2.2/src/perceptkit/conformance/report.py +170 -0
  30. perceptkit-0.2.2/src/perceptkit/conformance/suite.py +419 -0
  31. perceptkit-0.2.2/src/perceptkit/conformance/wake.py +151 -0
  32. perceptkit-0.2.2/src/perceptkit/contracts/__init__.py +97 -0
  33. perceptkit-0.2.2/src/perceptkit/contracts/_time.py +89 -0
  34. perceptkit-0.2.2/src/perceptkit/contracts/availability.py +77 -0
  35. perceptkit-0.2.2/src/perceptkit/contracts/context.py +50 -0
  36. perceptkit-0.2.2/src/perceptkit/contracts/delivery.py +167 -0
  37. perceptkit-0.2.2/src/perceptkit/contracts/errors.py +22 -0
  38. perceptkit-0.2.2/src/perceptkit/contracts/event.py +137 -0
  39. perceptkit-0.2.2/src/perceptkit/contracts/observation.py +172 -0
  40. perceptkit-0.2.2/src/perceptkit/contracts/receipt.py +129 -0
  41. perceptkit-0.2.2/src/perceptkit/contracts/records.py +367 -0
  42. perceptkit-0.2.2/src/perceptkit/contracts/report.py +127 -0
  43. perceptkit-0.2.2/src/perceptkit/contracts/versioning.py +63 -0
  44. perceptkit-0.2.2/src/perceptkit/fields.py +184 -0
  45. perceptkit-0.2.2/src/perceptkit/kit.py +223 -0
  46. perceptkit-0.2.2/src/perceptkit/manifest/__init__.py +57 -0
  47. perceptkit-0.2.2/src/perceptkit/manifest/checks.py +323 -0
  48. perceptkit-0.2.2/src/perceptkit/manifest/mapping.py +96 -0
  49. perceptkit-0.2.2/src/perceptkit/manifest/minimal.py +1282 -0
  50. perceptkit-0.2.2/src/perceptkit/manifest/types.py +211 -0
  51. perceptkit-0.2.2/src/perceptkit/manifest/units.py +84 -0
  52. perceptkit-0.2.2/src/perceptkit/ports/__init__.py +19 -0
  53. perceptkit-0.2.2/src/perceptkit/ports/storage.py +288 -0
  54. perceptkit-0.2.2/src/perceptkit/ports/wake.py +43 -0
  55. perceptkit-0.2.2/src/perceptkit/processing/__init__.py +49 -0
  56. perceptkit-0.2.2/src/perceptkit/processing/aggregate.py +80 -0
  57. perceptkit-0.2.2/src/perceptkit/processing/dispatch.py +356 -0
  58. perceptkit-0.2.2/src/perceptkit/processing/normalize.py +458 -0
  59. perceptkit-0.2.2/src/perceptkit/processing/pipeline.py +406 -0
  60. perceptkit-0.2.2/src/perceptkit/processing/recompute.py +170 -0
  61. perceptkit-0.2.2/src/perceptkit/processing/recurrence.py +166 -0
  62. perceptkit-0.2.2/src/perceptkit/processing/scheduled.py +233 -0
  63. perceptkit-0.2.2/src/perceptkit/prompts.py +75 -0
  64. perceptkit-0.2.2/src/perceptkit/queries/__init__.py +32 -0
  65. perceptkit-0.2.2/src/perceptkit/queries/api.py +457 -0
  66. perceptkit-0.2.2/src/perceptkit/retention.py +84 -0
  67. perceptkit-0.2.2/src/perceptkit/rules/__init__.py +19 -0
  68. perceptkit-0.2.2/src/perceptkit/rules/engine.py +112 -0
  69. perceptkit-0.2.2/src/perceptkit/rules/evaluators.py +228 -0
  70. perceptkit-0.2.2/src/perceptkit/rules/types.py +236 -0
  71. perceptkit-0.2.2/tests/fixtures/README.md +21 -0
  72. perceptkit-0.2.2/tests/fixtures/ios_snapshot_no_data.json +29 -0
  73. perceptkit-0.2.2/tests/fixtures/ios_snapshot_normal.json +78 -0
  74. perceptkit-0.2.2/tests/fixtures/ios_snapshot_unauthorized.json +32 -0
  75. perceptkit-0.2.2/tests/test_attribution.py +143 -0
  76. perceptkit-0.2.2/tests/test_catalog.py +21 -0
  77. perceptkit-0.2.2/tests/test_conformance.py +211 -0
  78. perceptkit-0.2.2/tests/test_conformance_wake_report.py +201 -0
  79. perceptkit-0.2.2/tests/test_contracts.py +311 -0
  80. perceptkit-0.2.2/tests/test_delivery_and_records.py +310 -0
  81. perceptkit-0.2.2/tests/test_edge_cases.py +201 -0
  82. perceptkit-0.2.2/tests/test_end_to_end.py +348 -0
  83. perceptkit-0.2.2/tests/test_event_envelope.py +179 -0
  84. perceptkit-0.2.2/tests/test_examples.py +56 -0
  85. perceptkit-0.2.2/tests/test_export.py +114 -0
  86. perceptkit-0.2.2/tests/test_identity.py +70 -0
  87. perceptkit-0.2.2/tests/test_ios_fixture.py +199 -0
  88. perceptkit-0.2.2/tests/test_isolation.py +211 -0
  89. perceptkit-0.2.2/tests/test_manifest.py +461 -0
  90. perceptkit-0.2.2/tests/test_no_host_leakage.py +108 -0
  91. perceptkit-0.2.2/tests/test_observation.py +60 -0
  92. perceptkit-0.2.2/tests/test_pipeline.py +280 -0
  93. perceptkit-0.2.2/tests/test_projection.py +26 -0
  94. perceptkit-0.2.2/tests/test_purity.py +122 -0
  95. perceptkit-0.2.2/tests/test_queries.py +527 -0
  96. perceptkit-0.2.2/tests/test_recompute.py +226 -0
  97. perceptkit-0.2.2/tests/test_recurrence.py +213 -0
  98. perceptkit-0.2.2/tests/test_regressions.py +558 -0
  99. perceptkit-0.2.2/tests/test_retention.py +97 -0
  100. perceptkit-0.2.2/tests/test_rules.py +258 -0
  101. perceptkit-0.2.2/tests/test_scheduled.py +207 -0
  102. perceptkit-0.2.2/tests/test_source_mirror.py +190 -0
  103. perceptkit-0.2.2/tests/test_streaks.py +164 -0
  104. perceptkit-0.2.2/tests/test_trend_models.py +127 -0
  105. perceptkit-0.2.2/tests/test_wake.py +109 -0
  106. perceptkit-0.2.2/uv.lock +155 -0
@@ -0,0 +1,136 @@
1
+ name: release
2
+
3
+ # 打 tag 时由 CI 构建并发布 wheel,同时生成构建出处凭证(provenance attestation)。
4
+ #
5
+ # 为什么不在本机编好再传:哈希锁只能证明「装进宿主镜像的字节 = 发布出去那份
6
+ # 字节」,证明不了「这份字节是由公开 tag 的源码编出来的」。本机编 + 手工上传
7
+ # 时,有仓库权限的人可以传一个跟源码对不上的 wheel,看源码的人看不出问题。
8
+ #
9
+ # 由 tag 触发的 CI 编译 + GitHub 签发的 attestation 把这一环补上:凭证里绑定了
10
+ # 仓库、workflow、commit 和产物摘要,任何人都能独立验证:
11
+ #
12
+ # gh attestation verify <wheel> --repo teleport-computer/perceptkit
13
+ #
14
+ # v0.2.0 / v0.2.1 是本机编好手工传的(当时还没有这个 workflow),所以那两个
15
+ # 版本没有凭证 —— 从 v0.2.2 起有。
16
+
17
+ on:
18
+ push:
19
+ tags: ["v*"]
20
+ workflow_dispatch:
21
+ inputs:
22
+ tag:
23
+ description: "要重新发布的 tag(不填则用当前 ref)"
24
+ required: false
25
+
26
+ permissions:
27
+ contents: write # 建 Release、传产物
28
+ id-token: write # 换取 OIDC token 用于签发凭证
29
+ attestations: write # 写 attestation
30
+
31
+ jobs:
32
+ build:
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+ with:
37
+ ref: ${{ github.event.inputs.tag || github.ref }}
38
+
39
+ - uses: actions/setup-python@v5
40
+ with:
41
+ python-version: "3.12"
42
+
43
+ - name: Install uv
44
+ run: pipx install uv
45
+
46
+ - name: Tag 与 pyproject 版本必须一致
47
+ # 不一致就停:否则会出现「tag 是 v0.2.2、wheel 却是 0.2.1」这种凭证与
48
+ # 产物名对不上的情况,验证方无从判断该信哪个。
49
+ run: |
50
+ TAG="${GITHUB_REF_NAME#v}"
51
+ PKG=$(grep -m1 '^version = ' pyproject.toml | cut -d'"' -f2)
52
+ echo "tag=$TAG perceptkit=$PKG"
53
+ test "$TAG" = "$PKG" || { echo "::error::tag $TAG 与版本 $PKG 不一致"; exit 1; }
54
+
55
+ - name: 跑测试
56
+ # 发一个测试不过的包出去比不发更糟 —— 装它的人会以为它是可用的。
57
+ run: uv run --extra dev pytest tests/ -q
58
+
59
+ - name: 示例必须能脱离宿主跑通
60
+ # 这是包对外的核心承诺(不采集数据、不碰存储、不调模型,
61
+ # 因此也就不需要 io、不需要 API key、不需要网络)。
62
+ # 发版时验一次,比等使用者发现便宜得多。
63
+ run: |
64
+ uv run python examples/quickstart.py
65
+ uv run python examples/end_to_end.py
66
+
67
+ - name: 构建
68
+ run: |
69
+ uv build
70
+ mkdir -p out
71
+ cp dist/* out/
72
+
73
+ - name: 传产物给发布 job
74
+ uses: actions/upload-artifact@v4
75
+ with:
76
+ name: dists
77
+ path: out/
78
+
79
+ - name: 生成构建出处凭证
80
+ uses: actions/attest-build-provenance@v1
81
+ with:
82
+ subject-path: "out/*"
83
+
84
+ - name: 记录产物摘要
85
+ # 打进 job 日志,方便对照 Release 页和宿主 lock 里的哈希。
86
+ run: |
87
+ echo "### 产物摘要" >> "$GITHUB_STEP_SUMMARY"
88
+ echo '```' >> "$GITHUB_STEP_SUMMARY"
89
+ sha256sum out/* >> "$GITHUB_STEP_SUMMARY"
90
+ echo '```' >> "$GITHUB_STEP_SUMMARY"
91
+ sha256sum out/*
92
+
93
+ - name: 发布到 Release
94
+ # 继续发 GitHub Release,不只发 PyPI:宿主的哈希锁现在钉的是 Release
95
+ # 上的 wheel URL,换成 PyPI 是另一件事(要重算哈希、改 lock),
96
+ # 两件事分开做。两边发的是**同一批字节**。
97
+ env:
98
+ GH_TOKEN: ${{ github.token }}
99
+ run: |
100
+ TAG="${GITHUB_REF_NAME}"
101
+ if gh release view "$TAG" >/dev/null 2>&1; then
102
+ gh release upload "$TAG" out/* --clobber
103
+ else
104
+ gh release create "$TAG" out/* --title "perceptkit $TAG" --generate-notes
105
+ fi
106
+
107
+ # --------------------------------------------------------------------------
108
+ # PyPI 发布单独一个 job
109
+ #
110
+ # 仓库里不存 token,走 OIDC(Trusted Publishing)。`id-token: write` 是每个
111
+ # job 各自声明的 —— 顶层的 permissions 不会自动传进来。
112
+ #
113
+ # environment 名字要和 PyPI 上那条 Trusted Publisher 配置填的一致。
114
+ # 姊妹仓库 memgarden 因为一个仓库里发两个包、必须靠 environment 区分才不被
115
+ # PyPI 判为歧义;这里只有一个包,本可以不填,仍然填上是为了两个仓库的配置
116
+ # 长得一样,照着抄的人不会踩到那个坑。
117
+ publish-pypi:
118
+ needs: build
119
+ runs-on: ubuntu-latest
120
+ environment: pypi-perceptkit
121
+ permissions:
122
+ id-token: write
123
+ steps:
124
+ - uses: actions/download-artifact@v4
125
+ with:
126
+ name: dists
127
+ path: out
128
+ - name: 发布到 PyPI
129
+ # 🔴 不要加 continue-on-error。加了之后 job 会判成 success,
130
+ # 「发布失败」和「发布成功」在 Actions 页面上长得一模一样 ——
131
+ # 姊妹仓库踩过一次,结果 PyPI 上躺着一个依赖装不上的版本。
132
+ # 让它红是安全的:GitHub Release 在上一个 job 里已经发完了。
133
+ uses: pypa/gh-action-pypi-publish@release/v1
134
+ with:
135
+ packages-dir: out/
136
+ skip-existing: true
@@ -0,0 +1,6 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ .pytest_cache/
5
+ *.db
6
+ dist/
@@ -0,0 +1,117 @@
1
+ # 变更记录
2
+
3
+ ## 0.2.2 — 未发布
4
+
5
+ **代码没有变化。** 这一版只换了「怎么发出去」。
6
+
7
+ ### 变更
8
+
9
+ - **发版改由 CI 在 tag 上构建,并带 GitHub 签发的出处凭证。**
10
+ 0.2.0 / 0.2.1 是在本机编好、手工传上去的。宿主的哈希锁只能证明
11
+ 「装进镜像的字节 = 发布页上那份字节」,**证不了这份字节是那个 tag 的源码
12
+ 编出来的** —— 有仓库权限的人可以传一个跟源码对不上的 wheel,
13
+ 而看源码的人看不出问题。现在任何人都能独立验:
14
+
15
+ gh attestation verify perceptkit-0.2.2-py3-none-any.whl \
16
+ --repo teleport-computer/perceptkit
17
+
18
+ - **开始发 PyPI**(`pip install perceptkit`),走 Trusted Publishing,
19
+ 仓库里不存 token。GitHub Release 照旧发**同一批字节** —— 宿主目前钉的是
20
+ Release 的 wheel URL,换成 PyPI 是另一件事。
21
+
22
+
23
+ ## 0.2.1 — 2026-08-31
24
+
25
+ ### 新增
26
+
27
+ - **conformance 多了第 ⑪ 条:两个来源镜像都要能往返。**
28
+ 这一条是从一个真实现上倒推出来的 —— 宿主的 Postgres adapter 写提醒时读了
29
+ `r.source_created_at`、读回来时又把它当构造参数传回去,而
30
+ `ReminderItemMirror` 上根本没有这个字段(`CalendarEventMirror` 有,
31
+ 它是照那个写的)。**写会抛、读也会抛,整条提醒镜像从来没通过过一次**,
32
+ 而这套套件全绿。
33
+
34
+ 原因是 ⑧ 一直在用日历,所以日历那半始终被覆盖着,提醒那半一次都没被碰过。
35
+ **一个只测一半的套件,给出的是「都测过了」的印象。**
36
+
37
+
38
+ ## 0.2.0 — 2026-08-31
39
+
40
+ **这一版有破坏性变更。** 宿主升级时要改 import,见下。
41
+
42
+ ### 破坏性
43
+
44
+ - **纯计算的八个模块搬进 `perceptkit.algorithms`**
45
+ (`attribution` / `glance` / `history` / `identity` / `observation` /
46
+ `streaks` / `trend_models` / `wake`)。
47
+
48
+ ```python
49
+ # 之前
50
+ from perceptkit import trend_models
51
+ from perceptkit.history import ...
52
+
53
+ # 之后
54
+ from perceptkit.algorithms import trend_models
55
+ from perceptkit.algorithms.history import ...
56
+ ```
57
+
58
+ 产品规范 §18 要求这一层单独存在(「不能再把 contract、算法、存储、
59
+ 宿主 runtime 接线混成一层」)。先前它们和 `kit.py`(装配和接线)平级。
60
+
61
+ `catalog` / `fields` / `retention` / `prompts` **留在顶层没动** ——
62
+ 它们是声明表和待定项,不是算法。
63
+
64
+ - **`list_events` 改为返回 `(事件, 下一页游标)`**,不再是一个列表。
65
+ 产品规范 §15 要求所有 list 查询分页或有明确上限。
66
+
67
+ ### 新增
68
+
69
+ - `list_definitions()` —— 当前配了哪些规则(规范 §15 列了它,先前完全没有)。
70
+ - `list_events(status=...)` —— 按投递状态筛。「为什么没提醒我」的答案
71
+ 往往是 suppressed 或 rejected,不是 pending。
72
+ - `export_subject()` —— 按人导出。规范 §8 要的是「定位、导出、删除」,
73
+ 导出那一半先前是空的。
74
+ - `recompute_aggregates()` —— 聚合算法升级后重算历史。**默认拒绝重算
75
+ 明细可能已被保留期清理的日子**:拿残缺明细折出来的永久统计会错一个
76
+ 数量级,而且旧值已被覆盖。
77
+ - 重复日程按查询窗口滚动展开(确定的子集;不认识的规则明确拒绝,不猜日期)。
78
+ - `run_wake_conformance()` / `run_report_conformance()` —— 规范 §20 并列的
79
+ 三种 adapter conformance,先前只有 storage 那一套。
80
+ - manifest 的第五条自动检查:投影不漂移。它抓的一类是泄漏 ——
81
+ 声明了「永不给 agent」的字段如果 wake_eligible,前后值会随事件信封
82
+ 存下来、投出去、进模型上下文。
83
+ - manifest 拆出 `aggregate_retention_days`:明细和聚合是两个保留期。
84
+ - 新增信号:`proximity_anchor` / `music_playback` / `app_usage`。
85
+ - 事件信封的 `context` 带上触发字段的单位。
86
+
87
+ ### 修复
88
+
89
+ - **同一个信号里两种聚合算法会互相覆盖,当天第二条上报直接崩。**
90
+ 聚合分派把**整条 payload** 递给了每个字段的 merger,于是一个字段声明的
91
+ 算法写到了所有字段头上。`health_vitals` 同时有 `numeric_dist`(静息心率)
92
+ 和 `main_of_day`(vo2_max):后者把字段写成裸数字,前者下一条进来读
93
+ `cell["min"]` 抛 `AttributeError` —— **每个用户每天第二次上报都会踩**。
94
+ 同一个原因还让声明 `none` 的字段凭空长出聚合(`weather` 只声明了
95
+ `temperature_c`,紫外线、湿度、体感温度全被写了 min/max/sum/count)。
96
+ 现在每个 merger 只拿到它自己那个字段。
97
+
98
+ 没被单测抓到,是因为所有单测用的信号都只声明了一种算法;影子第一次接
99
+ 真数据当天就炸了。
100
+
101
+ - **`unavailable` 现在会写进当前值。** 先前撤销权限后查询仍报 `fresh` ——
102
+ 把一个已经读不到的值当成当前事实。现在状态记下来、最后可靠值留作
103
+ `last_known`。
104
+ - **设备时钟明显错误的判据**:超过 24 小时的未来时间拒收(过去不限,
105
+ 离线补传是正常的)。先前没有任何判据,手机时间设成明年会把今天的数据
106
+ 写进明年,不报错。
107
+ - **事件 `context` 的白名单和长度上限真的实现了** —— 先前只写在文档里。
108
+ - **日历/提醒的读取走端口**,不再摸具体实现的私有属性(那会让任何真实
109
+ 存储静默返回空)。
110
+ - **一致性套件自己不再摸内存实现的内部** —— 先前它对每个真 adapter
111
+ 都报一个假失败。
112
+ - 接受规范 §7.1 用的字段名 `state` / `sample_id` 作为别名。照那份文档
113
+ 实现的 producer 先前每一条观测都被拒。
114
+
115
+ ## 0.1.0
116
+
117
+ 首个公开版本:判断内核(纯函数)。
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
@@ -0,0 +1,197 @@
1
+ # perceptkit 打包笔记
2
+
3
+ 镜像对象:`/tmp/mg-peek`(memgarden,同一个团队更早做的一次同类抽取)。
4
+ 布局、`pyproject.toml` 写法、`__init__.py` 语气、守卫测试风格都照它来。
5
+
6
+ ## 1. 依赖验证
7
+
8
+ `src/perceptkit/*.py` 的全部 import(AST 扫描,排除包自身):
9
+
10
+ ```
11
+ $ python3 - <<'EOF'
12
+ import ast, pathlib, sys
13
+ SRC = pathlib.Path("src/perceptkit")
14
+ stdlib = set(sys.stdlib_module_names)
15
+ ...
16
+ EOF
17
+ all imports: ['__future__', 'collections', 'dataclasses', 'datetime', 'hashlib', 'json', 'math', 'sys', 'typing', 'zoneinfo']
18
+ non-stdlib (excluding self): set()
19
+ ```
20
+
21
+ 零第三方依赖,全部标准库。`pyproject.toml` 的 `dependencies = []`。
22
+
23
+ ## 2. pyproject.toml
24
+
25
+ 照 memgarden 的写法:hatchling、`src/` 布局、`license = { file = "LICENSE" }`、
26
+ `requires-python = ">=3.10"`、`[tool.pytest.ini_options] testpaths = ["tests"]`。
27
+ 唯一实质差异:memgarden 有一个允许的同源第三方依赖
28
+ (`agent-protocol-core`),这个包没有等价的同源伙伴包,`dependencies = []`
29
+ 是真正的空。
30
+
31
+ ## 3. `src/perceptkit/__init__.py`
32
+
33
+ 重写成 memgarden 的语气:先说包判断什么,再列一段"不在这里的"(数据采集、
34
+ 存储、加解密、账号身份与鉴权、定时器/调度、真正调模型、决定 agent 最终该
35
+ 说什么),最后 re-export 十二个模块里宿主大概率会直接用到的名字(
36
+ `should_wake`、`build_perception_glance`、`classify`、`measurement_key` 等,
37
+ 完整列表见 `__all__`)。
38
+
39
+ ## 4. 迁移测试:改动前后
40
+
41
+ **改动前**(host 仓库 `/Users/hx/Projects/io/worktrees/feedling-mcp/feat-perception-health-chain/tests/`,
42
+ 10 个 `test_perception_kernel_*.py` 文件):
43
+
44
+ ```
45
+ grep -c '^ def test_\|^def test_' 逐文件相加 = 95 (parametrize 只按函数定义算一次)
46
+ ```
47
+
48
+ 按 pytest 实际会收集的用例数手工展开 parametrize(未在此仓库跑 pytest —— host
49
+ 仓库的 conftest 会尝试连 Postgres,用手工核对参数化列表长度代替):
50
+
51
+ ```
52
+ test_identity.py test_missing_any_part_is_refused 1 def -> 4 cases(kwargs 列表长度 4) +3
53
+ test_wake.py test_deny_listed_signals_are_never_wake_worthy 1 def -> 5 cases(NOT_WAKE_WORTHY_SIGNALS 长度 5) +4
54
+ test_wake.py test_durable_wake_signals_default_allow 1 def -> 7 cases(手写信号列表长度 7) +6
55
+ ```
56
+
57
+ **改动前实际总数:95 + 3 + 4 + 6 = 108**
58
+
59
+ **改动后**(`perceptkit/tests/`,`uv run python3 -m pytest --collect-only -q`):
60
+
61
+ ```
62
+ 106 tests collected in 0.02s
63
+ ```
64
+
65
+ 差值 108 → 106 的构成:
66
+
67
+ - **删除 6 条宿主集成测试**(它们断言宿主的 re-export 壳和内核对象是同一批
68
+ 对象,或直接读/扫宿主仓库的源码——这个独立包里既没有宿主壳、也没有宿主
69
+ 仓库目录,留着要么 ImportError 要么变成"扫空目录、永远绿"的假测试):
70
+ - `test_catalog.py::test_io_shell_reexports_the_same_objects`
71
+ - `test_projection.py::test_io_shells_reexport_kernel_objects`
72
+ - `test_wake.py::test_kernel_vocabulary_does_not_collide_with_the_two_io_wake_kind_sets`
73
+ (硬编码了宿主另外两套 wake_kind 常量,纯粹比对宿主内部事实)
74
+ - `test_wake.py::test_wake_unwired_names_stay_unreferenced_outside_the_kernel`
75
+ (扫宿主仓库 `backend/`、`tools/` 目录;这里没有这两个目录,会静默
76
+ "扫空、永远绿",是比失败更危险的假测试)
77
+ - `test_wake.py::test_every_real_durable_wake_signal_is_wake_worthy`
78
+ (直接 `read_text()` 宿主的 `perception/differ_v2.py` 源码核对信号名单,
79
+ 这个文件不存在于本仓库)
80
+ - `test_purity.py`(宿主版本本身就是扫宿主 `backend/`+`tools/` 目录的,
81
+ 不适合直接照搬——用它的思路重写了一份,见下)
82
+ - **新增 4 条**:`test_purity.py`(3 条)+ `test_no_host_leakage.py`(1 条)
83
+
84
+ `108 - 6 + 4 = 106`,与实际收集数一致。
85
+
86
+ 文件改名对照:`test_perception_kernel_X.py` -> `test_X.py`;`catalog`/`projection`
87
+ 两个文件里删掉了宿主集成用例后保留文件名不变(`projection` 原本就是
88
+ `fields.py` + `glance.py` 两个模块的联合测试,没有对应单模块名可用)。
89
+ 所有文件里的手写 `sys.path` bootstrap 全部删除,`import perception_kernel.X`
90
+ /`from perception_kernel import X` 全部改成 `import perceptkit.X` /
91
+ `from perceptkit import X`。
92
+
93
+ ## 5. `tests/test_purity.py`
94
+
95
+ 照抄 memgarden 版本的结构(AST 扫描 `src/perceptkit` 下每个文件的顶层
96
+ import),**唯一差异是允许名单是空集**(memgarden 允许同源的
97
+ `agent-protocol-core`,这个包没有等价的伙伴包)。三条测试:源码树非空
98
+ (护住扫描路径没写错)、无第三方 import、判断内核不碰网络/DB/进程/线程。
99
+
100
+ ## 6. `tests/test_no_host_leakage.py`
101
+
102
+ 选词标准:**只挑结构性的、不含糊的标识符**,不禁常见词。具体选了这些:
103
+
104
+ | 词 | 为什么 |
105
+ |---|---|
106
+ | `perception_kernel` | 宿主迁移前的内部包名——留着就是没洗干净的直接证据 |
107
+ | `io_cli` | 宿主内部 CLI 工具名 |
108
+ | `chat_resident_consumer` | 宿主 VPS 自托管常驻进程的模块名 |
109
+ | `differ_v2` / `signal_state_v2` / `effect_outbox` / `tool_executor_v2` | 宿主运行时内部模块名 |
110
+ | `model_api_runtime` | 宿主的运行时 lane 名——这个包要兼容多条宿主 runtime,写死一条的名字就是假设了宿主架构 |
111
+ | `OpenClaw` | 宿主生态里的第三方插件名 |
112
+ | `backend/*.py` / `tools/chat_resident_consumer.py` | 宿主仓库的目录布局,这个包不该假设自己在哪个宿主仓库下 |
113
+ | `perception_daily` | 宿主数据库的实际表名——这个包不碰存储,不该知道表叫什么 |
114
+ | `usr_[0-9a-f]{8,}` | 真实用户 id 的形状(照抄 memgarden) |
115
+ | `ADMIN_KEY` | 凭据名(照抄 memgarden) |
116
+
117
+ **刻意没禁**裸的 `io`(宿主产品名):只有两个字母,是标准库模块名
118
+ (`import io`)的合法用法,也是大量英文缩写/变量名的子串,禁了假阳性远大于
119
+ 真阳性;用更具体的标识符(内部模块名、插件名、表名、目录)来兜底同一个
120
+ "产品名泄漏"担忧,风险小得多。
121
+
122
+ 扫描范围只覆盖 `src/`、`README.md`、`pyproject.toml`,**刻意不扫
123
+ `tests/`**——这条测试自己就活在 `tests/` 里,它的 `BANNED` 正则字面量本身
124
+ 含有这些词,扫自己会自证失败(踩过一次:第一版把 `tests/` 也扫进去,
125
+ 测试文件里解释"删掉了什么"的说明文字被自己的正则命中)。
126
+
127
+ ## 7. 一个意外发现:`src/perceptkit/*.py`("已经就位"的文件)本身就在泄漏
128
+
129
+ 写完 leakage 测试、第一次跑之前先手动 grep 了一遍 `src/`(不是等测试跑起来
130
+ 才发现),结果是:这批已经放在 `src/perceptkit/` 里的源码**本身携带大量
131
+ 宿主内部细节**,集中在 `wake.py`、`prompts.py`、`fields.py`、`catalog.py`、
132
+ `history.py` 五个文件的注释/docstring 里:
133
+
134
+ - 宿主内部文件路径 + 行号(如 `proactive/gate.py:89`)
135
+ - 宿主内部函数名/常量名(如 `_proactive_v2_wake_kind`、`_COLLISION_WAKE_KINDS`、
136
+ 各种 `SWITCH_*_WAKE_ENABLED` 开关名)
137
+ - 宿主运行时 lane 名(`model_api_runtime/v2/...`)
138
+ - 宿主内部模块名(`differ_v2.py`、`chat_resident_consumer.py`、
139
+ `signal_state_v2.py`、`backend/capabilities/tool_schema.py`)
140
+ - 宿主内部工具名(`io_cli`)与生态插件名(`OpenClaw`)
141
+ - 宿主产品名的直接提及(`IO'S OWN foreground/background/inactive phase`)
142
+ - 宿主数据库表名(`perception_daily`)与技术选型(Postgres)
143
+ - 一处真人名字("hx 拍板",指代团队里的决策者)
144
+ - 一处指向宿主仓库内部测试 fixture 的路径断言
145
+ (`tests/fixtures/perception_kernel/prompt_baseline.json`,该路径在这个
146
+ 独立包里根本不存在,留着是死断言)
147
+
148
+ 这些注释本身的**设计理由是有价值的**(比如为什么 wake 源要单独起名、为什么
149
+ 默认允许+否决名单而非白名单),所以没有整段删掉,而是改写成不点名宿主内部
150
+ 结构的版本,保留"为什么这么设计",去掉"宿主哪个文件哪一行"。逐文件改动:
151
+ `wake.py`(改动最大)、`catalog.py`、`fields.py`、`prompts.py`、`history.py`。
152
+
153
+ 这不是"任务外顺手改了别的东西"——这批文件被判定为"标准做法就是照抄测试
154
+ 规范,然后让测试盯着",而写完的 `test_no_host_leakage.py` 本该、也确实
155
+ 应该抓到这个问题;不修就没法让 `pytest` 通过,也违背了整个任务"证明这个
156
+ 包能独立发布"的前提。
157
+
158
+ ## 8. 运行方式
159
+
160
+ 用 `uv`(`/tmp/mg-peek` 的 `[tool.uv.sources]` 暗示这个团队的约定,且本机
161
+ 已装 `uv 0.11.7`):
162
+
163
+ ```
164
+ $ uv sync --extra dev
165
+ Resolved 10 packages in 484ms
166
+ Building perceptkit @ file:///Users/hx/Projects/perceptkit
167
+ Installed 6 packages: iniconfig, packaging, pluggy, pygments, pytest, perceptkit
168
+
169
+ $ uv run python3 -m pytest -q
170
+ ........................................................................ [ 67%]
171
+ .................................. [100%]
172
+ 106 passed in 0.06s
173
+ ```
174
+
175
+ 也验证了不带 `uv run`、只激活 `.venv` 后跑纯 `python3 -m pytest` 同样通过
176
+ (`uv sync` 把包装成 editable install,`perceptkit` 在 sys.path 上):
177
+
178
+ ```
179
+ $ source .venv/bin/activate
180
+ $ python3 -m pytest -q
181
+ 106 passed in 0.06s
182
+ ```
183
+
184
+ (不激活任何环境、直接用系统 `python3 -m pytest` 会因为 `perceptkit` 没装
185
+ 而 `ModuleNotFoundError`——这是预期的:测试文件里的手写 `sys.path` bootstrap
186
+ 已按任务要求删除,proper packaging 意味着"先装包,再测",不是"从任意
187
+ 目录躲开安装步骤"。)
188
+
189
+ ## 9. 其他留意点
190
+
191
+ - `examples/` 目录在任务开始前就已存在但是空的,未处理(任务没要求写
192
+ quickstart 示例;memgarden 有一个 `examples/quickstart.py`,这个包目前
193
+ 没有)。
194
+ - README.md 是本次新写的(任务清单没明确列出,但 `pyproject.toml` 的
195
+ `readme = "README.md"` 字段引用它,`uv sync` 会因为它存在而不报错;
196
+ 没写的话仍能跑 pytest,但 `hatchling` 打包会失败)。内容参照 memgarden
197
+ README 的结构(做什么/不做什么 -> 最小代码 -> 目录),但篇幅大幅压缩。