saprfclib 0.1.0__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 (221) hide show
  1. saprfclib-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +68 -0
  2. saprfclib-0.1.0/.github/ISSUE_TEMPLATE/config.yml +8 -0
  3. saprfclib-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +24 -0
  4. saprfclib-0.1.0/.github/ISSUE_TEMPLATE/protocol_gap.yml +50 -0
  5. saprfclib-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +26 -0
  6. saprfclib-0.1.0/.github/dependabot.yml +17 -0
  7. saprfclib-0.1.0/.github/workflows/ci.yml +72 -0
  8. saprfclib-0.1.0/.github/workflows/docs.yml +52 -0
  9. saprfclib-0.1.0/.github/workflows/publish.yml +109 -0
  10. saprfclib-0.1.0/.github/workflows/security.yml +102 -0
  11. saprfclib-0.1.0/.gitignore +189 -0
  12. saprfclib-0.1.0/CHANGELOG.md +59 -0
  13. saprfclib-0.1.0/CLAUDE.md +206 -0
  14. saprfclib-0.1.0/CONTRIBUTING.md +196 -0
  15. saprfclib-0.1.0/LICENSE +373 -0
  16. saprfclib-0.1.0/NOTICE +48 -0
  17. saprfclib-0.1.0/PKG-INFO +181 -0
  18. saprfclib-0.1.0/README.md +144 -0
  19. saprfclib-0.1.0/SECURITY.md +52 -0
  20. saprfclib-0.1.0/conftest.py +15 -0
  21. saprfclib-0.1.0/docs/api/codec.md +5 -0
  22. saprfclib-0.1.0/docs/api/connect.md +5 -0
  23. saprfclib-0.1.0/docs/api/connection-pool.md +3 -0
  24. saprfclib-0.1.0/docs/api/connection.md +16 -0
  25. saprfclib-0.1.0/docs/api/exceptions.md +17 -0
  26. saprfclib-0.1.0/docs/api/rfc-server.md +3 -0
  27. saprfclib-0.1.0/docs/api/stores.md +11 -0
  28. saprfclib-0.1.0/docs/cookbook/connection-pool.md +58 -0
  29. saprfclib-0.1.0/docs/cookbook/index.md +13 -0
  30. saprfclib-0.1.0/docs/cookbook/rfc-server.md +96 -0
  31. saprfclib-0.1.0/docs/cookbook/snc-connection.md +53 -0
  32. saprfclib-0.1.0/docs/cookbook/table-parameters.md +79 -0
  33. saprfclib-0.1.0/docs/cookbook/trfc-submit.md +93 -0
  34. saprfclib-0.1.0/docs/cookbook/websocket-rfc.md +72 -0
  35. saprfclib-0.1.0/docs/getting-started/connection-options.md +114 -0
  36. saprfclib-0.1.0/docs/getting-started/error-handling.md +156 -0
  37. saprfclib-0.1.0/docs/getting-started/installation.md +46 -0
  38. saprfclib-0.1.0/docs/getting-started/quick-start.md +77 -0
  39. saprfclib-0.1.0/docs/index.md +36 -0
  40. saprfclib-0.1.0/docs/protocol/.gitkeep +0 -0
  41. saprfclib-0.1.0/docs/protocol/framing.md +792 -0
  42. saprfclib-0.1.0/docs/protocol/handshake.md +328 -0
  43. saprfclib-0.1.0/docs/protocol/methodology.md +213 -0
  44. saprfclib-0.1.0/docs/protocol/serialization.md +658 -0
  45. saprfclib-0.1.0/docs/protocol/snc.md +252 -0
  46. saprfclib-0.1.0/docs/protocol/trfc.md +327 -0
  47. saprfclib-0.1.0/examples/01_connect_and_call.py +42 -0
  48. saprfclib-0.1.0/examples/02_table_params.py +47 -0
  49. saprfclib-0.1.0/examples/03_connection_pool.py +53 -0
  50. saprfclib-0.1.0/examples/04_trfc_submit.py +66 -0
  51. saprfclib-0.1.0/examples/05_rfc_server.py +96 -0
  52. saprfclib-0.1.0/examples/06_snc_connection.py +59 -0
  53. saprfclib-0.1.0/examples/07_websocket_rfc.py +65 -0
  54. saprfclib-0.1.0/mkdocs.yml +101 -0
  55. saprfclib-0.1.0/pyproject.toml +116 -0
  56. saprfclib-0.1.0/src/saprfclib/__init__.py +116 -0
  57. saprfclib-0.1.0/src/saprfclib/_version.py +24 -0
  58. saprfclib-0.1.0/src/saprfclib/codec.py +562 -0
  59. saprfclib-0.1.0/src/saprfclib/compress.py +854 -0
  60. saprfclib-0.1.0/src/saprfclib/connection.py +4620 -0
  61. saprfclib-0.1.0/src/saprfclib/exceptions.py +282 -0
  62. saprfclib-0.1.0/src/saprfclib/invoke.py +874 -0
  63. saprfclib-0.1.0/src/saprfclib/metadata.py +329 -0
  64. saprfclib-0.1.0/src/saprfclib/pool.py +476 -0
  65. saprfclib-0.1.0/src/saprfclib/py.typed +0 -0
  66. saprfclib-0.1.0/src/saprfclib/router.py +548 -0
  67. saprfclib-0.1.0/src/saprfclib/server.py +1723 -0
  68. saprfclib-0.1.0/src/saprfclib/server_session.py +333 -0
  69. saprfclib-0.1.0/src/saprfclib/session.py +423 -0
  70. saprfclib-0.1.0/src/saprfclib/snc.py +1025 -0
  71. saprfclib-0.1.0/src/saprfclib/stores.py +888 -0
  72. saprfclib-0.1.0/src/saprfclib/transport.py +231 -0
  73. saprfclib-0.1.0/src/saprfclib/types.py +84 -0
  74. saprfclib-0.1.0/src/saprfclib/ws.py +632 -0
  75. saprfclib-0.1.0/tests/__init__.py +0 -0
  76. saprfclib-0.1.0/tests/_mocks.py +77 -0
  77. saprfclib-0.1.0/tests/conftest.py +117 -0
  78. saprfclib-0.1.0/tests/golden/__init__.py +0 -0
  79. saprfclib-0.1.0/tests/golden/framing/.gitkeep +0 -0
  80. saprfclib-0.1.0/tests/golden/framing/__init__.py +0 -0
  81. saprfclib-0.1.0/tests/golden/framing/logon_request.bin +0 -0
  82. saprfclib-0.1.0/tests/golden/framing/logon_request.json +62 -0
  83. saprfclib-0.1.0/tests/golden/framing/rfc_read_table_request.bin +0 -0
  84. saprfclib-0.1.0/tests/golden/framing/rfc_read_table_response.bin +0 -0
  85. saprfclib-0.1.0/tests/golden/framing/server_inbound_call_request.bin +0 -0
  86. saprfclib-0.1.0/tests/golden/framing/server_inbound_call_request.json +43 -0
  87. saprfclib-0.1.0/tests/golden/framing/server_ni_init_client.bin +0 -0
  88. saprfclib-0.1.0/tests/golden/framing/server_ni_init_gateway.bin +0 -0
  89. saprfclib-0.1.0/tests/golden/framing/server_registration_ack.bin +0 -0
  90. saprfclib-0.1.0/tests/golden/framing/server_registration_request.bin +0 -0
  91. saprfclib-0.1.0/tests/golden/framing/server_registration_request.json +181 -0
  92. saprfclib-0.1.0/tests/golden/framing/server_response.bin +0 -0
  93. saprfclib-0.1.0/tests/golden/framing/server_response.json +45 -0
  94. saprfclib-0.1.0/tests/golden/framing/stfc_changing_request.bin +0 -0
  95. saprfclib-0.1.0/tests/golden/framing/stfc_changing_response.bin +0 -0
  96. saprfclib-0.1.0/tests/golden/framing/stfc_connection_request.bin +0 -0
  97. saprfclib-0.1.0/tests/golden/framing/stfc_connection_request.json +153 -0
  98. saprfclib-0.1.0/tests/golden/framing/stfc_connection_response.bin +0 -0
  99. saprfclib-0.1.0/tests/golden/framing/stfc_connection_response.json +194 -0
  100. saprfclib-0.1.0/tests/golden/framing/stfc_deep_table_request.bin +0 -0
  101. saprfclib-0.1.0/tests/golden/framing/stfc_deep_table_response.bin +0 -0
  102. saprfclib-0.1.0/tests/golden/framing/stfc_exception_response.bin +0 -0
  103. saprfclib-0.1.0/tests/golden/framing/stfc_structure_request.bin +0 -0
  104. saprfclib-0.1.0/tests/golden/framing/stfc_structure_response.bin +0 -0
  105. saprfclib-0.1.0/tests/golden/framing/trfc_call_request.bin +0 -0
  106. saprfclib-0.1.0/tests/golden/framing/trfc_call_request.json +84 -0
  107. saprfclib-0.1.0/tests/golden/handshake/.gitkeep +0 -0
  108. saprfclib-0.1.0/tests/golden/handshake/__init__.py +0 -0
  109. saprfclib-0.1.0/tests/golden/handshake/gw_connect_response.bin +0 -0
  110. saprfclib-0.1.0/tests/golden/handshake/gw_connect_response.json +76 -0
  111. saprfclib-0.1.0/tests/golden/handshake/gw_done_client.bin +0 -0
  112. saprfclib-0.1.0/tests/golden/handshake/gw_done_client.json +75 -0
  113. saprfclib-0.1.0/tests/golden/handshake/gw_done_server.bin +0 -0
  114. saprfclib-0.1.0/tests/golden/handshake/gw_done_server.json +77 -0
  115. saprfclib-0.1.0/tests/golden/handshake/ni_version_request.bin +0 -0
  116. saprfclib-0.1.0/tests/golden/handshake/ni_version_request.json +135 -0
  117. saprfclib-0.1.0/tests/golden/handshake/ni_version_response.bin +0 -0
  118. saprfclib-0.1.0/tests/golden/handshake/ni_version_response.json +135 -0
  119. saprfclib-0.1.0/tests/golden/handshake/stfc_connection_handshake.bin +0 -0
  120. saprfclib-0.1.0/tests/golden/handshake/stfc_connection_handshake.json +226 -0
  121. saprfclib-0.1.0/tests/golden/handshake/test_handshake.py +38 -0
  122. saprfclib-0.1.0/tests/golden/router/ni_route_payload.bin +0 -0
  123. saprfclib-0.1.0/tests/golden/router/sapms_server_list.bin +0 -0
  124. saprfclib-0.1.0/tests/golden/router/sapms_server_list.json +219 -0
  125. saprfclib-0.1.0/tests/golden/serialization/.gitkeep +0 -0
  126. saprfclib-0.1.0/tests/golden/serialization/__init__.py +0 -0
  127. saprfclib-0.1.0/tests/golden/serialization/_replay.py +104 -0
  128. saprfclib-0.1.0/tests/golden/serialization/test_codec_roundtrip.py +306 -0
  129. saprfclib-0.1.0/tests/golden/serialization/test_serialization.py +56 -0
  130. saprfclib-0.1.0/tests/golden/serialization/type_bcd.bin +1 -0
  131. saprfclib-0.1.0/tests/golden/serialization/type_bcd.json +43 -0
  132. saprfclib-0.1.0/tests/golden/serialization/type_bcd_p15_2.bin +0 -0
  133. saprfclib-0.1.0/tests/golden/serialization/type_bcd_p15_2.json +84 -0
  134. saprfclib-0.1.0/tests/golden/serialization/type_byte.bin +1 -0
  135. saprfclib-0.1.0/tests/golden/serialization/type_byte.json +25 -0
  136. saprfclib-0.1.0/tests/golden/serialization/type_cday.bin +0 -0
  137. saprfclib-0.1.0/tests/golden/serialization/type_cday.json +24 -0
  138. saprfclib-0.1.0/tests/golden/serialization/type_char.bin +0 -0
  139. saprfclib-0.1.0/tests/golden/serialization/type_char.json +33 -0
  140. saprfclib-0.1.0/tests/golden/serialization/type_date.bin +0 -0
  141. saprfclib-0.1.0/tests/golden/serialization/type_date.json +81 -0
  142. saprfclib-0.1.0/tests/golden/serialization/type_decf16_GAP.json +19 -0
  143. saprfclib-0.1.0/tests/golden/serialization/type_dtday.bin +0 -0
  144. saprfclib-0.1.0/tests/golden/serialization/type_dtday.json +24 -0
  145. saprfclib-0.1.0/tests/golden/serialization/type_dtmonth.bin +0 -0
  146. saprfclib-0.1.0/tests/golden/serialization/type_dtmonth.json +24 -0
  147. saprfclib-0.1.0/tests/golden/serialization/type_dtweek.bin +0 -0
  148. saprfclib-0.1.0/tests/golden/serialization/type_dtweek.json +24 -0
  149. saprfclib-0.1.0/tests/golden/serialization/type_float.bin +1 -0
  150. saprfclib-0.1.0/tests/golden/serialization/type_float.json +25 -0
  151. saprfclib-0.1.0/tests/golden/serialization/type_int1.bin +1 -0
  152. saprfclib-0.1.0/tests/golden/serialization/type_int1.json +24 -0
  153. saprfclib-0.1.0/tests/golden/serialization/type_int2.bin +0 -0
  154. saprfclib-0.1.0/tests/golden/serialization/type_int2.json +24 -0
  155. saprfclib-0.1.0/tests/golden/serialization/type_int4.bin +0 -0
  156. saprfclib-0.1.0/tests/golden/serialization/type_int4.json +23 -0
  157. saprfclib-0.1.0/tests/golden/serialization/type_int8.bin +0 -0
  158. saprfclib-0.1.0/tests/golden/serialization/type_int8.json +24 -0
  159. saprfclib-0.1.0/tests/golden/serialization/type_num.bin +0 -0
  160. saprfclib-0.1.0/tests/golden/serialization/type_num.json +50 -0
  161. saprfclib-0.1.0/tests/golden/serialization/type_string.bin +0 -0
  162. saprfclib-0.1.0/tests/golden/serialization/type_string.json +66 -0
  163. saprfclib-0.1.0/tests/golden/serialization/type_structure.bin +0 -0
  164. saprfclib-0.1.0/tests/golden/serialization/type_structure.json +61 -0
  165. saprfclib-0.1.0/tests/golden/serialization/type_table.bin +0 -0
  166. saprfclib-0.1.0/tests/golden/serialization/type_table.json +78 -0
  167. saprfclib-0.1.0/tests/golden/serialization/type_time.bin +0 -0
  168. saprfclib-0.1.0/tests/golden/serialization/type_time.json +65 -0
  169. saprfclib-0.1.0/tests/golden/serialization/type_tminute.bin +0 -0
  170. saprfclib-0.1.0/tests/golden/serialization/type_tminute.json +24 -0
  171. saprfclib-0.1.0/tests/golden/serialization/type_tsecond.bin +0 -0
  172. saprfclib-0.1.0/tests/golden/serialization/type_tsecond.json +24 -0
  173. saprfclib-0.1.0/tests/golden/serialization/type_utclong.bin +0 -0
  174. saprfclib-0.1.0/tests/golden/serialization/type_utclong.json +24 -0
  175. saprfclib-0.1.0/tests/golden/serialization/type_utcminute.bin +0 -0
  176. saprfclib-0.1.0/tests/golden/serialization/type_utcminute.json +24 -0
  177. saprfclib-0.1.0/tests/golden/serialization/type_utcsecond.bin +0 -0
  178. saprfclib-0.1.0/tests/golden/serialization/type_utcsecond.json +24 -0
  179. saprfclib-0.1.0/tests/golden/serialization/type_xstring.bin +0 -0
  180. saprfclib-0.1.0/tests/golden/serialization/type_xstring.json +34 -0
  181. saprfclib-0.1.0/tests/golden/synthetic/__init__.py +0 -0
  182. saprfclib-0.1.0/tests/golden/synthetic/ni_header_synthetic.bin +0 -0
  183. saprfclib-0.1.0/tests/golden/synthetic/ni_header_synthetic.json +28 -0
  184. saprfclib-0.1.0/tests/golden/synthetic/test_synthetic.py +21 -0
  185. saprfclib-0.1.0/tests/golden/test_fixtures.py +56 -0
  186. saprfclib-0.1.0/tests/test_codec.py +536 -0
  187. saprfclib-0.1.0/tests/test_compress.py +338 -0
  188. saprfclib-0.1.0/tests/test_connection.py +1166 -0
  189. saprfclib-0.1.0/tests/test_exceptions.py +162 -0
  190. saprfclib-0.1.0/tests/test_invoke.py +661 -0
  191. saprfclib-0.1.0/tests/test_metadata.py +319 -0
  192. saprfclib-0.1.0/tests/test_packaging.py +65 -0
  193. saprfclib-0.1.0/tests/test_phase03_integration.py +264 -0
  194. saprfclib-0.1.0/tests/test_phase04_integration.py +479 -0
  195. saprfclib-0.1.0/tests/test_phase05_integration.py +296 -0
  196. saprfclib-0.1.0/tests/test_phase05_pool.py +261 -0
  197. saprfclib-0.1.0/tests/test_phase05_server.py +360 -0
  198. saprfclib-0.1.0/tests/test_phase06_bgrfc.py +224 -0
  199. saprfclib-0.1.0/tests/test_phase06_integration.py +408 -0
  200. saprfclib-0.1.0/tests/test_phase06_server.py +240 -0
  201. saprfclib-0.1.0/tests/test_phase06_stores.py +373 -0
  202. saprfclib-0.1.0/tests/test_phase06_trfc.py +239 -0
  203. saprfclib-0.1.0/tests/test_phase07_snc_integration.py +99 -0
  204. saprfclib-0.1.0/tests/test_phase07_wrfc_integration.py +206 -0
  205. saprfclib-0.1.0/tests/test_phase09_async_client.py +246 -0
  206. saprfclib-0.1.0/tests/test_phase09_async_pool.py +152 -0
  207. saprfclib-0.1.0/tests/test_phase09_async_server.py +232 -0
  208. saprfclib-0.1.0/tests/test_phase09_integration.py +339 -0
  209. saprfclib-0.1.0/tests/test_phase09_retry.py +414 -0
  210. saprfclib-0.1.0/tests/test_phase09_sqlite_store.py +348 -0
  211. saprfclib-0.1.0/tests/test_router.py +373 -0
  212. saprfclib-0.1.0/tests/test_session.py +158 -0
  213. saprfclib-0.1.0/tests/test_snc_frame.py +133 -0
  214. saprfclib-0.1.0/tests/test_snc_gss_binding.py +176 -0
  215. saprfclib-0.1.0/tests/test_snc_transport.py +428 -0
  216. saprfclib-0.1.0/tests/test_transport.py +169 -0
  217. saprfclib-0.1.0/tests/test_types.py +133 -0
  218. saprfclib-0.1.0/tests/test_wrfc_invoke.py +142 -0
  219. saprfclib-0.1.0/tests/test_ws_frames.py +206 -0
  220. saprfclib-0.1.0/tests/test_ws_proxy.py +84 -0
  221. saprfclib-0.1.0/tests/test_ws_upgrade.py +147 -0
