figurant 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 (170) hide show
  1. figurant-0.1.0/PKG-INFO +122 -0
  2. figurant-0.1.0/README.md +97 -0
  3. figurant-0.1.0/figurant/__init__.py +12 -0
  4. figurant-0.1.0/figurant/api.py +3429 -0
  5. figurant-0.1.0/figurant/bundle/__init__.py +346 -0
  6. figurant-0.1.0/figurant/bundle/_es_names.py +218 -0
  7. figurant-0.1.0/figurant/bundle/_es_work.py +268 -0
  8. figurant-0.1.0/figurant/bundle/_industries.py +342 -0
  9. figurant-0.1.0/figurant/bundle/_nl_names.py +462 -0
  10. figurant-0.1.0/figurant/bundle/_nl_origins.py +137 -0
  11. figurant-0.1.0/figurant/bundle/_nl_work.py +506 -0
  12. figurant-0.1.0/figurant/bundle/_profiles.py +151 -0
  13. figurant-0.1.0/figurant/bundle/_se_names.py +227 -0
  14. figurant-0.1.0/figurant/bundle/_se_work.py +280 -0
  15. figurant-0.1.0/figurant/bundle/_sectors.py +162 -0
  16. figurant-0.1.0/figurant/bundle/_segments.py +502 -0
  17. figurant-0.1.0/figurant/bundle/_university.py +307 -0
  18. figurant-0.1.0/figurant/bundle/content.py +391 -0
  19. figurant-0.1.0/figurant/bundle/packs/__init__.py +41 -0
  20. figurant-0.1.0/figurant/bundle/packs/es.py +239 -0
  21. figurant-0.1.0/figurant/bundle/packs/nl.py +150 -0
  22. figurant-0.1.0/figurant/bundle/packs/se.py +262 -0
  23. figurant-0.1.0/figurant/bundle/sources.py +291 -0
  24. figurant-0.1.0/figurant/cli.py +1205 -0
  25. figurant-0.1.0/figurant/compose/__init__.py +24 -0
  26. figurant-0.1.0/figurant/compose/coherence.py +136 -0
  27. figurant-0.1.0/figurant/compose/engine.py +390 -0
  28. figurant-0.1.0/figurant/compose/shape.py +127 -0
  29. figurant-0.1.0/figurant/contract/__init__.py +466 -0
  30. figurant-0.1.0/figurant/contract/v1.json +109 -0
  31. figurant-0.1.0/figurant/contract/v10.json +736 -0
  32. figurant-0.1.0/figurant/contract/v11.json +823 -0
  33. figurant-0.1.0/figurant/contract/v12.json +830 -0
  34. figurant-0.1.0/figurant/contract/v13.json +877 -0
  35. figurant-0.1.0/figurant/contract/v14.json +895 -0
  36. figurant-0.1.0/figurant/contract/v15.json +901 -0
  37. figurant-0.1.0/figurant/contract/v16.json +996 -0
  38. figurant-0.1.0/figurant/contract/v17.json +1025 -0
  39. figurant-0.1.0/figurant/contract/v18.json +1062 -0
  40. figurant-0.1.0/figurant/contract/v19.json +1068 -0
  41. figurant-0.1.0/figurant/contract/v2.json +474 -0
  42. figurant-0.1.0/figurant/contract/v20.json +1074 -0
  43. figurant-0.1.0/figurant/contract/v21.json +1080 -0
  44. figurant-0.1.0/figurant/contract/v22.json +1114 -0
  45. figurant-0.1.0/figurant/contract/v23.json +1155 -0
  46. figurant-0.1.0/figurant/contract/v24.json +1155 -0
  47. figurant-0.1.0/figurant/contract/v25.json +1155 -0
  48. figurant-0.1.0/figurant/contract/v26.json +1157 -0
  49. figurant-0.1.0/figurant/contract/v27.json +1192 -0
  50. figurant-0.1.0/figurant/contract/v28.json +1219 -0
  51. figurant-0.1.0/figurant/contract/v3.json +507 -0
  52. figurant-0.1.0/figurant/contract/v4.json +619 -0
  53. figurant-0.1.0/figurant/contract/v5.json +646 -0
  54. figurant-0.1.0/figurant/contract/v6.json +669 -0
  55. figurant-0.1.0/figurant/contract/v7.json +670 -0
  56. figurant-0.1.0/figurant/contract/v8.json +676 -0
  57. figurant-0.1.0/figurant/contract/v9.json +724 -0
  58. figurant-0.1.0/figurant/core/__init__.py +5 -0
  59. figurant-0.1.0/figurant/core/canonical.py +111 -0
  60. figurant-0.1.0/figurant/core/dates.py +107 -0
  61. figurant-0.1.0/figurant/core/draw.py +99 -0
  62. figurant-0.1.0/figurant/core/prf.py +88 -0
  63. figurant-0.1.0/figurant/core/spaces.py +88 -0
  64. figurant-0.1.0/figurant/core/surnames.py +140 -0
  65. figurant-0.1.0/figurant/export/__init__.py +338 -0
  66. figurant-0.1.0/figurant/export/formats.py +325 -0
  67. figurant-0.1.0/figurant/export/manifest.py +160 -0
  68. figurant-0.1.0/figurant/export/mock.py +256 -0
  69. figurant-0.1.0/figurant/export/render.py +360 -0
  70. figurant-0.1.0/figurant/export/replay.py +175 -0
  71. figurant-0.1.0/figurant/generate/__init__.py +11 -0
  72. figurant-0.1.0/figurant/generate/accounts.py +401 -0
  73. figurant-0.1.0/figurant/generate/affiliations.py +305 -0
  74. figurant-0.1.0/figurant/generate/assignments.py +252 -0
  75. figurant-0.1.0/figurant/generate/attributes.py +546 -0
  76. figurant-0.1.0/figurant/generate/contact.py +158 -0
  77. figurant-0.1.0/figurant/generate/drift.py +163 -0
  78. figurant-0.1.0/figurant/generate/fulfilment.py +258 -0
  79. figurant-0.1.0/figurant/generate/hierarchy.py +247 -0
  80. figurant-0.1.0/figurant/generate/movers.py +144 -0
  81. figurant-0.1.0/figurant/generate/names.py +721 -0
  82. figurant-0.1.0/figurant/generate/people.py +1016 -0
  83. figurant-0.1.0/figurant/generate/structure.py +1211 -0
  84. figurant-0.1.0/figurant/generate/systems.py +244 -0
  85. figurant-0.1.0/figurant/project/__init__.py +7 -0
  86. figurant-0.1.0/figurant/project/answerkey.py +996 -0
  87. figurant-0.1.0/figurant/project/appointments.py +417 -0
  88. figurant-0.1.0/figurant/project/asof.py +643 -0
  89. figurant-0.1.0/figurant/project/concurrent.py +259 -0
  90. figurant-0.1.0/figurant/project/deviations.py +84 -0
  91. figurant-0.1.0/figurant/project/dirt.py +841 -0
  92. figurant-0.1.0/figurant/project/edits.py +905 -0
  93. figurant-0.1.0/figurant/project/fallback.py +149 -0
  94. figurant-0.1.0/figurant/project/faultfamilies.py +1039 -0
  95. figurant-0.1.0/figurant/project/faults.py +505 -0
  96. figurant-0.1.0/figurant/project/futuremove.py +294 -0
  97. figurant-0.1.0/figurant/project/missing.py +194 -0
  98. figurant-0.1.0/figurant/project/naming.py +341 -0
  99. figurant-0.1.0/figurant/project/ownership.py +362 -0
  100. figurant-0.1.0/figurant/project/personal.py +605 -0
  101. figurant-0.1.0/figurant/project/precedence.py +105 -0
  102. figurant-0.1.0/figurant/project/renewals.py +302 -0
  103. figurant-0.1.0/figurant/project/reporting.py +424 -0
  104. figurant-0.1.0/figurant/project/resembling.py +379 -0
  105. figurant-0.1.0/figurant/project/scoring.py +494 -0
  106. figurant-0.1.0/figurant/project/teams.py +369 -0
  107. figurant-0.1.0/figurant/project/transitions.py +431 -0
  108. figurant-0.1.0/figurant/project/validity.py +398 -0
  109. figurant-0.1.0/figurant/scenario.py +794 -0
  110. figurant-0.1.0/figurant/select/__init__.py +1 -0
  111. figurant-0.1.0/figurant/select/cut.py +322 -0
  112. figurant-0.1.0/figurant/simulation.py +703 -0
  113. figurant-0.1.0/figurant.egg-info/PKG-INFO +122 -0
  114. figurant-0.1.0/figurant.egg-info/SOURCES.txt +168 -0
  115. figurant-0.1.0/figurant.egg-info/dependency_links.txt +1 -0
  116. figurant-0.1.0/figurant.egg-info/entry_points.txt +2 -0
  117. figurant-0.1.0/figurant.egg-info/requires.txt +8 -0
  118. figurant-0.1.0/figurant.egg-info/top_level.txt +1 -0
  119. figurant-0.1.0/pyproject.toml +123 -0
  120. figurant-0.1.0/setup.cfg +4 -0
  121. figurant-0.1.0/tests/test_affiliations.py +583 -0
  122. figurant-0.1.0/tests/test_answerkey.py +421 -0
  123. figurant-0.1.0/tests/test_appointments.py +787 -0
  124. figurant-0.1.0/tests/test_attributes.py +621 -0
  125. figurant-0.1.0/tests/test_bld2.py +526 -0
  126. figurant-0.1.0/tests/test_boundaries.py +779 -0
  127. figurant-0.1.0/tests/test_canonical.py +78 -0
  128. figurant-0.1.0/tests/test_cli.py +282 -0
  129. figurant-0.1.0/tests/test_complexity.py +140 -0
  130. figurant-0.1.0/tests/test_composition.py +451 -0
  131. figurant-0.1.0/tests/test_concurrent.py +382 -0
  132. figurant-0.1.0/tests/test_conformance.py +171 -0
  133. figurant-0.1.0/tests/test_contact.py +223 -0
  134. figurant-0.1.0/tests/test_contract.py +816 -0
  135. figurant-0.1.0/tests/test_cut.py +513 -0
  136. figurant-0.1.0/tests/test_delivery.py +549 -0
  137. figurant-0.1.0/tests/test_dirt.py +660 -0
  138. figurant-0.1.0/tests/test_docs.py +482 -0
  139. figurant-0.1.0/tests/test_draw.py +74 -0
  140. figurant-0.1.0/tests/test_drift.py +277 -0
  141. figurant-0.1.0/tests/test_edits.py +567 -0
  142. figurant-0.1.0/tests/test_faults.py +1025 -0
  143. figurant-0.1.0/tests/test_futuremove.py +295 -0
  144. figurant-0.1.0/tests/test_getting_started.py +225 -0
  145. figurant-0.1.0/tests/test_grants.py +473 -0
  146. figurant-0.1.0/tests/test_hierarchy.py +350 -0
  147. figurant-0.1.0/tests/test_import.py +196 -0
  148. figurant-0.1.0/tests/test_json_and_mock.py +368 -0
  149. figurant-0.1.0/tests/test_mapping.py +360 -0
  150. figurant-0.1.0/tests/test_missing.py +167 -0
  151. figurant-0.1.0/tests/test_movers.py +765 -0
  152. figurant-0.1.0/tests/test_namespace.py +529 -0
  153. figurant-0.1.0/tests/test_packs.py +291 -0
  154. figurant-0.1.0/tests/test_people.py +767 -0
  155. figurant-0.1.0/tests/test_personal.py +950 -0
  156. figurant-0.1.0/tests/test_populations.py +449 -0
  157. figurant-0.1.0/tests/test_rehire.py +655 -0
  158. figurant-0.1.0/tests/test_render.py +657 -0
  159. figurant-0.1.0/tests/test_renewals.py +397 -0
  160. figurant-0.1.0/tests/test_replay.py +563 -0
  161. figurant-0.1.0/tests/test_reporting.py +806 -0
  162. figurant-0.1.0/tests/test_reproducibility.py +370 -0
  163. figurant-0.1.0/tests/test_scenario.py +397 -0
  164. figurant-0.1.0/tests/test_scoring.py +553 -0
  165. figurant-0.1.0/tests/test_simulation.py +1090 -0
  166. figurant-0.1.0/tests/test_structure.py +1312 -0
  167. figurant-0.1.0/tests/test_systems.py +1121 -0
  168. figurant-0.1.0/tests/test_teams.py +606 -0
  169. figurant-0.1.0/tests/test_traceability.py +185 -0
  170. figurant-0.1.0/tests/test_transitions.py +738 -0
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: figurant
3
+ Version: 0.1.0
4
+ Summary: Synthetic organisational and identity data for testing identity governance
5
+ Keywords: synthetic-data,test-data,identity-governance,iam,hr,answer-key,reproducible
6
+ Classifier: Development Status :: 3 - Alpha
7
+ Classifier: Intended Audience :: Developers
8
+ Classifier: Intended Audience :: Information Technology
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Topic :: Software Development :: Testing
14
+ Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
15
+ Classifier: Operating System :: OS Independent
16
+ Requires-Python: <3.14,>=3.11
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: numpy<3,>=2.0
19
+ Requires-Dist: polars<3,>=1.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=8; extra == "dev"
22
+ Requires-Dist: pytest-xdist>=3; extra == "dev"
23
+ Requires-Dist: ruff>=0.6; extra == "dev"
24
+ Requires-Dist: complexipy>=3; extra == "dev"
25
+
26
+ # figurant — synthetic HR and identity data for testing identity governance
27
+
28
+ A reproducible generator of organisations that look like real ones: an org tree,
29
+ people with Dutch names and plausible ages, employments and registrations over a
30
+ history, accounts on several systems, and **an answer key** so a consuming system
31
+ can be *scored* rather than eyeballed.
32
+
33
+ It is an **exporter, not a connector**. It writes files that resemble what a source
34
+ system holds; it never reads from or writes into a live directory or HR system, and
35
+ it has no code path that can be pointed at real data. No organisation it produces is
36
+ recognisable as a real one, and no national identifier is produced in any mode.
37
+
38
+ ```bash
39
+ pip install -e .
40
+ figurant --scenario examples/acme-small.toml --out ./set
41
+ ```
42
+
43
+ That is a 400-person installer with its answer key, from a declaration you can read
44
+ in one screen. Three ship to start from: `examples/acme.toml` (the full one),
45
+ `examples/acme-small.toml` (the same organisation, smaller and faster) and
46
+ `examples/loop.toml` (four weeks of one organisation, for the simulation loop).
47
+
48
+ Nothing is on by default. A set is clean until a declaration asks for a fault, a
49
+ malformed value or a policy breach — so a clean set is the control that proves a
50
+ consumer is not reporting problems that are not there.
51
+
52
+ ## The sixty-second version
53
+
54
+ ```bash
55
+ # 1. Generate, and serve it where a consuming system can reach it
56
+ figurant --simulation examples/loop.toml --run ./run --reference 2026-10-12 --step 0
57
+ (cd run/current && python3 -m http.server 8080) # GET /json/index.json
58
+
59
+ # 2. Import it. Then say next, and import the same URL again
60
+ figurant --simulation examples/loop.toml --run ./run --reference 2026-10-12 --next
61
+
62
+ # 3. Reconcile backwards instead of reloading
63
+ figurant --simulation examples/loop.toml --run ./run --reference 2026-10-12 --revert
64
+ ```
65
+
66
+ `--reference` is the anchor every step counts back from, and it is required rather
67
+ than defaulted: nothing in the package reads the clock, so running the loop in a
68
+ later week is `--reference "$(date -I)"` at the call site and the whole loop shifts
69
+ with it — the same people, the same units, every event date moved.
70
+
71
+ `run/current/` is the same path at every step, so a consuming system is pointed at it
72
+ once and never reconfigured, and `run/feed/` holds what changed since the last step —
73
+ in both directions, so a revert is a reconciliation rather than a reload. Next week
74
+ the same declaration, anchored seven days later, replays the same situations against
75
+ the same organisation. See [the loop](docs/loop.md).
76
+
77
+ ## What you get
78
+
79
+ Sixteen tables — the organisation tree, people, employments over a history, teams,
80
+ and the access layer of systems, accounts, resources and assignments — written as
81
+ CSV, JSON pages, NDJSON, Parquet or a WireMock stub. Beside them, in `set/key/`, a
82
+ **separate** answer key: every planted fault with the value the data held before,
83
+ every malformed value and what it replaced, which personnel numbers belong to one
84
+ human, and the right answer for every copy of every attribute rather than only the
85
+ wrong ones. Full tables and formats in [the output](docs/output.md).
86
+
87
+ ## Where to go
88
+
89
+ | you want to… | read |
90
+ |---|---|
91
+ | start from nothing, write your own declaration, and share it so a colleague gets the same people | [getting started](docs/getting-started.md) |
92
+ | see a real set and what each part of it is for | [the walkthrough](docs/walkthrough.md) |
93
+ | decide which organisation comes out | [declaring an organisation](docs/organisations.md) |
94
+ | feed a system the same set over and over, with changes | [the loop](docs/loop.md) |
95
+ | ask for the situation you came to test | [scripting a case](docs/cases.md) |
96
+ | know what the files hold, and score a tool against the key | [the output](docs/output.md) |
97
+ | look up an option, a fault family or a dirt kind | [the reference](docs/reference.md) |
98
+ | know what every entity and field of the export holds | [the schema](docs/schema.md) |
99
+ | point a product's collector at a set, field by field | [the consumer mapping](docs/mapping.md) |
100
+ | load a set into a database | [importing](docs/import.md) |
101
+ | know what this tool will not do, and what to do instead | [for a consuming product](docs/consumers.md) |
102
+ | work on the tool itself | [development](docs/development.md) |
103
+
104
+ The walkthrough is the place to start if you have not seen a set before: it goes
105
+ through one, defect by defect, and says what each is a test of.
106
+
107
+ ## What it is built against
108
+
109
+ `docs/requirements.md` holds every requirement, each with how to verify it and —
110
+ where one was settled by measurement — the number that settled it.
111
+ `tests/TRACEABILITY.md` records which requirement each test covers, and
112
+ `tools/check_requirements.py` fails when one is covered by nothing.
113
+
114
+ [The case mapping](docs/conformance.md) records which set carries which scorable case, because the cases
115
+ live across sets rather than within one: a clean set is the control, so the faults are
116
+ in another.
117
+
118
+ Three more records exist because a claim nobody measured is a claim:
119
+ [platforms](docs/platforms.md) (the same set on Windows and Linux, hash for hash),
120
+ [importing](docs/import.md) (PostgreSQL accepting 46,290 rows with every foreign key
121
+ in place) and [the mutation score](docs/mutation.md) (what the suite detects when
122
+ faults are injected into the generator on purpose).
@@ -0,0 +1,97 @@
1
+ # figurant — synthetic HR and identity data for testing identity governance
2
+
3
+ A reproducible generator of organisations that look like real ones: an org tree,
4
+ people with Dutch names and plausible ages, employments and registrations over a
5
+ history, accounts on several systems, and **an answer key** so a consuming system
6
+ can be *scored* rather than eyeballed.
7
+
8
+ It is an **exporter, not a connector**. It writes files that resemble what a source
9
+ system holds; it never reads from or writes into a live directory or HR system, and
10
+ it has no code path that can be pointed at real data. No organisation it produces is
11
+ recognisable as a real one, and no national identifier is produced in any mode.
12
+
13
+ ```bash
14
+ pip install -e .
15
+ figurant --scenario examples/acme-small.toml --out ./set
16
+ ```
17
+
18
+ That is a 400-person installer with its answer key, from a declaration you can read
19
+ in one screen. Three ship to start from: `examples/acme.toml` (the full one),
20
+ `examples/acme-small.toml` (the same organisation, smaller and faster) and
21
+ `examples/loop.toml` (four weeks of one organisation, for the simulation loop).
22
+
23
+ Nothing is on by default. A set is clean until a declaration asks for a fault, a
24
+ malformed value or a policy breach — so a clean set is the control that proves a
25
+ consumer is not reporting problems that are not there.
26
+
27
+ ## The sixty-second version
28
+
29
+ ```bash
30
+ # 1. Generate, and serve it where a consuming system can reach it
31
+ figurant --simulation examples/loop.toml --run ./run --reference 2026-10-12 --step 0
32
+ (cd run/current && python3 -m http.server 8080) # GET /json/index.json
33
+
34
+ # 2. Import it. Then say next, and import the same URL again
35
+ figurant --simulation examples/loop.toml --run ./run --reference 2026-10-12 --next
36
+
37
+ # 3. Reconcile backwards instead of reloading
38
+ figurant --simulation examples/loop.toml --run ./run --reference 2026-10-12 --revert
39
+ ```
40
+
41
+ `--reference` is the anchor every step counts back from, and it is required rather
42
+ than defaulted: nothing in the package reads the clock, so running the loop in a
43
+ later week is `--reference "$(date -I)"` at the call site and the whole loop shifts
44
+ with it — the same people, the same units, every event date moved.
45
+
46
+ `run/current/` is the same path at every step, so a consuming system is pointed at it
47
+ once and never reconfigured, and `run/feed/` holds what changed since the last step —
48
+ in both directions, so a revert is a reconciliation rather than a reload. Next week
49
+ the same declaration, anchored seven days later, replays the same situations against
50
+ the same organisation. See [the loop](docs/loop.md).
51
+
52
+ ## What you get
53
+
54
+ Sixteen tables — the organisation tree, people, employments over a history, teams,
55
+ and the access layer of systems, accounts, resources and assignments — written as
56
+ CSV, JSON pages, NDJSON, Parquet or a WireMock stub. Beside them, in `set/key/`, a
57
+ **separate** answer key: every planted fault with the value the data held before,
58
+ every malformed value and what it replaced, which personnel numbers belong to one
59
+ human, and the right answer for every copy of every attribute rather than only the
60
+ wrong ones. Full tables and formats in [the output](docs/output.md).
61
+
62
+ ## Where to go
63
+
64
+ | you want to… | read |
65
+ |---|---|
66
+ | start from nothing, write your own declaration, and share it so a colleague gets the same people | [getting started](docs/getting-started.md) |
67
+ | see a real set and what each part of it is for | [the walkthrough](docs/walkthrough.md) |
68
+ | decide which organisation comes out | [declaring an organisation](docs/organisations.md) |
69
+ | feed a system the same set over and over, with changes | [the loop](docs/loop.md) |
70
+ | ask for the situation you came to test | [scripting a case](docs/cases.md) |
71
+ | know what the files hold, and score a tool against the key | [the output](docs/output.md) |
72
+ | look up an option, a fault family or a dirt kind | [the reference](docs/reference.md) |
73
+ | know what every entity and field of the export holds | [the schema](docs/schema.md) |
74
+ | point a product's collector at a set, field by field | [the consumer mapping](docs/mapping.md) |
75
+ | load a set into a database | [importing](docs/import.md) |
76
+ | know what this tool will not do, and what to do instead | [for a consuming product](docs/consumers.md) |
77
+ | work on the tool itself | [development](docs/development.md) |
78
+
79
+ The walkthrough is the place to start if you have not seen a set before: it goes
80
+ through one, defect by defect, and says what each is a test of.
81
+
82
+ ## What it is built against
83
+
84
+ `docs/requirements.md` holds every requirement, each with how to verify it and —
85
+ where one was settled by measurement — the number that settled it.
86
+ `tests/TRACEABILITY.md` records which requirement each test covers, and
87
+ `tools/check_requirements.py` fails when one is covered by nothing.
88
+
89
+ [The case mapping](docs/conformance.md) records which set carries which scorable case, because the cases
90
+ live across sets rather than within one: a clean set is the control, so the faults are
91
+ in another.
92
+
93
+ Three more records exist because a claim nobody measured is a claim:
94
+ [platforms](docs/platforms.md) (the same set on Windows and Linux, hash for hash),
95
+ [importing](docs/import.md) (PostgreSQL accepting 46,290 rows with every foreign key
96
+ in place) and [the mutation score](docs/mutation.md) (what the suite detects when
97
+ faults are injected into the generator on purpose).
@@ -0,0 +1,12 @@
1
+ """Synthetic organisational and identity data for testing identity governance.
2
+
3
+ This file exists so that ``figurant`` is a regular package rather than an implicit
4
+ namespace one. Without it, ``import figurant`` merges EVERY ``figurant`` directory on
5
+ ``sys.path`` into one package - which is how a wheel verification here first
6
+ read half its modules from the built wheel and half from the source tree,
7
+ unnoticed, because both portions answered. A regular package takes the first
8
+ match and stops.
9
+
10
+ Nothing is imported here on purpose: ``figurant.contract`` and ``figurant.bundle``
11
+ cost real time to import, and the CLI should pay for only what it uses.
12
+ """