matelab-python-sdk 0.1.0a20__tar.gz → 0.1.0a21__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 (65) hide show
  1. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/CHANGELOG.md +13 -0
  2. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/PKG-INFO +18 -15
  3. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/README.md +17 -14
  4. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/contracts/matelab-integration-v1.lock.json +3 -3
  5. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/contracts/matelab-integration-v1.openapi.yaml +55 -82
  6. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/docs/operation-coverage.yaml +4 -4
  7. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/pyproject.toml +1 -1
  8. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/_generated/models.py +25 -40
  9. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_generation.py +43 -5
  10. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_operation_coverage.py +1 -1
  11. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_records.py +1 -1
  12. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/uv.lock +1 -1
  13. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/.env.example +0 -0
  14. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/.github/workflows/release.yml +0 -0
  15. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/.gitignore +0 -0
  16. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/AGENTS.md +0 -0
  17. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/CONTEXT.md +0 -0
  18. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/LICENSE +0 -0
  19. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/NOTICE +0 -0
  20. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/scripts/check_installed_package.py +0 -0
  21. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/scripts/check_release.py +0 -0
  22. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/scripts/generate_models.py +0 -0
  23. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/__init__.py +0 -0
  24. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/_generated/__init__.py +0 -0
  25. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/_hashes.py +0 -0
  26. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/_transport.py +0 -0
  27. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/_wire_validation.py +0 -0
  28. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/client.py +0 -0
  29. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/domains/__init__.py +0 -0
  30. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/domains/_attachment_content.py +0 -0
  31. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/domains/cloud_drive.py +0 -0
  32. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/domains/groups.py +0 -0
  33. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/domains/literature.py +0 -0
  34. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/domains/notebooks.py +0 -0
  35. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/domains/records.py +0 -0
  36. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/domains/templates.py +0 -0
  37. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/domains/uploads.py +0 -0
  38. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/domains/users.py +0 -0
  39. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/errors.py +0 -0
  40. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/py.typed +0 -0
  41. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/session.py +0 -0
  42. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/src/matelab/streaming.py +0 -0
  43. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/conftest.py +0 -0
  44. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/provider/test_provider_smoke.py +0 -0
  45. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_async_attachment_upload.py +0 -0
  46. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_client.py +0 -0
  47. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_cloud_drive.py +0 -0
  48. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_comments_and_downloads.py +0 -0
  49. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_consumer_smoke.py +0 -0
  50. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_errors.py +0 -0
  51. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_extended_records.py +0 -0
  52. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_groups.py +0 -0
  53. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_notebook_lifecycle.py +0 -0
  54. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_notebooks.py +0 -0
  55. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_record_lifecycle.py +0 -0
  56. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_record_mutations.py +0 -0
  57. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_release.py +0 -0
  58. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_streaming.py +0 -0
  59. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_template_lifecycle.py +0 -0
  60. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_templates.py +0 -0
  61. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_transport.py +0 -0
  62. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_uploads_and_literature.py +0 -0
  63. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/test_users.py +0 -0
  64. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/typing/consumer.py +0 -0
  65. {matelab_python_sdk-0.1.0a20 → matelab_python_sdk-0.1.0a21}/tests/typing/pyproject.toml +0 -0
@@ -3,6 +3,19 @@
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.0a21 - 2026-08-28
7
+
8
+ - Adopts immutable `matelab-spec v0.4.4` at commit `be070c5f931b2063327a3beaccc53a360204b64e` and OpenAPI
9
+ SHA-256 `2de38e46d3814b8b049af8afd18e85fb20be6ca29cc409390d25df663abc43f9`, aligned with Provider evidence
10
+ `7bd9704286057653632424fa82f413d99cd69ab6`.
11
+ - Accepts the required nullable `readRecord.datetime_locked` wire field for current unlocked records and follows the
12
+ unexposed `/actions/app` rename to `readApplicationBootstrap`; SDK Session identity remains sourced from token issuance.
13
+ - Keeps `RecordFilesAttachmentAppend` root-only. The release describes a safe optional folder path in its dedicated
14
+ staged-name append schema, but `UpdateRecordRequest.add[]` still references `AddValueOperation`, which forbids that
15
+ `path`;
16
+ exposing it would require bypassing whole-request Contract validation. PVD-044 also records that malformed path
17
+ containers can silently append at root.
18
+
6
19
  ## 0.1.0a20 - 2026-08-24
7
20
 