@@ -0,0 +1,68 @@
1
+ name: Bug report
2
+ description: Something behaves incorrectly against an SAP system
3
+ labels: ["bug"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ **Never paste real credentials, real hostnames, customer data, or an unsanitised
9
+ pcap.** Redact before posting. If this is a security issue, do not open an issue —
10
+ see SECURITY.md.
11
+
12
+ - type: input
13
+ id: version
14
+ attributes:
15
+ label: saprfclib version
16
+ placeholder: "0.1.0 (python -c 'import saprfclib; print(saprfclib.__version__)')"
17
+ validations:
18
+ required: true
19
+
20
+ - type: input
21
+ id: python
22
+ attributes:
23
+ label: Python version and OS
24
+ placeholder: "3.13.1 on Ubuntu 24.04"
25
+ validations:
26
+ required: true
27
+
28
+ - type: input
29
+ id: sap
30
+ attributes:
31
+ label: SAP system release
32
+ placeholder: "SAP NW 7.58 / S/4HANA 2023 / BTP ABAP environment"
33
+
34
+ - type: dropdown
35
+ id: transport
36
+ attributes:
37
+ label: Connection mode
38
+ options:
39
+ - Direct TCP (ashost/sysnr)
40
+ - SAProuter
41
+ - Message server (group logon)
42
+ - SNC
43
+ - WebSocket RFC (BTP)
44
+ - RFC server (inbound)
45
+ validations:
46
+ required: true
47
+
48
+ - type: textarea
49
+ id: what
50
+ attributes:
51
+ label: What happened
52
+ description: Include the full traceback if there is one, with credentials redacted.
53
+ validations:
54
+ required: true
55
+
56
+ - type: textarea
57
+ id: expected
58
+ attributes:
59
+ label: What you expected
60
+ validations:
61
+ required: true
62
+
63
+ - type: textarea
64
+ id: repro
65
+ attributes:
66
+ label: Reproduction
67
+ description: Smallest snippet that shows the problem, with placeholders for connection parameters.
68
+ render: python
@@ -0,0 +1,8 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Security vulnerability
4
+ url: https://github.com/randomstr1ng/saprfclib/security/advisories/new
5
+ about: Report privately. Never open a public issue for a security problem.
6
+ - name: Documentation
7
+ url: https://randomstr1ng.github.io/saprfclib/
8
+ about: Getting started, cookbook, API reference, and wire-protocol docs.
@@ -0,0 +1,24 @@
1
+ name: Feature request
2
+ description: Propose new functionality
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: What problem does this solve
9
+ validations:
10
+ required: true
11
+
12
+ - type: textarea
13
+ id: proposal
14
+ attributes:
15
+ label: Proposed API or behaviour
16
+ render: python
17
+
18
+ - type: checkboxes
19
+ id: constraints
20
+ attributes:
21
+ label: Constraints
22
+ options:
23
+ - label: This can be implemented in pure Python with no new native dependency
24
+ required: true
@@ -0,0 +1,50 @@
1
+ name: Protocol gap or wrong wire behaviour
2
+ description: A field, type, or frame is unsupported, mis-parsed, or mis-serialized
3
+ labels: ["protocol"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ This project does not accept guessed wire values. A report that includes observed
9
+ bytes can be acted on; one that does not usually cannot. See CLAUDE.md for the
10
+ evidence tiers.
11
+
12
+ **Sanitise captures before attaching them** — no credentials, no real hostnames,
13
+ no customer data.
14
+
15
+ - type: textarea
16
+ id: area
17
+ attributes:
18
+ label: Which part of the protocol
19
+ placeholder: "e.g. DecFloat34 decode, tRFC TID format, 0x0514 session token"
20
+ validations:
21
+ required: true
22
+
23
+ - type: textarea
24
+ id: observed
25
+ attributes:
26
+ label: Observed bytes
27
+ description: Hex dump of the frame or field as seen on the wire, with offsets if known.
28
+ render: text
29
+
30
+ - type: textarea
31
+ id: expected
32
+ attributes:
33
+ label: What saprfclib produced or expected instead
34
+
35
+ - type: dropdown
36
+ id: evidence
37
+ attributes:
38
+ label: Evidence tier
39
+ options:
40
+ - Live capture (pcap from my own system)
41
+ - Behavioural probe (server accepted/rejected)
42
+ - Inference from a neighbouring confirmed field
43
+ validations:
44
+ required: true
45
+
46
+ - type: input
47
+ id: sap
48
+ attributes:
49
+ label: SAP system release
50
+ placeholder: "SAP NW 7.58 / S/4HANA 2023"
@@ -0,0 +1,26 @@
1
+ ## What this changes
2
+
3
+ <!-- One or two sentences. -->
4
+
5
+ ## Evidence (required for any wire-behaviour change)
6
+
7
+ <!--
8
+ State the source for every wire value you added or changed. See CLAUDE.md.
9
+ Delete this section only if the PR touches no protocol behaviour.
10
+ -->
11
+
12
+ - [ ] Live capture / golden fixture — fixture path:
13
+ - [ ] Behavioural probe against a live system — describe:
14
+ - [ ] Inference from a confirmed field — labelled `[ASSUMED]` in code and docs
15
+
16
+ ## Checklist
17
+
18
+ - [ ] `hatch run test -m "not integration"` passes
19
+ - [ ] `hatch run lint:check` passes
20
+ - [ ] `hatch run lint:type` passes
21
+ - [ ] No golden fixture was edited to make a test pass
22
+ - [ ] No new non-Python runtime dependency
23
+ - [ ] No SAP source, headers, binaries, or decompiler output added
24
+ - [ ] `docs/protocol/` updated if wire behaviour changed
25
+ - [ ] `CHANGELOG.md` updated under "Unreleased"
26
+ - [ ] New source files carry the MPL-2.0 header
@@ -0,0 +1,17 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ day: "monday"
8
+ open-pull-requests-limit: 5
9
+ labels: ["dependencies"]
10
+
11
+ - package-ecosystem: "github-actions"
12
+ directory: "/"
13
+ schedule:
14
+ interval: "weekly"
15
+ day: "monday"
16
+ open-pull-requests-limit: 5
17
+ labels: ["dependencies", "ci"]
@@ -0,0 +1,72 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+ branches: ["main"]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ci-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ python-version: ["3.12", "3.13"]
23
+
24
+ steps:
25
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26
+ with:
27
+ fetch-depth: 0 # required for hatch-vcs git-tag version derivation
28
+ persist-credentials: false
29
+
30
+ - name: Set up Python ${{ matrix.python-version }}
31
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
32
+ with:
33
+ python-version: ${{ matrix.python-version }}
34
+
35
+ - name: Install Hatch
36
+ run: pip install hatch "hatchling>=1.21,<1.32"
37
+
38
+ - name: Lint (Ruff)
39
+ run: hatch run lint:check
40
+
41
+ - name: Type-check (mypy)
42
+ run: hatch run lint:type
43
+
44
+ - name: Test (pytest — offline only, no live SAP)
45
+ run: hatch run test -m "not integration"
46
+
47
+ - name: Build distribution
48
+ run: hatch build
49
+
50
+ - name: Verify importable wheel
51
+ run: |
52
+ pip install dist/*.whl --force-reinstall
53
+ python -c "import saprfclib; print(saprfclib.__version__)"
54
+
55
+ docs-build:
56
+ runs-on: ubuntu-latest
57
+ steps:
58
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
59
+ with:
60
+ fetch-depth: 0
61
+ persist-credentials: false
62
+
63
+ - name: Set up Python
64
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
65
+ with:
66
+ python-version: "3.12"
67
+
68
+ - name: Install Hatch
69
+ run: pip install hatch "hatchling>=1.21,<1.32"
70
+
71
+ - name: Build docs (strict)
72
+ run: hatch run docs:build
@@ -0,0 +1,52 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: pages
13
+ cancel-in-progress: false
14
+
15
+ jobs:
16
+ build:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
20
+ with:
21
+ fetch-depth: 0
22
+ persist-credentials: false
23
+
24
+ - name: Set up Python
25
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
26
+ with:
27
+ python-version: "3.12"
28
+
29
+ - name: Install Hatch
30
+ run: pip install hatch "hatchling>=1.21,<1.32"
31
+
32
+ - name: Build docs
33
+ run: hatch run docs:build
34
+
35
+ - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
36
+
37
+ - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
38
+ with:
39
+ path: ./site
40
+
41
+ deploy:
42
+ needs: build
43
+ runs-on: ubuntu-latest
44
+ environment:
45
+ name: github-pages
46
+ url: ${{ steps.deployment.outputs.page_url }}
47
+ permissions:
48
+ pages: write
49
+ id-token: write
50
+ steps:
51
+ - id: deployment
52
+ uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
@@ -0,0 +1,109 @@
1
+ name: Publish
2
+
3
+ # Tag-driven release. hatch-vcs derives the version from the tag, so the tag is the
4
+ # single source of truth — never hand-edit a version string.
5
+ #
6
+ # git tag v0.2.0 && git push origin v0.2.0
7
+ #
8
+ # Uploads use PyPI Trusted Publishing (OIDC). No API token is stored in this repo.
9
+
10
+ on:
11
+ push:
12
+ tags: ["v*"]
13
+ workflow_dispatch:
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ build:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
+ with:
24
+ fetch-depth: 0
25
+ persist-credentials: false
26
+
27
+ - name: Set up Python
28
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
29
+ with:
30
+ python-version: "3.12"
31
+
32
+ - name: Install Hatch
33
+ run: pip install hatch "hatchling>=1.21,<1.32"
34
+
35
+ - name: Test before release (offline suite)
36
+ run: hatch run test -m "not integration"
37
+
38
+ - name: Build wheel + sdist
39
+ run: hatch build
40
+
41
+ - name: Show built version
42
+ run: ls -l dist/
43
+
44
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
45
+ with:
46
+ name: dist
47
+ path: dist/
48
+ if-no-files-found: error
49
+
50
+ testpypi:
51
+ needs: build
52
+ runs-on: ubuntu-latest
53
+ environment:
54
+ name: testpypi
55
+ url: https://test.pypi.org/p/saprfclib
56
+ permissions:
57
+ id-token: write # required for Trusted Publishing
58
+ steps:
59
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
60
+ with:
61
+ name: dist
62
+ path: dist/
63
+
64
+ - name: Publish to TestPyPI
65
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
66
+ with:
67
+ repository-url: https://test.pypi.org/legacy/
68
+
69
+ pypi:
70
+ needs: testpypi
71
+ runs-on: ubuntu-latest
72
+ environment:
73
+ name: pypi # protect with a required reviewer in repo settings
74
+ url: https://pypi.org/p/saprfclib
75
+ permissions:
76
+ id-token: write
77
+ steps:
78
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
79
+ with:
80
+ name: dist
81
+ path: dist/
82
+
83
+ - name: Publish to PyPI
84
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
85
+
86
+ github-release:
87
+ needs: pypi
88
+ if: github.ref_type == 'tag'
89
+ runs-on: ubuntu-latest
90
+ permissions:
91
+ contents: write
92
+ steps:
93
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
94
+ with:
95
+ name: dist
96
+ path: dist/
97
+
98
+ # `gh` ships on the runner, so no third-party action is needed in the
99
+ # release path. One less dependency to trust on a job that has write
100
+ # access to the repository.
101
+ - name: Create GitHub release
102
+ env:
103
+ GH_TOKEN: ${{ github.token }}
104
+ TAG: ${{ github.ref_name }}
105
+ run: |
106
+ gh release create "$TAG" dist/* \
107
+ --repo "$GITHUB_REPOSITORY" \
108
+ --title "$TAG" \
109
+ --generate-notes
@@ -0,0 +1,102 @@
1
+ name: Security
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+ branches: ["main"]
8
+ schedule:
9
+ - cron: "0 6 * * 1" # Weekly Monday 06:00 UTC — catch new CVEs without code change
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ trivy:
16
+ name: Trivy (dependency CVEs + secrets)
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
20
+ with:
21
+ persist-credentials: false
22
+
23
+ - name: Install Trivy
24
+ run: |
25
+ curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \
26
+ | sh -s -- -b /usr/local/bin latest
27
+
28
+ - name: Dependency vulnerability scan (HIGH/CRITICAL)
29
+ # Scans pyproject.toml / requirements / installed packages for known CVEs.
30
+ # OSV + NVD + GitHub Advisory DB. Exit 1 on any HIGH or CRITICAL finding.
31
+ run: |
32
+ trivy fs \
33
+ --scanners vuln \
34
+ --pkg-types library \
35
+ --severity HIGH,CRITICAL \
36
+ --exit-code 1 \
37
+ --format table \
38
+ .
39
+
40
+ - name: Secret scan
41
+ # Detects hardcoded credentials, API keys, private keys in source tree.
42
+ # Any finding fails the build — credentials must never be committed.
43
+ run: |
44
+ trivy fs \
45
+ --scanners secret \
46
+ --exit-code 1 \
47
+ --format table \
48
+ .
49
+
50
+ semgrep:
51
+ name: Semgrep (SAST)
52
+ runs-on: ubuntu-latest
53
+ steps:
54
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
55
+ with:
56
+ persist-credentials: false
57
+
58
+ - name: Set up Python
59
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
60
+ with:
61
+ python-version: "3.12"
62
+
63
+ - name: Install Semgrep
64
+ run: pip install semgrep
65
+
66
+ - name: SAST scan
67
+ # p/python — Python security rules (dangerous builtins, injection, etc.)
68
+ # p/secrets — hardcoded credential patterns (belt-and-suspenders vs Trivy)
69
+ # p/trailofbits — Trail of Bits crypto/protocol rules (relevant for RFC wire code)
70
+ # --error: any finding → exit 1. Suppress false positives inline with # nosemgrep.
71
+ run: |
72
+ semgrep \
73
+ --config p/python \
74
+ --config p/secrets \
75
+ --config p/trailofbits \
76
+ --error \
77
+ --no-rewrite-rule-ids \
78
+ src/ tests/
79
+
80
+ zizmor:
81
+ name: Zizmor (GitHub Actions workflow audit)
82
+ runs-on: ubuntu-latest
83
+ steps:
84
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
85
+ with:
86
+ persist-credentials: false
87
+
88
+ - name: Set up Python
89
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
90
+ with:
91
+ python-version: "3.12"
92
+
93
+ - name: Audit workflows
94
+ # GH_TOKEN enables zizmor's online audits — notably impostor-commit
95
+ # detection, which verifies that a pinned SHA actually belongs to the
96
+ # action's repository. Without it those audits are skipped, which is
97
+ # precisely the risk that pinning by SHA introduces.
98
+ env:
99
+ GH_TOKEN: ${{ github.token }}
100
+ run: |
101
+ pip install zizmor
102
+ zizmor --persona=regular .github/workflows/