primarysource 0.1.0__py3-none-any.whl

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 (303) hide show
  1. primarysource/__init__.py +5 -0
  2. primarysource/bbf/__init__.py +56 -0
  3. primarysource/bbf/_xml.py +63 -0
  4. primarysource/bbf/catalogue.json +777 -0
  5. primarysource/bbf/catalogue.py +137 -0
  6. primarysource/bbf/cli.py +79 -0
  7. primarysource/bbf/datamodel.py +281 -0
  8. primarysource/bbf/importer.py +151 -0
  9. primarysource/bbf/library.py +380 -0
  10. primarysource/bbf/server.py +72 -0
  11. primarysource/budget.py +63 -0
  12. primarysource/c2sp/__init__.py +53 -0
  13. primarysource/c2sp/_fetch.py +5 -0
  14. primarysource/c2sp/catalogue.json +231 -0
  15. primarysource/c2sp/catalogue.py +95 -0
  16. primarysource/c2sp/cli.py +62 -0
  17. primarysource/c2sp/importer.py +90 -0
  18. primarysource/c2sp/library.py +309 -0
  19. primarysource/c2sp/markdown.py +31 -0
  20. primarysource/c2sp/server.py +68 -0
  21. primarysource/cavp/__init__.py +42 -0
  22. primarysource/cavp/cli.py +84 -0
  23. primarysource/cavp/data/vectors.toml +47 -0
  24. primarysource/cavp/importer.py +156 -0
  25. primarysource/cavp/model.py +279 -0
  26. primarysource/cavp/parse.py +269 -0
  27. primarysource/cavp/paths.py +17 -0
  28. primarysource/cavp/server.py +67 -0
  29. primarysource/cavp/tools.py +169 -0
  30. primarysource/cli.py +107 -0
  31. primarysource/cursor.py +82 -0
  32. primarysource/cve/__init__.py +5 -0
  33. primarysource/cve/__main__.py +6 -0
  34. primarysource/cve/cli.py +144 -0
  35. primarysource/cve/collection.py +20 -0
  36. primarysource/cve/config.py +58 -0
  37. primarysource/cve/errors.py +16 -0
  38. primarysource/cve/kernel.py +232 -0
  39. primarysource/cve/mcp.py +139 -0
  40. primarysource/cve/parse.py +318 -0
  41. primarysource/cve/py.typed +0 -0
  42. primarysource/cve/service.py +760 -0
  43. primarysource/cve/store.py +214 -0
  44. primarysource/cve/sync.py +438 -0
  45. primarysource/cve/views.py +304 -0
  46. primarysource/debpolicy/__init__.py +16 -0
  47. primarysource/debpolicy/catalogue.py +263 -0
  48. primarysource/debpolicy/cli.py +12 -0
  49. primarysource/debpolicy/server.py +11 -0
  50. primarysource/draft/__init__.py +11 -0
  51. primarysource/draft/catalogue.py +117 -0
  52. primarysource/draft/cli.py +149 -0
  53. primarysource/draft/collection.py +21 -0
  54. primarysource/draft/datatracker.py +158 -0
  55. primarysource/draft/importer.py +163 -0
  56. primarysource/draft/licence.py +89 -0
  57. primarysource/draft/names.py +64 -0
  58. primarysource/draft/net.py +133 -0
  59. primarysource/draft/segment.py +192 -0
  60. primarysource/draft/server.py +91 -0
  61. primarysource/draft/status.py +146 -0
  62. primarysource/draft/store.py +199 -0
  63. primarysource/draft/tracked.json +104 -0
  64. primarysource/draft/views.py +430 -0
  65. primarysource/draft/xmlmeta.py +74 -0
  66. primarysource/ecma/__init__.py +52 -0
  67. primarysource/ecma/_pdf.py +143 -0
  68. primarysource/ecma/catalogue.json +22 -0
  69. primarysource/ecma/catalogue.py +100 -0
  70. primarysource/ecma/cli.py +67 -0
  71. primarysource/ecma/identify.py +25 -0
  72. primarysource/ecma/importer.py +108 -0
  73. primarysource/ecma/library.py +240 -0
  74. primarysource/ecma/server.py +67 -0
  75. primarysource/errors.py +29 -0
  76. primarysource/fetch.py +360 -0
  77. primarysource/fsutil.py +150 -0
  78. primarysource/gitdocs/__init__.py +15 -0
  79. primarysource/gitdocs/catalogue.py +116 -0
  80. primarysource/gitdocs/cli.py +12 -0
  81. primarysource/gitdocs/server.py +8 -0
  82. primarysource/home.py +65 -0
  83. primarysource/iana/__init__.py +32 -0
  84. primarysource/iana/catalogue.json +244 -0
  85. primarysource/iana/catalogue.py +69 -0
  86. primarysource/iana/cli.py +69 -0
  87. primarysource/iana/importer.py +157 -0
  88. primarysource/iana/model.py +325 -0
  89. primarysource/iana/reader.py +315 -0
  90. primarysource/iana/server.py +67 -0
  91. primarysource/iana/store.py +83 -0
  92. primarysource/iana/xmlsafe.py +47 -0
  93. primarysource/identify.py +186 -0
  94. primarysource/ieee/__init__.py +58 -0
  95. primarysource/ieee/_html.py +49 -0
  96. primarysource/ieee/catalogue.json +74 -0
  97. primarysource/ieee/catalogue.py +123 -0
  98. primarysource/ieee/cli.py +79 -0
  99. primarysource/ieee/identify.py +24 -0
  100. primarysource/ieee/ieee802.py +47 -0
  101. primarysource/ieee/importer.py +305 -0
  102. primarysource/ieee/posix.py +233 -0
  103. primarysource/ieee/server.py +79 -0
  104. primarysource/index.py +446 -0
  105. primarysource/iso/__init__.py +51 -0
  106. primarysource/iso/cli.py +98 -0
  107. primarysource/iso/designation.py +95 -0
  108. primarysource/iso/identify.py +100 -0
  109. primarysource/iso/importer.py +224 -0
  110. primarysource/iso/library.py +249 -0
  111. primarysource/iso/server.py +69 -0
  112. primarysource/itu/__init__.py +41 -0
  113. primarysource/itu/__main__.py +8 -0
  114. primarysource/itu/asn1mod.py +478 -0
  115. primarysource/itu/catalog.py +472 -0
  116. primarysource/itu/cli.py +445 -0
  117. primarysource/itu/collection.py +20 -0
  118. primarysource/itu/corpus.py +118 -0
  119. primarysource/itu/errata.py +16 -0
  120. primarysource/itu/formal.py +565 -0
  121. primarysource/itu/identify.py +26 -0
  122. primarysource/itu/index.py +490 -0
  123. primarysource/itu/mcp.py +474 -0
  124. primarysource/itu/py.typed +0 -0
  125. primarysource/itu/sources.py +600 -0
  126. primarysource/itu/text.py +609 -0
  127. primarysource/kconfig/__init__.py +16 -0
  128. primarysource/kconfig/catalogue.py +98 -0
  129. primarysource/kconfig/cli.py +12 -0
  130. primarysource/kconfig/server.py +8 -0
  131. primarysource/model.py +63 -0
  132. primarysource/nist/__init__.py +5 -0
  133. primarysource/nist/collection.py +46 -0
  134. primarysource/nist/data/acquired-nist.json +1657 -0
  135. primarysource/nist/data/nist-catalog.json +12090 -0
  136. primarysource/nist/data/sources.toml +1996 -0
  137. primarysource/nist/identify.py +41 -0
  138. primarysource/nist/ingest/__init__.py +1 -0
  139. primarysource/nist/ingest/acquire_nist.py +128 -0
  140. primarysource/nist/ingest/controls.py +82 -0
  141. primarysource/nist/ingest/discover_nist.py +218 -0
  142. primarysource/nist/ingest/extract.py +48 -0
  143. primarysource/nist/ingest/fetch.py +211 -0
  144. primarysource/nist/ingest/index.py +14 -0
  145. primarysource/nist/ingest/oscal.py +480 -0
  146. primarysource/nist/ingest/prose_layouts.py +236 -0
  147. primarysource/nist/ingest/sources.py +94 -0
  148. primarysource/nist/ingest/status.py +255 -0
  149. primarysource/nist/models.py +7 -0
  150. primarysource/nist/paths.py +33 -0
  151. primarysource/nist/server/__init__.py +3 -0
  152. primarysource/nist/server/corpus.py +38 -0
  153. primarysource/nist/server/mcp.py +19 -0
  154. primarysource/nist/server/tools.py +35 -0
  155. primarysource/nist/spec.py +38 -0
  156. primarysource/pdfcorpus/__init__.py +16 -0
  157. primarysource/pdfcorpus/corpus.py +159 -0
  158. primarysource/pdfcorpus/fts.py +175 -0
  159. primarysource/pdfcorpus/index.py +70 -0
  160. primarysource/pdfcorpus/models.py +30 -0
  161. primarysource/pdfcorpus/server.py +96 -0
  162. primarysource/pdfcorpus/spec.py +37 -0
  163. primarysource/pdfcorpus/split.py +391 -0
  164. primarysource/pdfcorpus/tools.py +244 -0
  165. primarysource/pep/__init__.py +15 -0
  166. primarysource/pep/catalogue.py +191 -0
  167. primarysource/pep/cli.py +12 -0
  168. primarysource/pep/server.py +8 -0
  169. primarysource/pkgdocs/__init__.py +19 -0
  170. primarysource/pkgdocs/fetch.py +356 -0
  171. primarysource/pkgdocs/index.py +89 -0
  172. primarysource/pkgdocs/model.py +76 -0
  173. primarysource/pkgdocs/readers/__init__.py +1 -0
  174. primarysource/pkgdocs/readers/_common.py +134 -0
  175. primarysource/pkgdocs/readers/asciidoc.py +160 -0
  176. primarysource/pkgdocs/readers/latex.py +315 -0
  177. primarysource/pkgdocs/readers/markdown.py +173 -0
  178. primarysource/pkgdocs/readers/pod.py +132 -0
  179. primarysource/pkgdocs/readers/rst.py +239 -0
  180. primarysource/pkgdocs/readers/scdoc.py +108 -0
  181. primarysource/pkgdocs/runtime.py +552 -0
  182. primarysource/pkgdocs/serving.py +158 -0
  183. primarysource/pkgdocs/store.py +167 -0
  184. primarysource/pms/__init__.py +16 -0
  185. primarysource/pms/catalogue.py +88 -0
  186. primarysource/pms/cli.py +12 -0
  187. primarysource/pms/server.py +8 -0
  188. primarysource/py.typed +0 -0
  189. primarysource/readers/__init__.py +10 -0
  190. primarysource/readers/bookmarks.py +192 -0
  191. primarysource/readers/html.py +1312 -0
  192. primarysource/readers/markdown.py +189 -0
  193. primarysource/readers/pdf.py +187 -0
  194. primarysource/readers/xml.py +230 -0
  195. primarysource/refs.py +87 -0
  196. primarysource/registry.py +161 -0
  197. primarysource/render.py +49 -0
  198. primarysource/rfc/__init__.py +3 -0
  199. primarysource/rfc/__main__.py +6 -0
  200. primarysource/rfc/cite.py +204 -0
  201. primarysource/rfc/cli.py +903 -0
  202. primarysource/rfc/collection.py +19 -0
  203. primarysource/rfc/config.py +138 -0
  204. primarysource/rfc/errata.py +203 -0
  205. primarysource/rfc/fsutil.py +82 -0
  206. primarysource/rfc/graph.py +213 -0
  207. primarysource/rfc/index/__init__.py +1 -0
  208. primarysource/rfc/index/chunker.py +281 -0
  209. primarysource/rfc/index/embed.py +166 -0
  210. primarysource/rfc/index/hybrid.py +305 -0
  211. primarysource/rfc/index/lexical.py +223 -0
  212. primarysource/rfc/index/rfcxml.py +673 -0
  213. primarysource/rfc/index/search.py +508 -0
  214. primarysource/rfc/index/txtseg.py +585 -0
  215. primarysource/rfc/library.py +294 -0
  216. primarysource/rfc/mcp/__init__.py +1 -0
  217. primarysource/rfc/mcp/server.py +381 -0
  218. primarysource/rfc/packs/__init__.py +1 -0
  219. primarysource/rfc/packs/builder.py +228 -0
  220. primarysource/rfc/packs/collections/all-current-txt.json +10 -0
  221. primarysource/rfc/packs/collections/essentials.json +19 -0
  222. primarysource/rfc/packs/collections/fyi-current.json +10 -0
  223. primarysource/rfc/packs/collections/standards-current.json +10 -0
  224. primarysource/rfc/packs/collections/year-2025.json +10 -0
  225. primarysource/rfc/packs/collections/year-2026.json +10 -0
  226. primarysource/rfc/packs/collections.py +163 -0
  227. primarysource/rfc/packs/docsetexport.py +163 -0
  228. primarysource/rfc/packs/epub.py +159 -0
  229. primarysource/rfc/packs/export.py +306 -0
  230. primarysource/rfc/packs/htmlexport.py +82 -0
  231. primarysource/rfc/packs/serve.py +143 -0
  232. primarysource/rfc/packs/zimexport.py +171 -0
  233. primarysource/rfc/query.py +466 -0
  234. primarysource/rfc/subjects.py +457 -0
  235. primarysource/rfc/sync/__init__.py +1 -0
  236. primarysource/rfc/sync/downloader.py +132 -0
  237. primarysource/rfc/sync/manifest.py +162 -0
  238. primarysource/rfc/sync/scanner.py +49 -0
  239. primarysource/rfc/sync/state.py +464 -0
  240. primarysource/rfc/views.py +1067 -0
  241. primarysource/rpmdocs/__init__.py +15 -0
  242. primarysource/rpmdocs/catalogue.py +117 -0
  243. primarysource/rpmdocs/cli.py +12 -0
  244. primarysource/rpmdocs/server.py +8 -0
  245. primarysource/scaffold.py +223 -0
  246. primarysource/serve.py +75 -0
  247. primarysource/settings.py +152 -0
  248. primarysource/usb/__init__.py +5 -0
  249. primarysource/usb/collection.py +37 -0
  250. primarysource/usb/data/acquired.json +2025 -0
  251. primarysource/usb/data/sources-usb.toml +852 -0
  252. primarysource/usb/data/sources.toml +10 -0
  253. primarysource/usb/data/usb-catalog.json +1663 -0
  254. primarysource/usb/data/usb-classification.json +1986 -0
  255. primarysource/usb/data/usb-corpus-build.json +608 -0
  256. primarysource/usb/data/usb-pdf-choice.json +839 -0
  257. primarysource/usb/data/usb-split-measure.json +1803 -0
  258. primarysource/usb/ingest/__init__.py +1 -0
  259. primarysource/usb/ingest/acquire.py +130 -0
  260. primarysource/usb/ingest/discover_usb.py +88 -0
  261. primarysource/usb/ingest/extract.py +48 -0
  262. primarysource/usb/ingest/fetch.py +197 -0
  263. primarysource/usb/ingest/index.py +14 -0
  264. primarysource/usb/ingest/sources.py +85 -0
  265. primarysource/usb/models.py +7 -0
  266. primarysource/usb/paths.py +33 -0
  267. primarysource/usb/server/__init__.py +3 -0
  268. primarysource/usb/server/corpus.py +38 -0
  269. primarysource/usb/server/mcp.py +19 -0
  270. primarysource/usb/server/tools.py +35 -0
  271. primarysource/usb/spec.py +20 -0
  272. primarysource/usbids/__init__.py +56 -0
  273. primarysource/usbids/cli.py +65 -0
  274. primarysource/usbids/importer.py +272 -0
  275. primarysource/usbids/parse.py +283 -0
  276. primarysource/usbids/server.py +55 -0
  277. primarysource/usbids/store.py +80 -0
  278. primarysource/usbids/tools.py +174 -0
  279. primarysource/zim/__init__.py +5 -0
  280. primarysource/zim/__main__.py +8 -0
  281. primarysource/zim/_libzim.py +37 -0
  282. primarysource/zim/catalog.py +594 -0
  283. primarysource/zim/cli.py +555 -0
  284. primarysource/zim/collection.py +41 -0
  285. primarysource/zim/errors.py +100 -0
  286. primarysource/zim/groups.py +30 -0
  287. primarysource/zim/library.py +668 -0
  288. primarysource/zim/manage.py +636 -0
  289. primarysource/zim/markdown.py +1247 -0
  290. primarysource/zim/mcp.py +201 -0
  291. primarysource/zim/purl.py +130 -0
  292. primarysource/zim/py.typed +0 -0
  293. primarysource/zim/refs.py +90 -0
  294. primarysource/zim/service.py +1182 -0
  295. primarysource/zim/snapshot.py +197 -0
  296. primarysource/zim/sync.py +269 -0
  297. primarysource/zim/views.py +230 -0
  298. primarysource-0.1.0.dist-info/METADATA +118 -0
  299. primarysource-0.1.0.dist-info/RECORD +303 -0
  300. primarysource-0.1.0.dist-info/WHEEL +4 -0
  301. primarysource-0.1.0.dist-info/entry_points.txt +2 -0
  302. primarysource-0.1.0.dist-info/licenses/LICENSE +203 -0
  303. primarysource-0.1.0.dist-info/licenses/NOTICE +51 -0
