matelab-python-sdk 0.1.0a21__tar.gz → 0.1.0a23__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 (68) hide show
  1. matelab_python_sdk-0.1.0a23/AGENTS.md +52 -0
  2. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/CHANGELOG.md +16 -0
  3. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/PKG-INFO +16 -10
  4. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/README.md +15 -9
  5. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/contracts/matelab-integration-v1.lock.json +3 -3
  6. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/contracts/matelab-integration-v1.openapi.yaml +16 -11
  7. matelab_python_sdk-0.1.0a23/docs/agent-architecture.md +28 -0
  8. matelab_python_sdk-0.1.0a23/docs/agent-workflows.md +76 -0
  9. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/docs/operation-coverage.yaml +1 -1
  10. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/pyproject.toml +1 -1
  11. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/_generated/models.py +3 -6
  12. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/records.py +54 -3
  13. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_generation.py +10 -5
  14. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_records.py +72 -1
  15. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/uv.lock +1 -1
  16. matelab_python_sdk-0.1.0a21/AGENTS.md +0 -94
  17. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/.env.example +0 -0
  18. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/.github/workflows/release.yml +0 -0
  19. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/.gitignore +0 -0
  20. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/CONTEXT.md +0 -0
  21. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/LICENSE +0 -0
  22. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/NOTICE +0 -0
  23. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/scripts/check_installed_package.py +0 -0
  24. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/scripts/check_release.py +0 -0
  25. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/scripts/generate_models.py +0 -0
  26. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/__init__.py +0 -0
  27. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/_generated/__init__.py +0 -0
  28. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/_hashes.py +0 -0
  29. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/_transport.py +0 -0
  30. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/_wire_validation.py +0 -0
  31. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/client.py +0 -0
  32. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/__init__.py +0 -0
  33. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/_attachment_content.py +0 -0
  34. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/cloud_drive.py +0 -0
  35. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/groups.py +0 -0
  36. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/literature.py +0 -0
  37. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/notebooks.py +0 -0
  38. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/templates.py +0 -0
  39. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/uploads.py +0 -0
  40. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/users.py +0 -0
  41. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/errors.py +0 -0
  42. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/py.typed +0 -0
  43. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/session.py +0 -0
  44. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/streaming.py +0 -0
  45. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/conftest.py +0 -0
  46. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/provider/test_provider_smoke.py +0 -0
  47. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_async_attachment_upload.py +0 -0
  48. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_client.py +0 -0
  49. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_cloud_drive.py +0 -0
  50. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_comments_and_downloads.py +0 -0
  51. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_consumer_smoke.py +0 -0
  52. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_errors.py +0 -0
  53. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_extended_records.py +0 -0
  54. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_groups.py +0 -0
  55. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_notebook_lifecycle.py +0 -0
  56. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_notebooks.py +0 -0
  57. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_operation_coverage.py +0 -0
  58. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_record_lifecycle.py +0 -0
  59. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_record_mutations.py +0 -0
  60. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_release.py +0 -0
  61. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_streaming.py +0 -0
  62. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_template_lifecycle.py +0 -0
  63. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_templates.py +0 -0
  64. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_transport.py +0 -0
  65. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_uploads_and_literature.py +0 -0
  66. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_users.py +0 -0
  67. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/typing/consumer.py +0 -0
  68. {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/typing/pyproject.toml +0 -0
@@ -0,0 +1,52 @@
1
+ # AGENTS.md
2
+
3
+ 本文件只承载每次维护都必须在上下文中的步骤、权威和授权边界。分支规则通过下列指针按需加载。
4
+
5
+ ## 维护顺序
6
+
7
+ 1. **定界**:先读 `git status` 和适用权威;完成标准是任务范围、用户现有改动和所需外部权限均已识别。
8
+ 2. **查证**:沿 public intent → wire operation → Provider evidence 核对;完成标准是每个受影响 operation 的
9
+ request、response、security、examples、成功码及 PVD/PCG 均有结论,Contract 缺口已明确报告。
10
+ 3. **收敛**:只修改任务必需的层;完成标准是 public、adapter、generated、tests、coverage 和 docs 的影响均已
11
+ 记账,未加入 Provider 推测、Consumer 私有概念或无真实第二实现的 seam。
12
+ 4. **验证**:先跑聚焦测试,再跑适用的完整门槛;完成标准是必需命令全部退出为零,skip 单独说明。
13
+ 5. **交付**:复核最终 diff、状态和验证证据;完成标准是结果、限制和未执行项都可由用户独立核对。
14
+
15
+ ## 分支指针
16
+
17
+ - **SDK architecture**:修改 public 类型、命名、身份空间或模块边界时读
18
+ [`docs/agent-architecture.md#public-interface`](docs/agent-architecture.md#public-interface);修改 transport、认证、
19
+ Session、stream 或错误时读
20
+ [`docs/agent-architecture.md#transport-session-and-errors`](docs/agent-architecture.md#transport-session-and-errors)。
21
+ - **Contract adoption**:同步 spec tag、snapshot、lock 或生成模型时执行
22
+ [`docs/agent-workflows.md#contract-adoption`](docs/agent-workflows.md#contract-adoption)。
23
+ - **Delivery gate**:代码、Contract、依赖或构建元数据变化后执行
24
+ [`docs/agent-workflows.md#delivery-gate`](docs/agent-workflows.md#delivery-gate)。
25
+ - **Provider validation**:任何实时 Provider smoke、读取或 mutation 前执行
26
+ [`docs/agent-workflows.md#provider-validation`](docs/agent-workflows.md#provider-validation)。
27
+ - **Release**:版本、制品、commit、tag、push 或 publish 任务执行
28
+ [`docs/agent-workflows.md#release`](docs/agent-workflows.md#release)。
29
+
30
+ ## 权威
31
+
32
+ | 权威 | 负责内容 |
33
+ | --- | --- |
34
+ | `README.md` | 当前 public interface、集成语义、示例及人类流程 |
35
+ | `CONTEXT.md` | public 类型和命名采用的领域词汇 |
36
+ | `CHANGELOG.md` | SDK 版本变化与迁移影响;不定义当前接口 |
37
+ | `docs/operation-coverage.yaml` | operation 状态、公开入口、稳定性和已知问题 |
38
+ | `pyproject.toml` | SDK 版本、依赖范围、构建与工具配置;`uv.lock` 只锁定本仓库环境 |
39
+ | Contract lock 与 snapshot | Contract pin 与 wire schema |
40
+ | `src/matelab/__init__.py::__all__` | 公开导入边界 |
41
+
42
+ 机器权威变化时,同一变更同步受影响的当前态文档;历史只追加到 `CHANGELOG.md`。测试提供行为证据,不替代
43
+ 接口、coverage 或 Contract 权威。
44
+
45
+ ## 范围与授权
46
+
47
+ - 现有工作区改动属于用户;在其上做最小、可归因的修改。默认只写本仓库,邻接的 spec、Provider 和 Consumer
48
+ 仓库只作只读证据来源。
49
+ - `/home/tnzhu/projects/matelab-spec` 定义 Wire Contract、兼容策略及 Provider 问题;`matelab-server` 是 Provider
50
+ 实现,`matelab-mcp` 只提供 Consumer evidence,均不能反向定义 SDK interface。
51
+ - commit、push、tag、publish、生产访问和真实 Provider mutation 需要本轮明确授权;授权只覆盖用户指定的目标与
52
+ 副作用。
@@ -3,6 +3,22 @@
3
3
  This file records release-to-release changes and migration impact. It is not the authority for the current interface;
4
4
  see [README.md](README.md) for current behavior and [AGENTS.md](AGENTS.md) for maintenance policy.
5
5
 
6
+ ## 0.1.0a23 - 2026-09-04
7
+
8
+ - Temporarily accepts `readRecord` table file attachments that contain valid `filename`, `hash`, and `size` fields but
9
+ omit the Contract-required `timestamp`. The SDK preserves the omission instead of synthesizing a timestamp; explicit
10
+ null timestamps and other malformed attachment values remain rejected.
11
+
12
+ ## 0.1.0a22 - 2026-09-03
13
+
14
+ - Adopts immutable `matelab-spec v0.4.5` at commit `6afac7ff151680130e6e4bc6d95782589b97f4e7` and OpenAPI
15
+ SHA-256 `31a8d03b4366e2345dfccdbbaaaccf6754b2711428afa4295d778d121988460d`, aligned with Provider source evidence
16
+ `59eb05b233e5ddd023a60ccb5e5c5f2ae22a70f0` and its verified production-core equivalent.
17
+ - Tightens the private `readRecord` wire model so required `datetime_locked` is a non-negative integer; the Provider
18
+ normalizes an unavailable lock timestamp to `0`. The public `Record` interface remains unchanged.
19
+ - Retains the root-only `RecordFilesAttachmentAppend` boundary because `UpdateRecordRequest.add[]` still excludes the
20
+ folder `path` declared by the dedicated staged-name append schema; PVD-044 remains contract-contained.
21
+
6
22
  ## 0.1.0a21 - 2026-08-28
7
23
 
8
24
  - Adopts immutable `matelab-spec v0.4.4` at commit `be070c5f931b2063327a3beaccc53a360204b64e` and OpenAPI
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matelab-python-sdk
3
- Version: 0.1.0a21
3
+ Version: 0.1.0a23
4
4
  Summary: Reusable async Python client for the Matelab Integration Contract
5
5
  Author-email: 朱天念 <zhutiannian@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -35,10 +35,10 @@ This README describes the current SDK interface, integration semantics, examples
35
35
  [AGENTS.md](AGENTS.md) for durable maintenance rules, and `docs/operation-coverage.yaml` for the machine-readable
36
36
  operation inventory.
37
37
 
38
- The current alpha is `0.1.0a21`. `[project].version` in `pyproject.toml` is the sole SDK version source;
38
+ The current alpha is `0.1.0a23`. `[project].version` in `pyproject.toml` is the sole SDK version source;
39
39
  `uv.lock` only mirrors that source.
40
40
 
41
- The SDK is pinned to the immutable `matelab-spec v0.4.4` Contract Release. The sole release pin is
41
+ The SDK is pinned to the immutable `matelab-spec v0.4.5` Contract Release. The sole release pin is
42
42
  `contracts/matelab-integration-v1.lock.json`, which records
43
43
  the source tag, commit, OpenAPI path, local snapshot path, and SHA-256.
44
44
 
@@ -64,14 +64,14 @@ To test the same artifact a downstream Consumer will install, build and install
64
64
 
65
65
  ```bash
66
66
  uv build --no-build-isolation --out-dir dist/release
67
- python -m pip install dist/release/matelab_python_sdk-0.1.0a21-py3-none-any.whl
67
+ python -m pip install dist/release/matelab_python_sdk-0.1.0a23-py3-none-any.whl
68
68
  ```
69
69
 
70
70
  Do not infer Provider compatibility from the SDK version alone. A release is also bound to the Contract
71
71
  tag, commit, and checksum recorded below.
72
72
 
73
73
  The Contract and public SDK interface track the current reviewed Provider at
74
- `7bd9704286057653632424fa82f413d99cd69ab6`. Provider limitations that remain relevant to integrations are documented
74
+ `59eb05b233e5ddd023a60ccb5e5c5f2ae22a70f0`. Provider limitations that remain relevant to integrations are documented
75
75
  alongside the affected interfaces below.
76
76
 
77
77
  ## Design
@@ -121,7 +121,7 @@ success code it does not allow raises `MatelabProtocolError` rather than assigni
121
121
  `MatelabTransportError.status_code` remains available for HTTP failures. Provider response bodies, top-level `msg`,
122
122
  legacy `errmsg`, opaque `errs`, uncontracted debug fields, and request payloads are not attached to exceptions because
123
123
  Provider-authored diagnostics may contain stack traces, credentials, or submitted values.
124
- The pinned v0.4.4 Contract also declares optional code-4 `errs`, but PVD-041 and PCG-015 record opaque array/object
124
+ The pinned v0.4.5 Contract also declares optional code-4 `errs`, but PVD-041 and PCG-015 record opaque array/object
125
125
  containers and insufficient evidence for requiredness, cardinality, or exclusion from other codes. The SDK
126
126
  therefore keeps those diagnostics wire-only and does not expose `errs` as stable public metadata. A typed detail
127
127
  interface remains blocked until a future Contract release can define one from stronger Provider evidence. `retryable`
@@ -317,7 +317,7 @@ result cannot be retried through the same client.
317
317
 
318
318
  Files-module append and table/files replacement use the general record attachment intents and may be combined with
319
319
  other non-conflicting record changes. `RecordFilesAttachmentAppend` currently targets the module root. Although
320
- v0.4.4 records Provider evidence for safe folder paths, its top-level `UpdateRecordRequest.add[]` schema still excludes
320
+ v0.4.5 records Provider evidence for safe folder paths, its top-level `UpdateRecordRequest.add[]` schema still excludes
321
321
  the `path` property declared by the dedicated staged-name append shape; the SDK therefore does not expose folder paths
322
322
  until that wire-schema conflict is resolved (PVD-044):
323
323
 
@@ -354,6 +354,12 @@ not accept a caption. Record reads likewise accept only one canonical attachment
354
354
  an attachment array violates the pinned response contract. Both replacement forms require the replacement hash to
355
355
  differ from the current occurrence.
356
356
 
357
+ As a temporary Provider-compatibility exception, `records.read()` also accepts a table file attachment object whose
358
+ otherwise valid `filename`, `hash`, and `size` are present but whose Contract-required `timestamp` is omitted. The SDK
359
+ preserves that omission in `Record.modules` and does not invent a timestamp; null timestamps and all other malformed
360
+ attachment shapes remain response errors. This exception can be removed after the Provider canonicalizes current and
361
+ historical record content.
362
+
357
363
  All record attachment intents reject handles from another notebook or authenticated uploader, duplicate use within one
358
364
  finalization request, and raw Provider attachment references. Row/index-based operations use an immediate SDK read and
359
365
  must not be called while a concurrent editor is known to be active. The Provider offers no expected hash or revision,
@@ -920,6 +926,6 @@ uv run python scripts/check_release.py dist/release/*.whl dist/release/*.tar.gz
920
926
  ```
921
927
 
922
928
  Rebuilding the same commit with the same locked environment and `SOURCE_DATE_EPOCH` must produce
923
- byte-identical wheel and source distribution hashes. The release is bound to immutable `matelab-spec v0.4.4`, commit
924
- `be070c5f931b2063327a3beaccc53a360204b64e`, and OpenAPI SHA-256
925
- `2de38e46d3814b8b049af8afd18e85fb20be6ca29cc409390d25df663abc43f9`.
929
+ byte-identical wheel and source distribution hashes. The release is bound to immutable `matelab-spec v0.4.5`, commit
930
+ `6afac7ff151680130e6e4bc6d95782589b97f4e7`, and OpenAPI SHA-256
931
+ `31a8d03b4366e2345dfccdbbaaaccf6754b2711428afa4295d778d121988460d`.
@@ -7,10 +7,10 @@ This README describes the current SDK interface, integration semantics, examples
7
7
  [AGENTS.md](AGENTS.md) for durable maintenance rules, and `docs/operation-coverage.yaml` for the machine-readable
8
8
  operation inventory.
9
9
 
10
- The current alpha is `0.1.0a21`. `[project].version` in `pyproject.toml` is the sole SDK version source;
10
+ The current alpha is `0.1.0a23`. `[project].version` in `pyproject.toml` is the sole SDK version source;
11
11
  `uv.lock` only mirrors that source.
12
12
 
13
- The SDK is pinned to the immutable `matelab-spec v0.4.4` Contract Release. The sole release pin is
13
+ The SDK is pinned to the immutable `matelab-spec v0.4.5` Contract Release. The sole release pin is
14
14
  `contracts/matelab-integration-v1.lock.json`, which records
15
15
  the source tag, commit, OpenAPI path, local snapshot path, and SHA-256.
16
16
 
@@ -36,14 +36,14 @@ To test the same artifact a downstream Consumer will install, build and install
36
36
 
37
37
  ```bash
38
38
  uv build --no-build-isolation --out-dir dist/release
39
- python -m pip install dist/release/matelab_python_sdk-0.1.0a21-py3-none-any.whl
39
+ python -m pip install dist/release/matelab_python_sdk-0.1.0a23-py3-none-any.whl
40
40
  ```
41
41
 
42
42
  Do not infer Provider compatibility from the SDK version alone. A release is also bound to the Contract
43
43
  tag, commit, and checksum recorded below.
44
44
 
45
45
  The Contract and public SDK interface track the current reviewed Provider at
46
- `7bd9704286057653632424fa82f413d99cd69ab6`. Provider limitations that remain relevant to integrations are documented
46
+ `59eb05b233e5ddd023a60ccb5e5c5f2ae22a70f0`. Provider limitations that remain relevant to integrations are documented
47
47
  alongside the affected interfaces below.
48
48
 
49
49
  ## Design
@@ -93,7 +93,7 @@ success code it does not allow raises `MatelabProtocolError` rather than assigni
93
93
  `MatelabTransportError.status_code` remains available for HTTP failures. Provider response bodies, top-level `msg`,
94
94
  legacy `errmsg`, opaque `errs`, uncontracted debug fields, and request payloads are not attached to exceptions because
95
95
  Provider-authored diagnostics may contain stack traces, credentials, or submitted values.
96
- The pinned v0.4.4 Contract also declares optional code-4 `errs`, but PVD-041 and PCG-015 record opaque array/object
96
+ The pinned v0.4.5 Contract also declares optional code-4 `errs`, but PVD-041 and PCG-015 record opaque array/object
97
97
  containers and insufficient evidence for requiredness, cardinality, or exclusion from other codes. The SDK
98
98
  therefore keeps those diagnostics wire-only and does not expose `errs` as stable public metadata. A typed detail
99
99
  interface remains blocked until a future Contract release can define one from stronger Provider evidence. `retryable`
@@ -289,7 +289,7 @@ result cannot be retried through the same client.
289
289
 
290
290
  Files-module append and table/files replacement use the general record attachment intents and may be combined with
291
291
  other non-conflicting record changes. `RecordFilesAttachmentAppend` currently targets the module root. Although
292
- v0.4.4 records Provider evidence for safe folder paths, its top-level `UpdateRecordRequest.add[]` schema still excludes
292
+ v0.4.5 records Provider evidence for safe folder paths, its top-level `UpdateRecordRequest.add[]` schema still excludes
293
293
  the `path` property declared by the dedicated staged-name append shape; the SDK therefore does not expose folder paths
294
294
  until that wire-schema conflict is resolved (PVD-044):
295
295
 
@@ -326,6 +326,12 @@ not accept a caption. Record reads likewise accept only one canonical attachment
326
326
  an attachment array violates the pinned response contract. Both replacement forms require the replacement hash to
327
327
  differ from the current occurrence.
328
328
 
329
+ As a temporary Provider-compatibility exception, `records.read()` also accepts a table file attachment object whose
330
+ otherwise valid `filename`, `hash`, and `size` are present but whose Contract-required `timestamp` is omitted. The SDK
331
+ preserves that omission in `Record.modules` and does not invent a timestamp; null timestamps and all other malformed
332
+ attachment shapes remain response errors. This exception can be removed after the Provider canonicalizes current and
333
+ historical record content.
334
+
329
335
  All record attachment intents reject handles from another notebook or authenticated uploader, duplicate use within one
330
336
  finalization request, and raw Provider attachment references. Row/index-based operations use an immediate SDK read and
331
337
  must not be called while a concurrent editor is known to be active. The Provider offers no expected hash or revision,
@@ -892,6 +898,6 @@ uv run python scripts/check_release.py dist/release/*.whl dist/release/*.tar.gz
892
898
  ```
893
899
 
894
900
  Rebuilding the same commit with the same locked environment and `SOURCE_DATE_EPOCH` must produce
895
- byte-identical wheel and source distribution hashes. The release is bound to immutable `matelab-spec v0.4.4`, commit
896
- `be070c5f931b2063327a3beaccc53a360204b64e`, and OpenAPI SHA-256
897
- `2de38e46d3814b8b049af8afd18e85fb20be6ca29cc409390d25df663abc43f9`.
901
+ byte-identical wheel and source distribution hashes. The release is bound to immutable `matelab-spec v0.4.5`, commit
902
+ `6afac7ff151680130e6e4bc6d95782589b97f4e7`, and OpenAPI SHA-256
903
+ `31a8d03b4366e2345dfccdbbaaaccf6754b2711428afa4295d778d121988460d`.
@@ -2,12 +2,12 @@
2
2
  "schema_version": 1,
3
3
  "contract_line": "integration-v1",
4
4
  "release": {
5
- "tag": "v0.4.4",
6
- "commit": "be070c5f931b2063327a3beaccc53a360204b64e"
5
+ "tag": "v0.4.5",
6
+ "commit": "6afac7ff151680130e6e4bc6d95782589b97f4e7"
7
7
  },
8
8
  "openapi": {
9
9
  "source_path": "openapi/matelab-integration-v1.openapi.yaml",
10
10
  "snapshot": "matelab-integration-v1.openapi.yaml",
11
- "sha256": "2de38e46d3814b8b049af8afd18e85fb20be6ca29cc409390d25df663abc43f9"
11
+ "sha256": "31a8d03b4366e2345dfccdbbaaaccf6754b2711428afa4295d778d121988460d"
12
12
  }
13
13
  }
@@ -1,7 +1,7 @@
1
1
  openapi: 3.1.0
2
2
  info:
3
3
  title: Matelab Integration Contract
4
- version: 0.4.4
4
+ version: 0.4.5
5
5
  description: |
6
6
  Current PHP wire contract for generating SDKs against the selected
7
7
  `matelab-server` Provider version.
@@ -9,7 +9,12 @@ info:
9
9
  x-contract-line: integration-v1
10
10
  x-provider-evidence:
11
11
  repository: /home/tnzhu/projects/matelab-server
12
- commit: 7bd9704286057653632424fa82f413d99cd69ab6
12
+ commit: 59eb05b233e5ddd023a60ccb5e5c5f2ae22a70f0
13
+ productionRuntime:
14
+ source: /home/elnhr/matelab
15
+ workerStartedAt: "2026-09-02T08:11:00+08:00"
16
+ coreEquivalentCommit: 86c57f60aa66bf794d6227c77d1d483b3ea8c271
17
+ comparison: crlf-insensitive-core-tree
13
18
  files:
14
19
  - controller/Actions.php
15
20
  - controller/Tokens.php
@@ -34,13 +39,16 @@ info:
34
39
  - lib/Route.php
35
40
  - lib/HttpServer.php
36
41
  - lib/MultiException.php
42
+ - config/Db.php
37
43
  - config/Channel.php
38
44
  - sql/eln.sql
39
45
  - sql/matelab.sql
40
46
  - sql/recommend.sql
41
47
  x-provider-audit:
42
- currentCommit: 7bd9704286057653632424fa82f413d99cd69ab6
43
- previousReleaseEvidenceCommit: a3e6b961800f6b1ab666f0d3f6cd4c64c2a3ce27
48
+ currentCommit: 59eb05b233e5ddd023a60ccb5e5c5f2ae22a70f0
49
+ productionRuntimeCoreEquivalentCommit: 86c57f60aa66bf794d6227c77d1d483b3ea8c271
50
+ previousReleaseEvidenceCommit: 7bd9704286057653632424fa82f413d99cd69ab6
51
+ lastProviderVerifiedCommit: 59eb05b233e5ddd023a60ccb5e5c5f2ae22a70f0
44
52
  contractStatus: current-provider-aligned
45
53
  incorporatedChanges:
46
54
  - {operationId: stageTemporaryFile, change: staging-identifiers-expanded-to-100}
@@ -52,7 +60,7 @@ info:
52
60
  - {operationId: updateRecordData, change: safe-files-folder-path-restored-to-callable-shape}
53
61
  - {operationId: readApplicationBootstrap, change: identity-and-notifications-moved-out-of-app-response}
54
62
  - {operationId: readRecord, change: table-file-cell-readback-modeled-as-attachment-object-or-null}
55
- - {operationId: readRecord, change: current-unlocked-datetime-locked-may-be-null}
63
+ - {operationId: readRecord, change: missing-datetime-locked-normalized-to-zero}
56
64
  - {operationId: readTemplateContent, change: json-code-null-readback-accepted}
57
65
  - {operationId: saveTemplateContent, change: empty-number-and-boolean-cells-preserved}
58
66
  - {operationId: saveTemplateContent, change: noncanonical-module-structures-contract-contained}
@@ -2568,7 +2576,7 @@ paths:
2568
2576
  uid: REC-EXAMPLE-001
2569
2577
  current_version: 70001
2570
2578
  item_id: 9001
2571
- datetime_locked: null
2579
+ datetime_locked: 0
2572
2580
  locked: false
2573
2581
  signs: []
2574
2582
  signed: false
@@ -6527,12 +6535,9 @@ components:
6527
6535
  type: integer
6528
6536
  minimum: 1
6529
6537
  datetime_locked:
6530
- type: [integer, "null"]
6538
+ type: integer
6531
6539
  minimum: 0
6532
- description: |
6533
- Unix timestamp in milliseconds; null for the current unlocked
6534
- record. Provider paths that normalize the absent timestamp may
6535
- return zero.
6540
+ description: Unix timestamp in milliseconds; zero when no lock timestamp is available.
6536
6541
  locked:
6537
6542
  type: boolean
6538
6543
  signs:
@@ -0,0 +1,28 @@
1
+ # SDK Architecture Invariants
2
+
3
+ 本文件按 `AGENTS.md` 的 architecture 指针加载;只记录环境和配置无法直接表达的设计边界。
4
+
5
+ ## Public interface
6
+
7
+ - 本项目是 Matelab Integration Contract 的通用异步 Python Consumer,负责隐藏 HTTP 编码、认证刷新、业务错误、
8
+ wire model 和 Provider 兼容细节。
9
+ - `AsyncMatelab` 是稳定入口。public interface 表达用户意图,并隐藏 controller、path、opcode、wire 字段、
10
+ generated model 和认证刷新细节。
11
+ - 公共类型区分 userid、database id、UID、relation id、staging id 等身份空间。只表达 Provider 实际返回的
12
+ identity、affected rows、排序、分页、原子性、CAS、幂等性和持久化结果。
13
+ - MCP typed refs、capabilities、fingerprints、FastMCP/Redis context 与 Agent 展示模型留在 Consumer。只有真实
14
+ 第二种实现才能引入 seam。
15
+ - 支持 Python 3.11+;源码不使用 `from __future__ import annotations`,前向引用优先靠定义顺序解决。
16
+
17
+ ## Transport, Session, and errors
18
+
19
+ - `base_url` 是 Provider API 根;query、JSON、form、multipart、streaming、bearer 和成功码逐 operation 配置。
20
+ - `Token`、`Identity`、`Session` 是冻结且拒绝额外字段的 public model,组成规范化 Session JSON。每个
21
+ `AsyncMatelab` 最多持有一个最新 Session;SDK 不绑定裸 token。不同逻辑 Session 使用不同 client,可共享注入的
22
+ `httpx2.AsyncClient`。
23
+ - SDK 负责 bearer 注入、到期前刷新、单锁并发刷新和明确允许的单次 code-5 replay。集成方负责 Session
24
+ 持久化、加密、撤销、身份归属和跨进程锁;mutation 默认不重试,multipart/stream 仅在可重放时重试。
25
+ - token 在 repr 中隐藏;显式 Session JSON 保留凭证。日志、异常和提交内容排除凭证、SSO 参数、附件内容及
26
+ 敏感 payload。download stream 必须可显式关闭。
27
+ - transport、Provider business/authentication、Contract/schema 与 usage 错误保持稳定区分;公开错误提供集成级
28
+ `category` 和保守的 `retryable`,下游不重新解释 Provider 数字 code。
@@ -0,0 +1,76 @@
1
+ # Agent Maintenance Workflows
2
+
3
+ 本文件只在对应分支触发时加载。机器值始终从仓库读取;这里记录顺序、授权边界和完成标准。
4
+
5
+ ## Contract adoption
6
+
7
+ 1. 读取当前工作区、Contract lock 和远端 release refs。只选择新的 annotated tag;移动分支和改写后的旧
8
+ snapshot 不能成为 SDK pin。记录 tag object、peeled commit、OpenAPI path 与 SHA-256。完成标准:四者固定且
9
+ 互相一致。
10
+ 2. 比较当前 snapshot 与 release commit。逐项核对 operation request、response、security、examples、成功码、
11
+ Provider evidence 及 PVD/PCG。完成标准:每个 schema/operation 差异都分类为 generated-only、adapter、public
12
+ interface、test、coverage 或 docs 影响。
13
+ 3. request/response 均由生成 wire model 校验,adapter 只映射 public Python 名称与 wire 字段。required nullable
14
+ 使用无默认值的 `T | None`;request 拒绝未知字段,response 接受新增字段。若专用 schema、顶层 request 或
15
+ Provider evidence 相互矛盾,保留当前安全边界并报告缺口。
16
+ 4. 从 release commit 复制 OpenAPI snapshot;同一变更更新 lock 的 tag、commit、snapshot path 和 SHA-256,运行
17
+ 生成脚本并审查完整 generated diff。`_generated` 只由生成脚本修改;projection 只补偿生成器表达能力并保留
18
+ inherited required、`if/then`、`oneOf`、`anyOf` 和 `not`。完成标准:snapshot hash 与 lock 相等,生成差异
19
+ 全部可由 OpenAPI 差异解释,`--check` 确定、非写入且能发现 stale output。
20
+ 5. 同步 runtime、测试、operation coverage、README、CHANGELOG、`pyproject.toml` 与 `uv.lock` 中受影响的当前态。
21
+ 完成标准:每个 Contract 差异都有实现或明确 containment,历史 changelog 未被改写。
22
+ 6. 执行 [delivery gate](#delivery-gate)。
23
+
24
+ ## Delivery gate
25
+
26
+ 先运行改动对应的聚焦测试。新增或修改 operation 覆盖 method/path/encoding、认证、成功映射及适用的空结果、
27
+ required nullable 和 additive response fields;operation coverage 与 pinned OpenAPI exact match,implemented
28
+ operation 同时具备 public interface、wire adapter 和测试。删除或收紧 public interface 时同步移除过时测试,
29
+ 不保留无承诺的墓碑 alias。生成测试覆盖 metadata/checksum、schema、required/nullable、extra policy、确定性和
30
+ stale detection。
31
+
32
+ Ruff/Basedpyright 诊断通过修复代码或精确 ignore 解决,不缩小检查范围;Basedpyright 目标为 0 errors / 0 warnings。
33
+ 依赖变化声明已验证下限并排除未经验证的下一 major,构建后端范围与 wheel package 显式配置。
34
+
35
+ 交付代码、Contract、依赖或构建元数据前,依次完成:
36
+
37
+ ```bash
38
+ uv sync --frozen
39
+ uv run python scripts/generate_models.py --check
40
+ uv run ruff check .
41
+ uv run ruff format --check .
42
+ uv run basedpyright
43
+ uv run pytest
44
+ uv build --no-build-isolation
45
+ ```
46
+
47
+ 若沙箱导致缓存、Node wheel 或解释器发现失败,在沙箱外复核同一锁定命令。完成标准:所有命令退出为零;skip
48
+ 按测试名称和原因报告,不能计为通过;最终 `git diff --check` 无错误,最终 diff 只包含任务范围内文件。
49
+
50
+ ## Provider validation
51
+
52
+ 1. 默认使用 pinned examples、`MockTransport` 和 SDK adapter tests。Provider smoke 默认 skip 且不自动读取
53
+ `.env`;实时 Provider 只在当前任务确实需要且本轮已明确授权时启用。
54
+ 2. 执行前确认目标是隔离服务、`.env.test` 处于 Git ignore、命令显式加载该文件,并列出 authentication、refresh、
55
+ mutation 或带写痕迹读取的副作用。生产目标或 mutation 需要本轮单独明确授权。
56
+ 3. SDK public smoke 使用 README 的 `Opt-in Provider consumer smoke` 命令;route-by-route conformance 由
57
+ `matelab-spec` Provider Verification 负责。两者的结果分开报告。
58
+ 4. mutation 场景在写前记录 fixture,在写后回读,并尽力清理。完成标准:目标、Provider commit、命令、通过数、
59
+ skip/failure 和清理结果均可追溯;凭证及敏感 payload 未进入输出或提交。
60
+
61
+ ## Release
62
+
63
+ 1. 只有用户明确要求 commit、push、tag 或 publish 时进入发布流程。读取 `[project].version`,确认版本未发布、
64
+ changelog 已追加且 Contract pin 完整。SDK 遵循 SemVer;公开接口删除、重命名、类型收紧或行为变化按 breaking
65
+ change 处理,Contract Line 不充当 SDK 版本。完成标准:版本、文档和 tag 名 `v{version}` 一致。
66
+ 2. 在完整 delivery gate 通过后创建单一 release commit。完成标准:tracked worktree 干净,commit 仅包含已审查
67
+ diff。
68
+ 3. 以 release commit 的 committer timestamp 设置 `SOURCE_DATE_EPOCH`,从锁定环境构建两次并逐字节比较 wheel
69
+ 与 sdist;运行 `scripts/check_release.py`,再从 wheel 验证公开导入和类型消费。完成标准:制品可复现,
70
+ Apache-2.0 `LICENSE`/`NOTICE`、PEP 639 METADATA、`py.typed` 与公开边界审计通过,`_generated` 未公开,
71
+ checksum 已记录。
72
+ 4. 创建指向 release commit 的 annotated `v{version}` tag。先推送 `origin` 的 main/tag,再推送 GitHub mirror 的
73
+ main/tag;只有与项目版本完全匹配的 tag 触发 Trusted Publishing workflow,第三方 Action 固定 immutable
74
+ commit SHA。
75
+ 5. 跟踪 CI 的 Python 边界测试、生成检查、可复现构建、安装后消费和 PyPI publish。完成标准:两个远端 main/tag
76
+ 都指向 release commit,workflow success,PyPI 对应版本可读取,本地 tracked worktree 干净。
@@ -3,7 +3,7 @@
3
3
  schema_version: 1
4
4
  contract:
5
5
  line: integration-v1
6
- release: v0.4.4
6
+ release: v0.4.5
7
7
  operation_count: 71
8
8
 
9
9
  operations:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "matelab-python-sdk"
7
- version = "0.1.0a21"
7
+ version = "0.1.0a23"
8
8
  description = "Reusable async Python client for the Matelab Integration Contract"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -11,7 +11,7 @@ from pydantic import ConfigDict, Field, RootModel, SecretStr, StrictBool, Strict
11
11
 
12
12
  from matelab._wire_validation import WireModel
13
13
 
14
- OPENAPI_INFO_VERSION = "0.4.4"
14
+ OPENAPI_INFO_VERSION = "0.4.5"
15
15
 
16
16
 
17
17
  class ResponseEnvelope(WireModel):
@@ -3741,12 +3741,9 @@ class ItemViewResponse(WireModel):
3741
3741
  """
3742
3742
  current_version: Annotated[StrictInt, Field(ge=0)]
3743
3743
  item_id: Annotated[StrictInt, Field(ge=1)]
3744
- datetime_locked: Annotated[StrictInt | None, Field(ge=0)]
3744
+ datetime_locked: Annotated[StrictInt, Field(ge=0)]
3745
3745
  """
3746
- Unix timestamp in milliseconds; null for the current unlocked
3747
- record. Provider paths that normalize the absent timestamp may
3748
- return zero.
3749
-
3746
+ Unix timestamp in milliseconds; zero when no lock timestamp is available.
3750
3747
  """
3751
3748
  locked: StrictBool
3752
3749
  signs: list[RecordSignature]
@@ -1,13 +1,14 @@
1
1
  import re
2
2
  import secrets
3
3
  from collections.abc import AsyncIterable, Iterable, Iterator, Mapping, Sequence
4
+ from copy import deepcopy
4
5
  from dataclasses import dataclass, field
5
6
  from html.parser import HTMLParser
6
7
  from types import MappingProxyType
7
- from typing import IO, Literal, TypeAlias, cast
8
+ from typing import IO, Literal, Self, TypeAlias, cast
8
9
  from urllib.parse import unquote
9
10
 
10
- from pydantic import BaseModel, JsonValue
11
+ from pydantic import BaseModel, JsonValue, model_validator
11
12
  from typing_extensions import override
12
13
 
13
14
  from matelab._generated import models as wire_models
@@ -21,6 +22,56 @@ from matelab.domains.users import UserRef
21
22
  from matelab.errors import MatelabProtocolError, MatelabUsageError
22
23
  from matelab.streaming import ByteRange, DownloadStream
23
24
 
25
+ _MISSING_TIMESTAMP_MARKER = "__matelab_missing_record_attachment_timestamp__"
26
+
27
+
28
+ def _table_file_attachments(modules: JsonValue) -> Iterator[dict[str, JsonValue]]:
29
+ if not isinstance(modules, list):
30
+ return
31
+ for module in modules:
32
+ if not isinstance(module, dict) or module.get("type") != "table":
33
+ continue
34
+ columns = module.get("data")
35
+ if not isinstance(columns, list):
36
+ continue
37
+ for column in columns:
38
+ if not isinstance(column, dict) or column.get("type") != "file":
39
+ continue
40
+ values = column.get("data")
41
+ if not isinstance(values, list):
42
+ continue
43
+ for value in values:
44
+ if isinstance(value, dict):
45
+ yield value
46
+
47
+
48
+ class _ReadRecordResponse(wire_models.ItemViewResponse):
49
+ """Contain the Provider's temporary omission of stored attachment timestamps."""
50
+
51
+ @model_validator(mode="before")
52
+ @classmethod
53
+ def _allow_missing_attachment_timestamp(cls, value: object) -> object:
54
+ if not isinstance(value, dict):
55
+ return value
56
+ payload = deepcopy(cast(dict[str, JsonValue], value))
57
+ modules = payload.get("modules")
58
+ if modules is None:
59
+ return payload
60
+ for attachment in _table_file_attachments(modules):
61
+ if "timestamp" not in attachment and _MISSING_TIMESTAMP_MARKER not in attachment:
62
+ attachment["timestamp"] = 1
63
+ attachment[_MISSING_TIMESTAMP_MARKER] = True
64
+ return payload
65
+
66
+ @model_validator(mode="after")
67
+ def _restore_missing_attachment_timestamp(self) -> Self:
68
+ modules: JsonValue = [{"type": module.type, "data": cast(JsonValue, module.data)} for module in self.modules]
69
+ for attachment in _table_file_attachments(modules):
70
+ if attachment.pop(_MISSING_TIMESTAMP_MARKER, None) is True:
71
+ del attachment["timestamp"]
72
+ return self
73
+
74
+
24
75
  _LIST_RECORDS = Operation(
25
76
  method="POST",
26
77
  path="/eln_api/items",
@@ -68,7 +119,7 @@ _READ_RECORD = Operation(
68
119
  method="GET",
69
120
  path="/eln_items/item_view",
70
121
  encoding=Encoding.QUERY,
71
- response_model=wire_models.ItemViewResponse,
122
+ response_model=_ReadRecordResponse,
72
123
  success_codes=frozenset({0}),
73
124
  retry_on_access_expired=True,
74
125
  )
@@ -88,7 +88,7 @@ def test_generated_response_preserves_required_nullable_fields() -> None:
88
88
  "uid": None,
89
89
  "current_version": 0,
90
90
  "item_id": 1,
91
- "datetime_locked": None,
91
+ "datetime_locked": 0,
92
92
  "locked": False,
93
93
  "signs": [],
94
94
  "signed": False,
@@ -107,7 +107,12 @@ def test_generated_response_preserves_required_nullable_fields() -> None:
107
107
  assert ItemViewResponse.model_fields["keywords"].is_required()
108
108
  assert response.keywords is None
109
109
  assert ItemViewResponse.model_fields["datetime_locked"].is_required()
110
- assert response.datetime_locked is None
110
+ assert response.datetime_locked == 0
111
+
112
+ invalid = response.model_dump(mode="json")
113
+ invalid["datetime_locked"] = None
114
+ with pytest.raises(ValidationError):
115
+ _ = ItemViewResponse.model_validate(invalid)
111
116
 
112
117
 
113
118
  def _item_view_payload(modules: list[object]) -> dict[str, object]:
@@ -523,7 +528,7 @@ def test_generated_models_cover_every_component_schema_and_non_streaming_operati
523
528
  }
524
529
  assert expected_response_models <= class_names
525
530
  assert len(operation_ids) == 71
526
- assert wire_models.OPENAPI_INFO_VERSION == "0.4.4"
531
+ assert wire_models.OPENAPI_INFO_VERSION == "0.4.5"
527
532
 
528
533
 
529
534
  def test_generation_is_deterministic(tmp_path: Path) -> None:
@@ -635,8 +640,8 @@ def test_contract_lock_is_the_single_release_pin() -> None:
635
640
  openapi = cast(dict[str, Any], lock["openapi"])
636
641
  assert set(release) == {"tag", "commit"}
637
642
  assert set(openapi) == {"source_path", "snapshot", "sha256"}
638
- assert release == {"tag": "v0.4.4", "commit": "be070c5f931b2063327a3beaccc53a360204b64e"}
639
- assert openapi["sha256"] == "2de38e46d3814b8b049af8afd18e85fb20be6ca29cc409390d25df663abc43f9"
643
+ assert release == {"tag": "v0.4.5", "commit": "6afac7ff151680130e6e4bc6d95782589b97f4e7"}
644
+ assert openapi["sha256"] == "31a8d03b4366e2345dfccdbbaaaccf6754b2711428afa4295d778d121988460d"
640
645
  assert re.fullmatch(r"[0-9a-f]{40}", cast(str, release["commit"]))
641
646
  assert re.fullmatch(r"[0-9a-f]{64}", cast(str, openapi["sha256"]))
642
647
  assert not (ROOT / "contracts" / "matelab-integration-v1.sha256").exists()
@@ -243,7 +243,7 @@ async def test_read_current_record_maps_required_nullable_and_open_modules() ->
243
243
  uid="REC-EXAMPLE-001",
244
244
  current_version=70001,
245
245
  item_id=9001,
246
- datetime_locked=None,
246
+ datetime_locked=0,
247
247
  locked=False,
248
248
  signs=[
249
249
  {
@@ -303,6 +303,77 @@ async def test_read_current_record_maps_required_nullable_and_open_modules() ->
303
303
  assert "74034dae" not in repr(record.attachments[0])
304
304
 
305
305
 
306
+ @pytest.mark.asyncio
307
+ async def test_read_tolerates_provider_table_attachment_without_timestamp() -> None:
308
+ payload = _item_view_payload(uid="REC-EXAMPLE-001")
309
+ attachment = {
310
+ "hash": "d6a8f491a743ee3c10b42b4519d7545d7f1d79a7e98ed2ef9c03c0c1d75a1a91",
311
+ "filename": "evidence.pdf",
312
+ "size": 34,
313
+ }
314
+ payload["modules"] = [
315
+ {
316
+ "type": "table",
317
+ "name": "Data sheet",
318
+ "rows": ["row-1"],
319
+ "data": [{"name": "File", "type": "file", "data": [attachment]}],
320
+ }
321
+ ]
322
+
323
+ async def handler(_: Request) -> Response:
324
+ return Response(200, json=payload)
325
+
326
+ notebook = NotebookRef(notebook_id=23, title="Shared Example", owner_userid=202, scope="shared")
327
+ source = RecordLocator(notebook=notebook, record=RecordRef(record_id=9001, record_uid="REC-EXAMPLE-001"))
328
+ async with AsyncClient(base_url="https://matelab.example/api", transport=MockTransport(handler)) as http_client:
329
+ client = AsyncMatelab("https://ignored.example", session=_session(), http_client=http_client)
330
+ record = await client.records.read(source)
331
+
332
+ assert record.modules[0] == {
333
+ "name": "Data sheet",
334
+ "type": "table",
335
+ "data": [{"name": "File", "type": "file", "data": [attachment]}],
336
+ "language": None,
337
+ "style": None,
338
+ "rows": ["row-1"],
339
+ }
340
+ assert [(item.filename, item.size) for item in record.attachments] == [("evidence.pdf", 34)]
341
+
342
+
343
+ @pytest.mark.parametrize("invalid_change", ["filename", "hash", "size", "null_timestamp"])
344
+ @pytest.mark.asyncio
345
+ async def test_read_timestamp_compatibility_does_not_relax_other_attachment_requirements(invalid_change: str) -> None:
346
+ payload = _item_view_payload(uid="REC-EXAMPLE-001")
347
+ attachment: dict[str, object] = {
348
+ "hash": "d6a8f491a743ee3c10b42b4519d7545d7f1d79a7e98ed2ef9c03c0c1d75a1a91",
349
+ "filename": "evidence.pdf",
350
+ "size": 34,
351
+ "timestamp": 1_767_946_200_000,
352
+ }
353
+ if invalid_change == "null_timestamp":
354
+ attachment["timestamp"] = None
355
+ else:
356
+ del attachment[invalid_change]
357
+ payload["modules"] = [
358
+ {
359
+ "type": "table",
360
+ "name": "Data sheet",
361
+ "rows": ["row-1"],
362
+ "data": [{"name": "File", "type": "file", "data": [attachment]}],
363
+ }
364
+ ]
365
+
366
+ async def handler(_: Request) -> Response:
367
+ return Response(200, json=payload)
368
+
369
+ notebook = NotebookRef(notebook_id=23, title="Shared Example", owner_userid=202, scope="shared")
370
+ source = RecordLocator(notebook=notebook, record=RecordRef(record_id=9001, record_uid="REC-EXAMPLE-001"))
371
+ async with AsyncClient(base_url="https://matelab.example/api", transport=MockTransport(handler)) as http_client:
372
+ client = AsyncMatelab("https://ignored.example", session=_session(), http_client=http_client)
373
+ with pytest.raises(MatelabProtocolError, match="does not match the Integration Contract"):
374
+ _ = await client.records.read(source)
375
+
376
+
306
377
  @pytest.mark.asyncio
307
378
  async def test_read_rejects_historical_table_attachment_array_at_public_boundary() -> None:
308
379
  payload = _item_view_payload(uid="REC-EXAMPLE-001")
@@ -352,7 +352,7 @@ wheels = [
352
352
 
353
353
  [[package]]
354
354
  name = "matelab-python-sdk"
355
- version = "0.1.0a21"
355
+ version = "0.1.0a23"
356
356
  source = { editable = "." }
357
357
  dependencies = [
358
358
  { name = "httpx2" },
@@ -1,94 +0,0 @@
1
- # AGENTS.md
2
-
3
- 本文档只保留维护 `matelab-python-sdk` 必须长期遵守的约束,不记录当前功能清单、迁移历史、阶段性状态或
4
- 一次性验收数字。
5
-
6
- ## 文档与权威边界
7
-
8
- - `README.md`:当前 public interface、集成语义、示例及可执行的开发/发布流程;不承担版本迁移历史。
9
- - `CONTEXT.md`:公共领域术语及易混概念的统一词汇;不记录实现或协议细节。
10
- - `CHANGELOG.md`:按 SDK 版本记录变化和迁移影响;不作为当前接口说明。
11
- - `docs/operation-coverage.yaml`:operation 状态、公开入口、稳定性和已知问题的机器权威;不写叙述性用法。
12
- - `pyproject.toml`:SDK 版本、依赖范围、构建和工具配置的机器权威;`uv.lock` 只锁定本仓库环境。
13
- - `contracts/matelab-integration-v1.lock.json` 及其 snapshot:Contract pin 和 wire schema 的唯一机器权威。
14
- - `src/matelab/__init__.py::__all__`:公开导入边界;测试是行为证据,不替代上述文档。
15
- - 本文件只规定维护原则和质量门槛。机器权威变化时,同一变更必须同步受影响的叙述文档,避免复制无归属的
16
- 版本号、配置值或 operation 清单。
17
-
18
- ## 工作范围与 public interface
19
-
20
- - 本项目是 Matelab Integration Contract 的通用异步 Python Consumer,负责隐藏 HTTP 编码、认证刷新、业务
21
- 错误、wire model 和 Provider 兼容细节。
22
- - `/home/tnzhu/projects/matelab-spec` 负责 Wire Contract、兼容策略及 Provider 问题;`matelab-server` 是 Provider
23
- 实现,`matelab-mcp` 只是 Consumer 证据,均不能反向定义 SDK interface。
24
- - 修改前执行 `git status`,现有改动视为用户工作。默认只修改本仓库;未经明确授权,不修改相邻仓库、不迁移
25
- MCP、不 commit/push/tag/publish,也不对真实 Provider 执行 mutation。
26
- - `AsyncMatelab` 是稳定入口,能力按领域组织。public interface 只表达用户意图,不泄漏 controller、path、
27
- opcode、wire 字段、generated model 或认证刷新细节。
28
- - 公共类型必须区分 userid、database id、UID、relation id、staging id 等身份空间。Provider 未返回的 identity、
29
- affected rows、排序、分页、原子性、CAS、幂等性或持久化结果不得推测;legacy 限制应诚实表达。
30
- - MCP typed refs、capabilities、fingerprints、FastMCP/Redis context 和 Agent 展示模型不得进入 SDK。仅在存在真实
31
- 第二种实现时增加 seam,不为测试预设抽象。
32
- - 支持 Python 3.11+;源码不使用 `from __future__ import annotations`,前向引用优先通过定义顺序解决。
33
-
34
- ## Contract、生成与 transport
35
-
36
- - 每个 operation 必须核对 pinned OpenAPI 的 request、response、security、examples 和成功码,并查看相关
37
- PVD/PCG。Contract 不足时可只读参考 Provider/Consumer;仍无法安全表达就报告缺口,不猜测或静默适配漂移。
38
- - Contract Adoption 只能来自不可变 release tag,并在同一变更中更新唯一 lock 的 tag、commit、snapshot path
39
- 和 SHA-256,重新生成并复核差异;不得跟踪移动分支或改写正式 snapshot。
40
- - request/response 均由生成 wire model 校验,adapter 只负责 public Python 名称与 wire 字段映射。required
41
- nullable 使用无默认值的 `T | None`;request 拒绝未知字段,response 兼容新增字段。
42
- - 不手工编辑 `_generated`。generation projection 只能补偿生成器表达能力,并保留 inherited required 及
43
- `if/then`、`oneOf`、`anyOf`、`not`;生成变化必须可追溯,`--check` 必须确定、非写入且能发现 stale output。
44
- - `base_url` 是 Provider API 根;query、JSON、form、multipart、streaming、bearer 和成功码均按 operation 配置,
45
- 不能用全局默认掩盖差异。
46
- - `Token`、`Identity` 和 `Session` 是冻结、拒绝额外字段的 public model,组成规范化 Session JSON。每个
47
- `AsyncMatelab` 最多持有一个最新 Session;SDK 不绑定裸 token,持久化、加密、撤销、身份归属和跨进程锁
48
- 属于集成方。不同逻辑 Session 不共享 client,但可共享注入的 `httpx.AsyncClient`。
49
- - token 在 repr 中隐藏,显式 Session JSON 保留凭证;凭证、SSO 参数、附件内容及敏感 payload 不得进入日志、
50
- 异常或提交内容。
51
- - access 到期前刷新;并发刷新使用单锁;安全请求遇到 `code=5` 最多刷新重试一次。mutation 默认不重试,
52
- multipart/stream 仅在可重放时重试,download stream 必须可显式关闭。
53
- - transport、Provider business/authentication、Contract/schema 和 usage 错误必须稳定区分;公开错误直接提供
54
- 集成级 `category` 和保守的 `retryable`,下游不应重新解释 Provider 数字 code。
55
-
56
- ## 测试与质量门槛
57
-
58
- - 测试以 public interface 为主。新增或修改 operation 至少覆盖 method/path/encoding、认证、成功映射及适用的
59
- 空结果、required nullable、additive response fields;通用 transport/auth/error 语义在集中测试中覆盖。
60
- - 删除或收紧 public interface 时同步删除过时测试;无明确兼容承诺时不保留墓碑 alias 或测试。
61
- - operation coverage 必须与 pinned OpenAPI exact match;implemented operation 必须同时具备 public interface、
62
- wire adapter 和测试。生成测试必须覆盖 metadata/checksum、schema、required/nullable、extra policy、确定性和
63
- stale detection。
64
- - Provider smoke 默认 skip 且不自动读取 `.env`;本地 `.env.test` 必须保持 Git ignore。只能通过显式
65
- `uv run --env-file .env.test` 对已确认的隔离目标 opt in;生产目标、mutation 或带写痕迹的读取必须获得本轮
66
- 明确授权,并回读、尽力清理。
67
- - Ruff/Basedpyright 的具体配置以 `pyproject.toml` 为准;不得通过排除测试、全局关规则或缩小检查范围消除诊断。
68
- Basedpyright 目标为 0 errors / 0 warnings,必要 ignore 必须精确并说明原因。
69
- - 先跑相关测试,交付前跑完整门槛;skip 必须说明原因,不能报告为通过:
70
-
71
- ```bash
72
- uv sync --frozen
73
- uv run python scripts/generate_models.py --check
74
- uv run ruff check .
75
- uv run ruff format --check .
76
- uv run basedpyright
77
- uv run pytest
78
- uv build --no-build-isolation
79
- ```
80
-
81
- 若沙箱造成缓存、Node wheel 或解释器发现失败,应在沙箱外复核同一命令,不得改配置或使用未锁定工具绕过。
82
-
83
- ## 依赖、版本与发布
84
-
85
- - runtime dependency 必须声明经支持 Python 版本验证的下限,并排除未经验证的下一个 major;构建后端范围和
86
- wheel package 必须显式配置,不能依赖自动发现。
87
- - `[project].version` 是唯一 SDK 版本源;SDK 使用 SemVer,Contract Line 不是 SDK 版本。公开接口删除、重命名、
88
- 类型收紧或行为变化均按 breaking change 处理。
89
- - 发行采用 Apache-2.0;`LICENSE`、`NOTICE`、PEP 639 metadata 和制品内容必须一致,wheel 包含 `py.typed` 且
90
- 不公开 `_generated`。
91
- - Gitee `origin` 是内部主仓库,私有 GitHub mirror 只承载 PyPI Trusted Publishing。release workflow 仅由与
92
- 项目版本完全匹配的 `v*` tag 触发,第三方 Action 固定不可变 commit SHA。
93
- - release 必须来自干净 revision、锁定环境和固定 `SOURCE_DATE_EPOCH`,记录 Contract 与制品 checksum;候选
94
- 制品须审计内容/METADATA、验证安装后的公开导入和类型消费、覆盖 Python 版本边界并确认可复现构建。