8
21
  - Adopts immutable `matelab-spec v0.4.3` at commit `7965ac9803505ae35dc5ce36f52afc3c2c133c45` and OpenAPI
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matelab-python-sdk
3
- Version: 0.1.0a20
3
+ Version: 0.1.0a21
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.0a20`. `[project].version` in `pyproject.toml` is the sole SDK version source;
38
+ The current alpha is `0.1.0a21`. `[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.3` Contract Release. The sole release pin is
41
+ The SDK is pinned to the immutable `matelab-spec v0.4.4` 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.0a20-py3-none-any.whl
67
+ python -m pip install dist/release/matelab_python_sdk-0.1.0a21-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
- `a3e6b961800f6b1ab666f0d3f6cd4c64c2a3ce27`. Provider limitations that remain relevant to integrations are documented
74
+ `7bd9704286057653632424fa82f413d99cd69ab6`. 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.3 Contract also declares optional code-4 `errs`, but PVD-041 and PCG-015 record opaque array/object
124
+ The pinned v0.4.4 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`
@@ -316,8 +316,10 @@ supports one file column. A locally rejected target does not spend the handle; o
316
316
  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
- other non-conflicting record changes. `RecordFilesAttachmentAppend` targets the module root; the current Provider does
320
- not accept a files-folder path (PVD-044):
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
321
+ the `path` property declared by the dedicated staged-name append shape; the SDK therefore does not expose folder paths
322
+ until that wire-schema conflict is resolved (PVD-044):
321
323
 
322
324
  ```python
323
325
  from matelab import RecordAttachmentReplacement, RecordFilesAttachmentAppend, RecordValueUpdate
@@ -427,8 +429,8 @@ and `deletePersonalLiteratureItem`.
427
429
 
428
430
  Every other implemented operation is explicitly `experimental`; the exact per-operation list and its
429
431
  PVD/PCG references live in
430
- `docs/operation-coverage.yaml`. `resolveCurrentIdentity` is intentionally unexposed because the SDK accepts only
431
- complete, integration-validated Sessions and does not bind bare external token pairs. `readLiteratureCreateTemplate`
432
+ `docs/operation-coverage.yaml`. `readApplicationBootstrap` is intentionally unexposed because it returns the Provider
433
+ application environment and menu model, while SDK identity is sourced from token issuance. `readLiteratureCreateTemplate`
432
434
  is intentionally unexposed because it returns a frontend form definition and an optional deployment flag rather than
433
435
  a resource needed by the SDK interface. There are no `planned` operations. Experimental support means the SDK
434
436
  validates and exposes the pinned Contract while preserving limitations such as unstable ordering/pagination,
@@ -618,8 +620,9 @@ returns the acknowledgement classification
618
620
  issue a post-write read. Database, active-browser, and unclassified acknowledgements remain distinct, and mutation
619
621
  retries stay disabled. Encrypted record content is outside the SDK's public read/update interface: the list-level
620
622
  `RecordSummary.encrypted` flag lets callers identify and skip it, while the Provider password field and frontend-only
621
- password lifecycle remain unexposed. Files-module attachment append is root-only because the current Provider rejects
622
- folder-path segments (PVD-044). A staged attachment is indeterminate once the write has started.
623
+ password lifecycle remain unexposed. Files-module attachment append remains root-only because the pinned top-level
624
+ update request schema does not admit the dedicated staged-name append shape's folder `path`; malformed path containers
625
+ may also silently append at root according to PVD-044. A staged attachment is indeterminate once the write has started.
623
626
 
624
627
  Relation addition reads both endpoints and checks their resolved data server before writing; this reduces PVD-019
625
628
  risk but is not an atomic Provider authorization guarantee. It does not list relations merely to inspect the
@@ -917,6 +920,6 @@ uv run python scripts/check_release.py dist/release/*.whl dist/release/*.tar.gz
917
920
  ```
918
921
 
919
922
  Rebuilding the same commit with the same locked environment and `SOURCE_DATE_EPOCH` must produce
920
- byte-identical wheel and source distribution hashes. The release is bound to immutable `matelab-spec v0.4.3`, commit
921
- `7965ac9803505ae35dc5ce36f52afc3c2c133c45`, and OpenAPI SHA-256
922
- `6576812dd13f92ad58131f4edb8dc919d1b1fe0f19a695c2d25fbe6d13a8445b`.
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`.
@@ -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.0a20`. `[project].version` in `pyproject.toml` is the sole SDK version source;
10
+ The current alpha is `0.1.0a21`. `[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.3` Contract Release. The sole release pin is
13
+ The SDK is pinned to the immutable `matelab-spec v0.4.4` 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.0a20-py3-none-any.whl
39
+ python -m pip install dist/release/matelab_python_sdk-0.1.0a21-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
- `a3e6b961800f6b1ab666f0d3f6cd4c64c2a3ce27`. Provider limitations that remain relevant to integrations are documented
46
+ `7bd9704286057653632424fa82f413d99cd69ab6`. 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.3 Contract also declares optional code-4 `errs`, but PVD-041 and PCG-015 record opaque array/object
96
+ The pinned v0.4.4 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`
@@ -288,8 +288,10 @@ supports one file column. A locally rejected target does not spend the handle; o
288
288
  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
- other non-conflicting record changes. `RecordFilesAttachmentAppend` targets the module root; the current Provider does
292
- not accept a files-folder path (PVD-044):
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
293
+ the `path` property declared by the dedicated staged-name append shape; the SDK therefore does not expose folder paths
294
+ until that wire-schema conflict is resolved (PVD-044):
293
295
 
294
296
  ```python
295
297
  from matelab import RecordAttachmentReplacement, RecordFilesAttachmentAppend, RecordValueUpdate
@@ -399,8 +401,8 @@ and `deletePersonalLiteratureItem`.
399
401
 
400
402
  Every other implemented operation is explicitly `experimental`; the exact per-operation list and its
401
403
  PVD/PCG references live in
402
- `docs/operation-coverage.yaml`. `resolveCurrentIdentity` is intentionally unexposed because the SDK accepts only
403
- complete, integration-validated Sessions and does not bind bare external token pairs. `readLiteratureCreateTemplate`
404
+ `docs/operation-coverage.yaml`. `readApplicationBootstrap` is intentionally unexposed because it returns the Provider
405
+ application environment and menu model, while SDK identity is sourced from token issuance. `readLiteratureCreateTemplate`
404
406
  is intentionally unexposed because it returns a frontend form definition and an optional deployment flag rather than
405
407
  a resource needed by the SDK interface. There are no `planned` operations. Experimental support means the SDK
406
408
  validates and exposes the pinned Contract while preserving limitations such as unstable ordering/pagination,
@@ -590,8 +592,9 @@ returns the acknowledgement classification
590
592
  issue a post-write read. Database, active-browser, and unclassified acknowledgements remain distinct, and mutation
591
593
  retries stay disabled. Encrypted record content is outside the SDK's public read/update interface: the list-level
592
594
  `RecordSummary.encrypted` flag lets callers identify and skip it, while the Provider password field and frontend-only
593
- password lifecycle remain unexposed. Files-module attachment append is root-only because the current Provider rejects
594
- folder-path segments (PVD-044). A staged attachment is indeterminate once the write has started.
595
+ password lifecycle remain unexposed. Files-module attachment append remains root-only because the pinned top-level
596
+ update request schema does not admit the dedicated staged-name append shape's folder `path`; malformed path containers
597
+ may also silently append at root according to PVD-044. A staged attachment is indeterminate once the write has started.
595
598
 
596
599
  Relation addition reads both endpoints and checks their resolved data server before writing; this reduces PVD-019
597
600
  risk but is not an atomic Provider authorization guarantee. It does not list relations merely to inspect the
@@ -889,6 +892,6 @@ uv run python scripts/check_release.py dist/release/*.whl dist/release/*.tar.gz
889
892
  ```
890
893
 
891
894
  Rebuilding the same commit with the same locked environment and `SOURCE_DATE_EPOCH` must produce
892
- byte-identical wheel and source distribution hashes. The release is bound to immutable `matelab-spec v0.4.3`, commit
893
- `7965ac9803505ae35dc5ce36f52afc3c2c133c45`, and OpenAPI SHA-256
894
- `6576812dd13f92ad58131f4edb8dc919d1b1fe0f19a695c2d25fbe6d13a8445b`.
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`.
@@ -2,12 +2,12 @@
2
2
  "schema_version": 1,
3
3
  "contract_line": "integration-v1",
4
4
  "release": {
5
- "tag": "v0.4.3",
6
- "commit": "7965ac9803505ae35dc5ce36f52afc3c2c133c45"
5
+ "tag": "v0.4.4",
6
+ "commit": "be070c5f931b2063327a3beaccc53a360204b64e"
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": "6576812dd13f92ad58131f4edb8dc919d1b1fe0f19a695c2d25fbe6d13a8445b"
11
+ "sha256": "2de38e46d3814b8b049af8afd18e85fb20be6ca29cc409390d25df663abc43f9"
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.3
4
+ version: 0.4.4
5
5
  description: |
6
6
  Current PHP wire contract for generating SDKs against the selected
7
7
  `matelab-server` Provider version.
@@ -9,7 +9,7 @@ info:
9
9
  x-contract-line: integration-v1
10
10
  x-provider-evidence:
11
11
  repository: /home/tnzhu/projects/matelab-server
12
- commit: a3e6b961800f6b1ab666f0d3f6cd4c64c2a3ce27
12
+ commit: 7bd9704286057653632424fa82f413d99cd69ab6
13
13
  files:
14
14
  - controller/Actions.php
15
15
  - controller/Tokens.php
@@ -39,8 +39,8 @@ info:
39
39
  - sql/matelab.sql
40
40
  - sql/recommend.sql
41
41
  x-provider-audit:
42
- currentCommit: a3e6b961800f6b1ab666f0d3f6cd4c64c2a3ce27
43
- previousReleaseEvidenceCommit: 59101f006e9fd6d6ce0b951dfd34695a7949b54f
42
+ currentCommit: 7bd9704286057653632424fa82f413d99cd69ab6
43
+ previousReleaseEvidenceCommit: a3e6b961800f6b1ab666f0d3f6cd4c64c2a3ce27
44
44
  contractStatus: current-provider-aligned
45
45
  incorporatedChanges:
46
46
  - {operationId: stageTemporaryFile, change: staging-identifiers-expanded-to-100}
@@ -49,8 +49,10 @@ info:
49
49
  - {operationId: importRecordDataset, change: explicit-null-table-file-cells-rejected}
50
50
  - {operationId: extractLiteratureMetadata, change: doi-format-validated-before-extraction}
51
51
  - {operationId: searchRecordData, change: encrypted-records-excluded}
52
- - {operationId: updateRecordData, change: files-folder-path-removed-from-callable-shape}
52
+ - {operationId: updateRecordData, change: safe-files-folder-path-restored-to-callable-shape}
53
+ - {operationId: readApplicationBootstrap, change: identity-and-notifications-moved-out-of-app-response}
53
54
  - {operationId: readRecord, change: table-file-cell-readback-modeled-as-attachment-object-or-null}
55
+ - {operationId: readRecord, change: current-unlocked-datetime-locked-may-be-null}
54
56
  - {operationId: readTemplateContent, change: json-code-null-readback-accepted}
55
57
  - {operationId: saveTemplateContent, change: empty-number-and-boolean-cells-preserved}
56
58
  - {operationId: saveTemplateContent, change: noncanonical-module-structures-contract-contained}
@@ -68,8 +70,11 @@ info:
68
70
  record and currently resolves its private staging state through a
69
71
  native `#hash` reference.
70
72
 
71
- The MCP identity, ELN-group list, and notebook-list calls currently use
72
- non-contract POST and must migrate to GET.
73
+ At this recorded MCP commit, identity resolution used a non-contract
74
+ POST to `/actions/app`. The current Provider removed identity from that
75
+ response, so this historical call cannot define current wire; session
76
+ identity must come from token issuance. ELN-group and notebook-list
77
+ calls still provide Consumer evidence for their canonical GET forms.
73
78
  - repository: /home/tnzhu/projects/matelab-front
74
79
  commit: 3c356731e883f6035fc4b475b84982739bba4095
75
80
  files:
@@ -85,6 +90,11 @@ info:
85
90
  - src/views/recommend/fav.vue
86
91
  - src/views/recommend/favDetail.vue
87
92
  - src/views/recommend/favItems.vue
93
+ note: |
94
+ This Front commit reads environment/menu and identity/notifications
95
+ from `/actions/app`. The current Provider moved the latter fields out
96
+ of that route; only the still-callable application-bootstrap use case
97
+ defines the current `/actions/app` contract.
88
98
  x-record-attachment-staging:
89
99
  status: contract-contained
90
100
  producerOperationId: uploadRecordAttachment
@@ -132,8 +142,8 @@ info:
132
142
  operationArrays: [add]
133
143
  attachmentOperationSchema:
134
144
  $ref: "#/components/schemas/AppendFilesModuleStagedAttachmentOperation"
135
- folderPath: omitted
136
- readback: one-new-canonical-files-entry
145
+ folderPath: optional-non-empty-safe-segments
146
+ readback: one-new-canonical-files-entry-at-root-or-resolved-folder
137
147
  tableFileCellReplacement:
138
148
  operationArrays: [modify]
139
149
  attachmentOperationSchema:
@@ -227,15 +237,15 @@ tags:
227
237
  paths:
228
238
  /actions/app:
229
239
  get:
230
- operationId: resolveCurrentIdentity
240
+ operationId: readApplicationBootstrap
231
241
  tags: [Authentication]
232
- summary: Resolve the authenticated Matelab identity and application environment
242
+ summary: Read the authenticated Matelab application bootstrap
233
243
  description: |
234
- Read-only application bootstrap used by Matelab Front and the only
235
- `/actions/app` method in integration-v1. The MCP
236
- consumer uses `user.userid`, `user.username`, and required-nullable
237
- `user.email`; the remaining fields drive the Provider UI and are retained
238
- because they are part of the PHP wire response.
244
+ Read-only environment, product-name, AI and menu bootstrap used by
245
+ Matelab Front and the only `/actions/app` method in integration-v1.
246
+ The current Provider no longer returns `user` or `notifications` from
247
+ this route; authenticated identity is available from token issuance
248
+ flows and is not inferred from this response.
239
249
  security:
240
250
  - bearerAuth: []
241
251
  responses:
@@ -245,22 +255,16 @@ paths:
245
255
  application/json:
246
256
  schema:
247
257
  oneOf:
248
- - $ref: "#/components/schemas/AppIdentityResponse"
258
+ - $ref: "#/components/schemas/AppBootstrapResponse"
249
259
  - $ref: "#/components/schemas/ErrorResponse"
250
260
  examples:
251
261
  success:
252
- summary: Authenticated identity with no unread notifications
262
+ summary: Application bootstrap with enabled ELN navigation
253
263
  value:
254
264
  code: 0
255
265
  msg: ""
256
266
  errcode: 0
257
267
  errmsg: ""
258
- user:
259
- username: Example User
260
- avatar: null
261
- email: user@example.invalid
262
- userid: 101
263
- notifications: []
264
268
  env:
265
269
  ai: false
266
270
  smtp: true
@@ -292,22 +296,12 @@ paths:
292
296
  url: /db/eln
293
297
  order: 1
294
298
  nullable:
295
- summary: Identity fields backed by nullable user columns
299
+ summary: Application bootstrap with no licensed institute features
296
300
  value:
297
301
  code: 0
298
302
  msg: ""
299
303
  errcode: 0
300
304
  errmsg: ""
301
- user:
302
- username: null
303
- avatar: null
304
- email: null
305
- userid: 102
306
- notifications:
307
- - id: 501
308
- showtext: null
309
- datetime_add: "2026-01-02 08:00:00"
310
- datetime: "2026-01-02 08:00"
311
305
  env:
312
306
  ai: false
313
307
  smtp: false
@@ -1693,8 +1687,10 @@ paths:
1693
1687
  or replacing an exact table file cell, appending a table row and
1694
1688
  setting its file cell in the same request, appending one root files
1695
1689
  entry, and replacing one files entry by an immediately read index.
1696
- Files-module folder `path` is not part of the callable request shape
1697
- because the current Provider rejects valid path segments under PVD-044.
1690
+ Files-module folder `path` accepts a non-empty list of safe folder-name
1691
+ segments. PVD-044 remains because malformed or empty path containers are
1692
+ not reliably rejected by the Provider; those shapes stay outside the
1693
+ callable request schema.
1698
1694
  Form-file replacement remains unsafe under PVD-014. Richtext only
1699
1695
  recognizes hash-bearing `elnurl://` references; `#file{name}` is plain
1700
1696
  text there, while the hash path can fall back to an existing quote and
@@ -2572,7 +2568,7 @@ paths:
2572
2568
  uid: REC-EXAMPLE-001
2573
2569
  current_version: 70001
2574
2570
  item_id: 9001
2575
- datetime_locked: 0
2571
+ datetime_locked: null
2576
2572
  locked: false
2577
2573
  signs: []
2578
2574
  signed: false
@@ -4468,13 +4464,11 @@ components:
4468
4464
  filename: {type: string, minLength: 1}
4469
4465
  size: {type: integer, minimum: 0}
4470
4466
  hash: {$ref: "#/components/schemas/Sha256"}
4471
- AppIdentityResponse:
4467
+ AppBootstrapResponse:
4472
4468
  allOf:
4473
4469
  - $ref: "#/components/schemas/ResponseEnvelope"
4474
4470
  - type: object
4475
4471
  required:
4476
- - user
4477
- - notifications
4478
4472
  - env
4479
4473
  - ai_enable
4480
4474
  - ai_text
@@ -4488,13 +4482,6 @@ components:
4488
4482
  const: 0
4489
4483
  errcode:
4490
4484
  const: 0
4491
- user:
4492
- $ref: "#/components/schemas/AppUser"
4493
- notifications:
4494
- type: array
4495
- maxItems: 5
4496
- items:
4497
- $ref: "#/components/schemas/AppNotification"
4498
4485
  env:
4499
4486
  $ref: "#/components/schemas/AppEnvironment"
4500
4487
  ai_enable:
@@ -4516,36 +4503,6 @@ components:
4516
4503
  type: array
4517
4504
  items:
4518
4505
  $ref: "#/components/schemas/AppMenu"
4519
- AppUser:
4520
- type: object
4521
- required: [username, avatar, email, userid]
4522
- properties:
4523
- username:
4524
- type: [string, "null"]
4525
- description: Raw nullable `login_user.realname`.
4526
- avatar:
4527
- type: [string, "null"]
4528
- description: Raw id-photo path, normalized to null when empty.
4529
- email:
4530
- type: [string, "null"]
4531
- userid:
4532
- type: integer
4533
- minimum: 1
4534
- AppNotification:
4535
- type: object
4536
- required: [id, showtext, datetime_add, datetime]
4537
- properties:
4538
- id:
4539
- type: integer
4540
- minimum: 1
4541
- showtext:
4542
- type: [string, "null"]
4543
- datetime_add:
4544
- type: [string, "null"]
4545
- description: Raw database datetime retained by the PHP response.
4546
- datetime:
4547
- type: string
4548
- description: Provider-formatted `Y-m-d H:i`.
4549
4506
  AppEnvironment:
4550
4507
  type: object
4551
4508
  required:
@@ -5642,6 +5599,17 @@ components:
5642
5599
  required: [module, data]
5643
5600
  properties:
5644
5601
  module: {type: string, minLength: 1}
5602
+ path:
5603
+ type: array
5604
+ minItems: 1
5605
+ items:
5606
+ type: string
5607
+ minLength: 1
5608
+ maxLength: 245
5609
+ pattern: '^[^/:*?"<>\\|]+$'
5610
+ description: |
5611
+ Ordered folder names below the files-module root. Existing folders
5612
+ are reused by exact name and parent; missing folders are created.
5645
5613
  data:
5646
5614
  type: object
5647
5615
  required: [file, text]
@@ -5651,8 +5619,10 @@ components:
5651
5619
  additionalProperties: false
5652
5620
  additionalProperties: false
5653
5621
  description: |
5654
- Appends one staged attachment to the root of an existing files module.
5655
- Folder `path` is intentionally outside the verified shape.
5622
+ Appends one staged attachment to the root of an existing files module
5623
+ when `path` is absent, or below the resolved folder path when present.
5624
+ Empty and malformed path containers are intentionally outside the
5625
+ callable shape because the Provider treats some of them as root append.
5656
5626
  ReplaceFilesModuleStagedAttachmentOperation:
5657
5627
  type: object
5658
5628
  required: [path, data]
@@ -6557,9 +6527,12 @@ components:
6557
6527
  type: integer
6558
6528
  minimum: 1
6559
6529
  datetime_locked:
6560
- type: integer
6530
+ type: [integer, "null"]
6561
6531
  minimum: 0
6562
- description: Unix timestamp in milliseconds, or zero.
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.
6563
6536
  locked:
6564
6537
  type: boolean
6565
6538
  signs:
@@ -3,11 +3,11 @@
3
3
  schema_version: 1
4
4
  contract:
5
5
  line: integration-v1
6
- release: v0.4.3
6
+ release: v0.4.4
7
7
  operation_count: 71
8
8
 
9
9
  operations:
10
- - operation_id: resolveCurrentIdentity
10
+ - operation_id: readApplicationBootstrap
11
11
  method: GET
12
12
  path: /actions/app
13
13
  domain: authentication
@@ -15,8 +15,8 @@ operations:
15
15
  stability: not_applicable
16
16
  public_interface: null
17
17
  known_issues: []
18
- limitation: The SDK accepts only complete, integration-validated Sessions and does not bind bare external tokens.
19
- reopen_when: A Provider-independent SDK use case requires explicit identity re-resolution for an existing Session.
18
+ limitation: The Provider response is an application bootstrap model; SDK identity remains sourced from token issuance.
19
+ reopen_when: A Provider-independent SDK use case requires the Provider application environment or menu model.
20
20
 
21
21
  - operation_id: loginTokenSet
22
22
  method: POST
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "matelab-python-sdk"
7
- version = "0.1.0a20"
7
+ version = "0.1.0a21"
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.3"
14
+ OPENAPI_INFO_VERSION = "0.4.4"
15
15
 
16
16
 
17
17
  class ResponseEnvelope(WireModel):
@@ -380,32 +380,6 @@ class UploadAttachmentResponse(WireModel):
380
380
  hash: Sha256
381
381
 
382
382
 
383
- class AppUser(WireModel):
384
- username: Annotated[str | None, Field(...)]
385
- """
386
- Raw nullable `login_user.realname`.
387
- """
388
- avatar: Annotated[str | None, Field(...)]
389
- """
390
- Raw id-photo path, normalized to null when empty.
391
- """
392
- email: Annotated[str | None, Field(...)]
393
- userid: Annotated[StrictInt, Field(ge=1)]
394
-
395
-
396
- class AppNotification(WireModel):
397
- id: Annotated[StrictInt, Field(ge=1)]
398
- showtext: Annotated[str | None, Field(...)]
399
- datetime_add: Annotated[str | None, Field(...)]
400
- """
401
- Raw database datetime retained by the PHP response.
402
- """
403
- datetime: str
404
- """
405
- Provider-formatted `Y-m-d H:i`.
406
- """
407
-
408
-
409
383
  class AppEnvironment(WireModel):
410
384
  ai: StrictBool
411
385
  smtp: StrictBool
@@ -1572,6 +1546,10 @@ class AppendTableRowOperation(WireModel):
1572
1546
  row: Annotated[StrictInt, Field(ge=-1, le=-1)]
1573
1547
 
1574
1548
 
1549
+ class PathItem3(RootModel[str]):
1550
+ root: Annotated[str, Field(max_length=245, min_length=1, pattern='^[^/:*?"<>\\\\|]+$')]
1551
+
1552
+
1575
1553
  class Data(WireModel):
1576
1554
  model_config = ConfigDict(extra="forbid")
1577
1555
  file: StagedRecordAttachmentReference
@@ -1581,20 +1559,26 @@ class Data(WireModel):
1581
1559
  class AppendFilesModuleStagedAttachmentOperation(WireModel):
1582
1560
  model_config = ConfigDict(extra="forbid")
1583
1561
  module: Annotated[str, Field(min_length=1)]
1562
+ path: Annotated[list[PathItem3] | None, Field(min_length=1)] = None
1563
+ """
1564
+ Ordered folder names below the files-module root. Existing folders
1565
+ are reused by exact name and parent; missing folders are created.
1566
+
1567
+ """
1584
1568
  data: Data
1585
1569
 
1586
1570
 
1587
- class PathItem3(RootModel[str]):
1571
+ class PathItem4(RootModel[str]):
1588
1572
  root: Annotated[str, Field(min_length=1)]
1589
1573
 
1590
1574
 
1591
- class PathItem4(RootModel[StrictInt]):
1575
+ class PathItem5(RootModel[StrictInt]):
1592
1576
  root: Annotated[StrictInt, Field(ge=0)]
1593
1577
 
1594
1578
 
1595
1579
  class ReplaceFilesModuleStagedAttachmentOperation(WireModel):
1596
1580
  model_config = ConfigDict(extra="forbid")
1597
- path: tuple[PathItem3, PathItem4]
1581
+ path: tuple[PathItem4, PathItem5]
1598
1582
  data: Data
1599
1583
 
1600
1584
 
@@ -1605,18 +1589,18 @@ class AddEmptyFileTableColumnOperation(WireModel):
1605
1589
  type: Literal["file"]
1606
1590
 
1607
1591
 
1608
- class PathItem5(RootModel[str]):
1592
+ class PathItem6(RootModel[str]):
1609
1593
  root: Annotated[str, Field(min_length=1)]
1610
1594
 
1611
1595
 
1612
1596
  class DeleteWholeFileFormFieldOperation(WireModel):
1613
1597
  model_config = ConfigDict(extra="forbid")
1614
- path: tuple[PathItem5, PathItem5]
1598
+ path: tuple[PathItem6, PathItem6]
1615
1599
 
1616
1600
 
1617
1601
  class DeleteWholeFileTableColumnOperation(WireModel):
1618
1602
  model_config = ConfigDict(extra="forbid")
1619
- path: tuple[PathItem5, PathItem5]
1603
+ path: tuple[PathItem6, PathItem6]
1620
1604
 
1621
1605
 
1622
1606
  class User2(RootModel[StrictInt]):
@@ -3757,9 +3741,12 @@ class ItemViewResponse(WireModel):
3757
3741
  """
3758
3742
  current_version: Annotated[StrictInt, Field(ge=0)]
3759
3743
  item_id: Annotated[StrictInt, Field(ge=1)]
3760
- datetime_locked: Annotated[StrictInt, Field(ge=0)]
3744
+ datetime_locked: Annotated[StrictInt | None, Field(ge=0)]
3761
3745
  """
3762
- Unix timestamp in milliseconds, or zero.
3746
+ Unix timestamp in milliseconds; null for the current unlocked
3747
+ record. Provider paths that normalize the absent timestamp may
3748
+ return zero.
3749
+
3763
3750
  """
3764
3751
  locked: StrictBool
3765
3752
  signs: list[RecordSignature]
@@ -3991,7 +3978,7 @@ class ListCloudDriveFilesResponse(RootModel[CloudDriveFileListResponse | ErrorRe
3991
3978
  root: CloudDriveFileListResponse | ErrorResponse
3992
3979
 
3993
3980
 
3994
- class AppIdentityResponse(WireModel):
3981
+ class AppBootstrapResponse(WireModel):
3995
3982
  code: Annotated[StrictInt, Field(ge=0, le=0)]
3996
3983
  msg: str
3997
3984
  """
@@ -4011,8 +3998,6 @@ class AppIdentityResponse(WireModel):
4011
3998
  diagnostic field.
4012
3999
 
4013
4000
  """
4014
- user: AppUser
4015
- notifications: Annotated[list[AppNotification], Field(max_length=5)]
4016
4001
  env: AppEnvironment
4017
4002
  ai_enable: StrictBool
4018
4003
  ai_text: Annotated[list[str], Field(min_length=1)]
@@ -4102,8 +4087,8 @@ class LiteratureContent(RootModel[LiteratureContentObject | LiteratureContent1])
4102
4087
  root: LiteratureContentObject | LiteratureContent1
4103
4088
 
4104
4089
 
4105
- class ResolveCurrentIdentityResponse(RootModel[AppIdentityResponse | ErrorResponse]):
4106
- root: AppIdentityResponse | ErrorResponse
4090
+ class ReadApplicationBootstrapResponse(RootModel[AppBootstrapResponse | ErrorResponse]):
4091
+ root: AppBootstrapResponse | ErrorResponse
4107
4092
 
4108
4093
 
4109
4094
  class ListNotebooksResponse(RootModel[NotebookListResponse | ErrorResponse]):
@@ -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": 0,
91
+ "datetime_locked": None,
92
92
  "locked": False,
93
93
  "signs": [],
94
94
  "signed": False,
@@ -106,6 +106,8 @@ def test_generated_response_preserves_required_nullable_fields() -> None:
106
106
  )
107
107
  assert ItemViewResponse.model_fields["keywords"].is_required()
108
108
  assert response.keywords is None
109
+ assert ItemViewResponse.model_fields["datetime_locked"].is_required()
110
+ assert response.datetime_locked is None
109
111
 
110
112
 
111
113
  def _item_view_payload(modules: list[object]) -> dict[str, object]:
@@ -435,6 +437,10 @@ def test_generated_v012_attachment_write_shapes_are_strict_and_dedicated() -> No
435
437
  "AppendFilesModuleStagedAttachmentOperation",
436
438
  {"module": "Files", "data": {"file": staged, "text": "Evidence"}},
437
439
  ),
440
+ (
441
+ "AppendFilesModuleStagedAttachmentOperation",
442
+ {"module": "Files", "path": ["Evidence", "Raw Data"], "data": {"file": staged, "text": "Evidence"}},
443
+ ),
438
444
  (
439
445
  "ReplaceFilesModuleStagedAttachmentOperation",
440
446
  {"path": ["Files", 0], "data": {"file": staged, "text": "Replacement"}},
@@ -445,7 +451,7 @@ def test_generated_v012_attachment_write_shapes_are_strict_and_dedicated() -> No
445
451
  )
446
452
  for model_name, payload in valid_shapes:
447
453
  model = cast(type[BaseModel], getattr(wire_models, model_name))
448
- assert model.model_validate(payload).model_dump(mode="json") == payload
454
+ assert model.model_validate(payload).model_dump(mode="json", exclude_none=True) == payload
449
455
 
450
456
  invalid_shapes: tuple[tuple[str, dict[str, object]], ...] = (
451
457
  ("SetTableFileCellWithStagedAttachmentOperation", {"path": ["Measurements", "Evidence"], "data": staged}),
@@ -454,6 +460,26 @@ def test_generated_v012_attachment_write_shapes_are_strict_and_dedicated() -> No
454
460
  "AppendFilesModuleStagedAttachmentOperation",
455
461
  {"module": "Files", "data": {"file": "#hash{file, hash}", "text": "Evidence"}},
456
462
  ),
463
+ (
464
+ "AppendFilesModuleStagedAttachmentOperation",
465
+ {"module": "Files", "path": [], "data": {"file": staged, "text": "Evidence"}},
466
+ ),
467
+ (
468
+ "AppendFilesModuleStagedAttachmentOperation",
469
+ {"module": "Files", "path": ["Folder/Child"], "data": {"file": staged, "text": "Evidence"}},
470
+ ),
471
+ (
472
+ "AppendFilesModuleStagedAttachmentOperation",
473
+ {"module": "Files", "path": [""], "data": {"file": staged, "text": "Evidence"}},
474
+ ),
475
+ (
476
+ "AppendFilesModuleStagedAttachmentOperation",
477
+ {"module": "Files", "path": ["x" * 246], "data": {"file": staged, "text": "Evidence"}},
478
+ ),
479
+ (
480
+ "AppendFilesModuleStagedAttachmentOperation",
481
+ {"module": "Files", "path": [1], "data": {"file": staged, "text": "Evidence"}},
482
+ ),
457
483
  (
458
484
  "AddEmptyFileTableColumnOperation",
459
485
  {"module": "Measurements", "name": "Evidence", "type": "file", "data": []},
@@ -466,6 +492,18 @@ def test_generated_v012_attachment_write_shapes_are_strict_and_dedicated() -> No
466
492
  _ = model.model_validate(payload)
467
493
 
468
494
 
495
+ def test_update_record_request_still_excludes_dedicated_files_folder_path() -> None:
496
+ operation = {
497
+ "module": "Files",
498
+ "path": ["Evidence", "Raw Data"],
499
+ "data": {"file": "#file{staged-example}", "text": "Evidence"},
500
+ }
501
+ _ = wire_models.AppendFilesModuleStagedAttachmentOperation.model_validate(operation)
502
+
503
+ with pytest.raises(ValidationError):
504
+ _ = wire_models.UpdateRecordRequest.model_validate({"eln": "Example", "uid": "REC-001", "add": [operation]})
505
+
506
+
469
507
  def test_generated_models_cover_every_component_schema_and_non_streaming_operation() -> None:
470
508
  document = _load_openapi()
471
509
  components = cast(dict[str, Any], document["components"])
@@ -485,7 +523,7 @@ def test_generated_models_cover_every_component_schema_and_non_streaming_operati
485
523
  }
486
524
  assert expected_response_models <= class_names
487
525
  assert len(operation_ids) == 71
488
- assert wire_models.OPENAPI_INFO_VERSION == "0.4.3"
526
+ assert wire_models.OPENAPI_INFO_VERSION == "0.4.4"
489
527
 
490
528
 
491
529
  def test_generation_is_deterministic(tmp_path: Path) -> None:
@@ -597,8 +635,8 @@ def test_contract_lock_is_the_single_release_pin() -> None:
597
635
  openapi = cast(dict[str, Any], lock["openapi"])
598
636
  assert set(release) == {"tag", "commit"}
599
637
  assert set(openapi) == {"source_path", "snapshot", "sha256"}
600
- assert release == {"tag": "v0.4.3", "commit": "7965ac9803505ae35dc5ce36f52afc3c2c133c45"}
601
- assert openapi["sha256"] == "6576812dd13f92ad58131f4edb8dc919d1b1fe0f19a695c2d25fbe6d13a8445b"
638
+ assert release == {"tag": "v0.4.4", "commit": "be070c5f931b2063327a3beaccc53a360204b64e"}
639
+ assert openapi["sha256"] == "2de38e46d3814b8b049af8afd18e85fb20be6ca29cc409390d25df663abc43f9"
602
640
  assert re.fullmatch(r"[0-9a-f]{40}", cast(str, release["commit"]))
603
641
  assert re.fullmatch(r"[0-9a-f]{64}", cast(str, openapi["sha256"]))
604
642
  assert not (ROOT / "contracts" / "matelab-integration-v1.sha256").exists()
@@ -149,5 +149,5 @@ def test_completed_coverage_has_only_intentionally_unexposed_operations() -> Non
149
149
 
150
150
  assert non_implemented == {
151
151
  "readLiteratureCreateTemplate": "intentionally_unexposed",
152
- "resolveCurrentIdentity": "intentionally_unexposed",
152
+ "readApplicationBootstrap": "intentionally_unexposed",
153
153
  }
@@ -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=0,
246
+ datetime_locked=None,
247
247
  locked=False,
248
248
  signs=[
249
249
  {
@@ -352,7 +352,7 @@ wheels = [
352
352
 
353
353
  [[package]]
354
354
  name = "matelab-python-sdk"
355
- version = "0.1.0a20"
355
+ version = "0.1.0a21"
356
356
  source = { editable = "." }
357
357
  dependencies = [
358
358
  { name = "httpx2" },