@@ -0,0 +1,231 @@
1
+ {
2
+ "repo": "C2SP/C2SP",
3
+ "commit": "09f3e519fe79bcbdccadcf821182e3b488a14d71",
4
+ "raw_base": "https://raw.githubusercontent.com/C2SP/C2SP/",
5
+ "terms": {
6
+ "licence": "CC-BY-4.0",
7
+ "placement": "layer",
8
+ "terms_url": "https://github.com/C2SP/C2SP/blob/09f3e519fe79bcbdccadcf821182e3b488a14d71/.github/README.md",
9
+ "terms_read": "2026-11-12",
10
+ "summary": "All C2SP specifications are licensed CC BY 4.0 (the repository's README); attribution required. Code and data in the repository are BSD-1-Clause."
11
+ },
12
+ "specs": [
13
+ {
14
+ "id": "BLAKE3",
15
+ "path": "BLAKE3.md",
16
+ "title": "The BLAKE3 Hashing Framework",
17
+ "size": 32004,
18
+ "sha256": "85c336e9900538e572333dd16ddde9f605c000ec8293001dd4c2642193b9c07e"
19
+ },
20
+ {
21
+ "id": "FLOE",
22
+ "path": "FLOE.md",
23
+ "title": "Fast Lightweight Online Encryption (FLOE)",
24
+ "size": 19400,
25
+ "sha256": "7808f6995f098400009e363674013d71a4d980ad30fc3ca46cb34aae1fafc044"
26
+ },
27
+ {
28
+ "id": "XAES-256-GCM",
29
+ "path": "XAES-256-GCM.md",
30
+ "title": "The XAES-256-GCM extended-nonce AEAD",
31
+ "size": 14100,
32
+ "sha256": "bcce71811d53d1d19f0298dc52746fd4f92f42bc4aec55abad5d2bd1ab9b2810"
33
+ },
34
+ {
35
+ "id": "age",
36
+ "path": "age.md",
37
+ "title": "age",
38
+ "size": 27736,
39
+ "sha256": "b0a767b91a184c536e8a04002f7bba7521388fee3989f5891e23cc1aaa00232f"
40
+ },
41
+ {
42
+ "id": "age-plugin",
43
+ "path": "age-plugin.md",
44
+ "title": "Plugin system for age",
45
+ "size": 25210,
46
+ "sha256": "83623f2d3942fb99c0d59b45c4f9d8bad2b29bf5af178abade558b6fc8341c95"
47
+ },
48
+ {
49
+ "id": "chacha20-poly1305-siv",
50
+ "path": "chacha20-poly1305-siv.md",
51
+ "title": "ChaCha20-Poly1305-SIV",
52
+ "size": 42410,
53
+ "sha256": "b93e3c9bf3e63669b059947f89bef7f3f55c502a59ef2798b8f084086fb2854b"
54
+ },
55
+ {
56
+ "id": "chacha8rand",
57
+ "path": "chacha8rand.md",
58
+ "title": "ChaCha8Rand",
59
+ "size": 18839,
60
+ "sha256": "09610ec86f76f1fb0160f354b68801be7fdf9948a04daa2dc88544286b33cd8d"
61
+ },
62
+ {
63
+ "id": "chunked-encryption",
64
+ "path": "chunked-encryption.md",
65
+ "title": "Chunked encryption",
66
+ "size": 18366,
67
+ "sha256": "4c2efb7e9930d4df06835f64c111e13c475afb6eed314b85fbbaac59a8227201"
68
+ },
69
+ {
70
+ "id": "cocktail-dkg",
71
+ "path": "cocktail-dkg.md",
72
+ "title": "COCKTAIL Distributed Key Generation",
73
+ "size": 148605,
74
+ "sha256": "e7ded83da0b54d9bf0052368ddcd2ca9ad6e257ed7fd17abf14f7e69f44e717d"
75
+ },
76
+ {
77
+ "id": "det-keygen",
78
+ "path": "det-keygen.md",
79
+ "title": "Deterministic Key Generation",
80
+ "size": 20994,
81
+ "sha256": "d78969dbe85eb5461c5cbf9136617c85887bed884884ff66a0da8398e411f11a"
82
+ },
83
+ {
84
+ "id": "https-bastion",
85
+ "path": "https-bastion.md",
86
+ "title": "HTTPS Bastion",
87
+ "size": 7053,
88
+ "sha256": "a3939dd7075f3578fb996f87feafc96e5d6edc71b895c6e965343c0b0565d07c"
89
+ },
90
+ {
91
+ "id": "httpsig-pq",
92
+ "path": "httpsig-pq.md",
93
+ "title": "Post-Quantum Algorithms for HTTP Message Signatures",
94
+ "size": 47902,
95
+ "sha256": "363ed0952d224a4f4ab1276eb1aa1fe88a046f35e046265eaa4c37dc362027e2"
96
+ },
97
+ {
98
+ "id": "jq255",
99
+ "path": "jq255.md",
100
+ "title": "The jq255e and jq255s Groups, Key Exchange and Signatures",
101
+ "size": 60656,
102
+ "sha256": "d59fff0aae35128adb3753f72e7f23873d08685951619c9bf0939c4820810021"
103
+ },
104
+ {
105
+ "id": "kopis",
106
+ "path": "kopis.md",
107
+ "title": "The Kopis Key Encapsulation Mechanism",
108
+ "size": 14607,
109
+ "sha256": "9707bb224408d1ea48f5748c8492472c8968ca2f5318a79ee2594bfcb768c6bd"
110
+ },
111
+ {
112
+ "id": "mtc-tlog",
113
+ "path": "mtc-tlog.md",
114
+ "title": "Merkle Tree Certificates With Tiled Transparency Logs",
115
+ "size": 8019,
116
+ "sha256": "d4cb827114feb7e5bdf5a5083eddc8805afcf135ee102543319711e607abf978"
117
+ },
118
+ {
119
+ "id": "passkey-record",
120
+ "path": "passkey-record.md",
121
+ "title": "Passkey records",
122
+ "size": 6770,
123
+ "sha256": "5e33609021db3023bec58c5b9512c7fb9a9f7ffc52853afb9a20356d8f1a7c31"
124
+ },
125
+ {
126
+ "id": "phc-strings",
127
+ "path": "phc-strings.md",
128
+ "title": "PHC Strings",
129
+ "size": 16430,
130
+ "sha256": "b53e4460a558486388233f8ca5cd30ef1f0543c870b7e0bfeecc282c2fbc2bd9"
131
+ },
132
+ {
133
+ "id": "sequencehash",
134
+ "path": "sequencehash.md",
135
+ "title": "SequenceHash and SequenceMAC",
136
+ "size": 32689,
137
+ "sha256": "63a3db48455cf3643203ac8665720042ae1f62498e374351b0d228ec4157f101"
138
+ },
139
+ {
140
+ "id": "signed-note",
141
+ "path": "signed-note.md",
142
+ "title": "Note",
143
+ "size": 7841,
144
+ "sha256": "bd9bf002294385e0cf3c79aac74b193a3831ccc7b32073fb00baa727653816ae"
145
+ },
146
+ {
147
+ "id": "static-ct-api",
148
+ "path": "static-ct-api.md",
149
+ "title": "The Static Certificate Transparency API",
150
+ "size": 14179,
151
+ "sha256": "e7173ca6fc226ada35d4bffb2cb8bf5a68f6ba7f12a3057fbb696fc6d32b6b5a"
152
+ },
153
+ {
154
+ "id": "tlog-checkpoint",
155
+ "path": "tlog-checkpoint.md",
156
+ "title": "Transparency Log Checkpoints",
157
+ "size": 5999,
158
+ "sha256": "528317ad4dabb0568a1c20728b74fdab6fbad99749cc2fb089e8fe92c8ddf00e"
159
+ },
160
+ {
161
+ "id": "tlog-cosignature",
162
+ "path": "tlog-cosignature.md",
163
+ "title": "Transparency Log Cosignatures",
164
+ "size": 10715,
165
+ "sha256": "9cf3f0ef2690319697fcbb4d1e823b593d90a1769912fdcb859ab89a3f924fbe"
166
+ },
167
+ {
168
+ "id": "tlog-list",
169
+ "path": "tlog-list.md",
170
+ "title": "Transparency Log Lists",
171
+ "size": 2295,
172
+ "sha256": "9c83ee9dbe91b33fdd628f97e35aac7044ff092cfb9cd2e1458721651a0c5934"
173
+ },
174
+ {
175
+ "id": "tlog-mirror",
176
+ "path": "tlog-mirror.md",
177
+ "title": "Transparency Log Mirrors",
178
+ "size": 23273,
179
+ "sha256": "1aef508bd5fd8c4a341c307c5aa446ce4888a32ba6109c7c6da177a502c049e9"
180
+ },
181
+ {
182
+ "id": "tlog-policy",
183
+ "path": "tlog-policy.md",
184
+ "title": "Transparency log trust policy",
185
+ "size": 9389,
186
+ "sha256": "2a8745ba91627d976ff2922f18c29a1ecd3567a03107749d5ae164cc5825bbd3"
187
+ },
188
+ {
189
+ "id": "tlog-proof",
190
+ "path": "tlog-proof.md",
191
+ "title": "Transparency Log Proofs",
192
+ "size": 5733,
193
+ "sha256": "2b46a3a156ddb477fd88091bdb83b984135d9c42cba53cbed867d13d99afd5e8"
194
+ },
195
+ {
196
+ "id": "tlog-tiles",
197
+ "path": "tlog-tiles.md",
198
+ "title": "Tiled Transparency Logs",
199
+ "size": 12447,
200
+ "sha256": "951b7c96d57b1563a427ecc2c916a44daf4b12b0fa5d0d5188f9c0868855ab1b"
201
+ },
202
+ {
203
+ "id": "tlog-witness",
204
+ "path": "tlog-witness.md",
205
+ "title": "Transparency Log Witness Protocol",
206
+ "size": 14392,
207
+ "sha256": "c9eaf9f403f0e8b3e61229d19f174022b9d80a3851c8c008bfc0ca9ee1180b89"
208
+ },
209
+ {
210
+ "id": "upki-revocation",
211
+ "path": "upki-revocation.md",
212
+ "title": "UPKI Revocation",
213
+ "size": 37013,
214
+ "sha256": "e79976dfed9d73cf37ad1a1a7bc2d2e72dadd7ebbc57c4c0664d0181b0f90ab1"
215
+ },
216
+ {
217
+ "id": "vrf-r255",
218
+ "path": "vrf-r255.md",
219
+ "title": "ristretto255 VRF",
220
+ "size": 9450,
221
+ "sha256": "411c9aed374b4bf342f2afc277e3a3da3ffa22fb7be1f5fe3f4ffb1c4220a4c6"
222
+ },
223
+ {
224
+ "id": "well-known-ssh-hosts",
225
+ "path": "well-known-ssh-hosts.md",
226
+ "title": ".well-known/ssh-known-hosts",
227
+ "size": 2187,
228
+ "sha256": "9b1573f4d78c56e8cee08928a601cb58ff3a880db3fbffb374fc448958d7a66f"
229
+ }
230
+ ]
231
+ }
@@ -0,0 +1,95 @@
1
+ """The C2SP catalogue: which specifications, at which commit, with which sha256. Metadata only, never text.
2
+
3
+ `catalogue.json` is package data. It pins one commit of https://github.com/C2SP/C2SP and, for each top-level
4
+ specification file at that commit, its size and sha256. The importer refuses any file whose digest differs, so a
5
+ rewritten history or a tampered mirror cannot change what a ref reads.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import re
12
+ from dataclasses import dataclass
13
+ from importlib import resources
14
+ from typing import Any
15
+
16
+ from primarysource.errors import LibError
17
+
18
+ DOC_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9-]{0,62}")
19
+ COMMIT = re.compile(r"[0-9a-f]{40}")
20
+ SHA256 = re.compile(r"[0-9a-f]{64}")
21
+ HOSTS = ("raw.githubusercontent.com",)
22
+
23
+
24
+ @dataclass(frozen=True, slots=True)
25
+ class Terms:
26
+ """What the publisher allows, and therefore where the text may be placed."""
27
+
28
+ licence: str
29
+ placement: str
30
+ terms_url: str
31
+ terms_read: str
32
+ summary: str
33
+
34
+
35
+ @dataclass(frozen=True, slots=True)
36
+ class Spec:
37
+ """One specification file at the pinned commit."""
38
+
39
+ id: str
40
+ path: str
41
+ title: str
42
+ size: int
43
+ sha256: str
44
+
45
+
46
+ @dataclass(frozen=True, slots=True)
47
+ class Catalogue:
48
+ repo: str
49
+ commit: str
50
+ raw_base: str
51
+ terms: Terms
52
+ specs: tuple[Spec, ...]
53
+
54
+ def spec(self, doc_id: str) -> Spec:
55
+ for candidate in self.specs:
56
+ if candidate.id == doc_id:
57
+ return candidate
58
+ known = ", ".join(s.id for s in self.specs)
59
+ raise LibError(f"no C2SP specification {doc_id!r} in the catalogue", f"known: {known}")
60
+
61
+ def url(self, spec: Spec) -> str:
62
+ return f"{self.raw_base}{self.commit}/{spec.path}"
63
+
64
+
65
+ def parse_catalogue(raw: dict[str, Any]) -> Catalogue:
66
+ """Validate a catalogue document. Raises `ValueError`: a malformed catalogue is a packaging bug."""
67
+ commit = raw["commit"]
68
+ if not isinstance(commit, str) or not COMMIT.fullmatch(commit):
69
+ raise ValueError("catalogue commit must be 40 lowercase hex digits")
70
+ terms = raw["terms"]
71
+ specs: list[Spec] = []
72
+ for entry in raw["specs"]:
73
+ spec = Spec(entry["id"], entry["path"], entry["title"], int(entry["size"]), entry["sha256"])
74
+ if not DOC_ID.fullmatch(spec.id) or spec.path != f"{spec.id}.md":
75
+ raise ValueError(f"catalogue entry {spec.id!r}: id must be a slug and path <id>.md")
76
+ if not SHA256.fullmatch(spec.sha256):
77
+ raise ValueError(f"catalogue entry {spec.id!r}: sha256 must be 64 hex digits")
78
+ specs.append(spec)
79
+ if len({s.id for s in specs}) != len(specs):
80
+ raise ValueError("duplicate specification ids in the catalogue")
81
+ if terms["placement"] not in ("layer", "volume"):
82
+ raise ValueError("terms.placement must be 'layer' or 'volume'")
83
+ return Catalogue(
84
+ raw["repo"],
85
+ commit,
86
+ raw["raw_base"],
87
+ Terms(terms["licence"], terms["placement"], terms["terms_url"], terms["terms_read"], terms["summary"]),
88
+ tuple(specs),
89
+ )
90
+
91
+
92
+ def catalogue() -> Catalogue:
93
+ """The packaged catalogue."""
94
+ text = resources.files("primarysource.c2sp").joinpath("catalogue.json").read_text(encoding="utf-8")
95
+ return parse_catalogue(json.loads(text))
@@ -0,0 +1,62 @@
1
+ """`primarysource c2sp ...`: the collection's own commands.
2
+
3
+ primarysource c2sp import [--only ID ...]
4
+ primarysource c2sp docs
5
+ primarysource c2sp outline <doc>
6
+ primarysource c2sp read <ref> [--max-tokens N]
7
+ primarysource c2sp search <query> [--doc ID] [--limit N] [--start N]
8
+ primarysource c2sp cite <ref>
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import sys
15
+ from collections.abc import Sequence
16
+
17
+ from primarysource.c2sp import importer, library
18
+ from primarysource.errors import LibError
19
+
20
+
21
+ def _parser() -> argparse.ArgumentParser:
22
+ parser = argparse.ArgumentParser(prog="primarysource c2sp", description="C2SP specifications (CC BY 4.0)")
23
+ commands = parser.add_subparsers(dest="command", required=True)
24
+ imp = commands.add_parser("import", help="fetch and verify the pinned specifications (one request per second)")
25
+ imp.add_argument("--only", action="append", default=[], metavar="ID", help="import only this specification")
26
+ commands.add_parser("docs", help="the catalogue and what is imported")
27
+ commands.add_parser("outline", help="a specification's headings with refs").add_argument("doc")
28
+ read = commands.add_parser("read", help="read by ref")
29
+ read.add_argument("ref")
30
+ read.add_argument("--max-tokens", type=int, default=2000)
31
+ search = commands.add_parser("search", help="find blocks containing words")
32
+ search.add_argument("query")
33
+ search.add_argument("--doc")
34
+ search.add_argument("--limit", type=int, default=10)
35
+ search.add_argument("--start", type=int, default=0)
36
+ commands.add_parser("cite", help="the citation for a ref").add_argument("ref")
37
+ return parser
38
+
39
+
40
+ def _out(text: str) -> int:
41
+ sys.stdout.write(text if text.endswith("\n") else text + "\n")
42
+ return 0
43
+
44
+
45
+ def main(argv: Sequence[str]) -> int:
46
+ args = _parser().parse_args(list(argv))
47
+ try:
48
+ if args.command == "import":
49
+ result = importer.import_specs(library.default_home(), args.only)
50
+ return _out(f"imported {len(result.files)} specifications at {result.commit[:7]} into {result.directory}")
51
+ if args.command == "docs":
52
+ return _out(library.docs())
53
+ if args.command == "outline":
54
+ return _out(library.outline(args.doc))
55
+ if args.command == "read":
56
+ return _out(library.read(args.ref, max_tokens=args.max_tokens))
57
+ if args.command == "search":
58
+ return _out(library.search(args.query, doc=args.doc, limit=args.limit, start=args.start))
59
+ return _out(library.cite(args.ref))
60
+ except LibError as exc:
61
+ print(exc.as_text(), file=sys.stderr)
62
+ return 1
@@ -0,0 +1,90 @@
1
+ """Import the pinned C2SP specifications into `$PRIMARYSOURCE_HOME/c2sp/<commit>/`.
2
+
3
+ Each specification is fetched from `raw.githubusercontent.com/C2SP/C2SP/<commit>/<id>.md`, at most one request per
4
+ second, and written only if its sha256 equals the catalogue's pin. `install.json` is written last, so a reader
5
+ never sees an install that is missing a file. C2SP specifications are licensed CC BY 4.0, so the installed files
6
+ may also be baked into an image layer; this code only ever writes to the home directory.
7
+
8
+ Run as `primarysource c2sp import [--only ID ...]`.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ from collections.abc import Sequence
15
+ from dataclasses import dataclass, field
16
+ from datetime import UTC, datetime
17
+ from pathlib import Path
18
+
19
+ from primarysource.c2sp import markdown
20
+ from primarysource.c2sp._fetch import Fetcher, Transport, sha256, verify
21
+ from primarysource.c2sp.catalogue import DOC_ID, HOSTS, Catalogue, Spec
22
+ from primarysource.c2sp.catalogue import catalogue as packaged_catalogue
23
+ from primarysource.errors import LibError
24
+ from primarysource.home import atomic_write
25
+
26
+ INSTALL_RECORD = "install.json"
27
+ MAX_FILE = markdown.MAX_BYTES
28
+
29
+
30
+ @dataclass(frozen=True, slots=True)
31
+ class ImportResult:
32
+ commit: str
33
+ directory: Path
34
+ files: dict[str, str] = field(default_factory=dict)
35
+ requests: int = 0
36
+
37
+
38
+ def install_dir(home: Path, commit: str) -> Path:
39
+ return home / commit
40
+
41
+
42
+ def import_specs(
43
+ home: Path,
44
+ only: Sequence[str] = (),
45
+ *,
46
+ cat: Catalogue | None = None,
47
+ transport: Transport | None = None,
48
+ fetcher: Fetcher | None = None,
49
+ ) -> ImportResult:
50
+ """Fetch, verify and install the catalogued specs (all, or those named in `only`) under `home`."""
51
+ cat = cat or packaged_catalogue()
52
+ chosen = [cat.spec(name) for name in only] if only else list(cat.specs)
53
+ fetcher = fetcher or Fetcher(HOSTS, transport)
54
+ target = install_dir(home, cat.commit)
55
+ record_path = target / INSTALL_RECORD
56
+ installed: dict[str, str] = {}
57
+ if record_path.is_file():
58
+ try:
59
+ installed = dict(json.loads(record_path.read_text(encoding="utf-8")).get("files", {}))
60
+ except (ValueError, OSError):
61
+ installed = {}
62
+ for spec in chosen:
63
+ data = fetcher.get(cat.url(spec), min(MAX_FILE, max(spec.size * 2, 1024)))
64
+ verify(data, spec.sha256, spec.id)
65
+ markdown.parse(data) # refuses non-UTF-8, NULs and over-bound text before anything is written
66
+ atomic_write(target / spec.path, data)
67
+ installed[spec.id] = spec.sha256
68
+ record = {
69
+ "collection": "c2sp",
70
+ "repo": cat.repo,
71
+ "commit": cat.commit,
72
+ "licence": cat.terms.licence,
73
+ "imported": datetime.now(UTC).isoformat(timespec="seconds"),
74
+ "files": dict(sorted(installed.items())),
75
+ }
76
+ atomic_write(record_path, (json.dumps(record, indent=2) + "\n").encode("utf-8"))
77
+ return ImportResult(cat.commit, target, dict(sorted(installed.items())), fetcher.requests)
78
+
79
+
80
+ def pin_specs(raw_base: str, commit: str, names: Sequence[str], fetcher: Fetcher) -> list[Spec]:
81
+ """Maintainer step: fetch `names` (file ids) at `commit` and return catalogue entries with size and sha256.
82
+ The result is what goes in `catalogue.json`; it is a review step, never run by the importer."""
83
+ specs: list[Spec] = []
84
+ for name in names:
85
+ if not DOC_ID.fullmatch(name):
86
+ raise LibError(f"not a specification id: {name!r}", "")
87
+ data = fetcher.get(f"{raw_base}{commit}/{name}.md", MAX_FILE)
88
+ doc = markdown.parse(data)
89
+ specs.append(Spec(name, f"{name}.md", doc.title or name, len(data), sha256(data)))
90
+ return specs