@ossiq/cli-darwin-x64 0.1.12

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 (131) hide show
  1. package/bin/ossiq/_internal/Python.framework/Versions/3.13/Python +0 -0
  2. package/bin/ossiq/_internal/Python.framework/Versions/3.13/Resources/Info.plist +30 -0
  3. package/bin/ossiq/_internal/attrs-26.1.0.dist-info/INSTALLER +1 -0
  4. package/bin/ossiq/_internal/attrs-26.1.0.dist-info/METADATA +199 -0
  5. package/bin/ossiq/_internal/attrs-26.1.0.dist-info/RECORD +37 -0
  6. package/bin/ossiq/_internal/attrs-26.1.0.dist-info/REQUESTED +0 -0
  7. package/bin/ossiq/_internal/attrs-26.1.0.dist-info/WHEEL +4 -0
  8. package/bin/ossiq/_internal/attrs-26.1.0.dist-info/licenses/LICENSE +21 -0
  9. package/bin/ossiq/_internal/base_library.zip +0 -0
  10. package/bin/ossiq/_internal/cel/cel.cpython-313-darwin.so +0 -0
  11. package/bin/ossiq/_internal/certifi/cacert.pem +3959 -0
  12. package/bin/ossiq/_internal/certifi/py.typed +0 -0
  13. package/bin/ossiq/_internal/charset_normalizer/cd.cpython-313-darwin.so +0 -0
  14. package/bin/ossiq/_internal/charset_normalizer/md.cpython-313-darwin.so +0 -0
  15. package/bin/ossiq/_internal/common_expression_language-0.10.0.dist-info/INSTALLER +1 -0
  16. package/bin/ossiq/_internal/common_expression_language-0.10.0.dist-info/METADATA +261 -0
  17. package/bin/ossiq/_internal/common_expression_language-0.10.0.dist-info/RECORD +16 -0
  18. package/bin/ossiq/_internal/common_expression_language-0.10.0.dist-info/REQUESTED +0 -0
  19. package/bin/ossiq/_internal/common_expression_language-0.10.0.dist-info/WHEEL +4 -0
  20. package/bin/ossiq/_internal/common_expression_language-0.10.0.dist-info/entry_points.txt +2 -0
  21. package/bin/ossiq/_internal/common_expression_language-0.10.0.dist-info/licenses/LICENSE +201 -0
  22. package/bin/ossiq/_internal/common_expression_language-0.10.0.dist-info/sboms/cel.cyclonedx.json +2109 -0
  23. package/bin/ossiq/_internal/libcrypto.3.dylib +0 -0
  24. package/bin/ossiq/_internal/libncurses.6.dylib +0 -0
  25. package/bin/ossiq/_internal/libssl.3.dylib +0 -0
  26. package/bin/ossiq/_internal/ossiq/data/SKILL.md +313 -0
  27. package/bin/ossiq/_internal/ossiq/domain/README.md +373 -0
  28. package/bin/ossiq/_internal/ossiq/solver/README.md +155 -0
  29. package/bin/ossiq/_internal/ossiq/strategy/README.md +182 -0
  30. package/bin/ossiq/_internal/ossiq/ui/html_templates/filter_format_highlight_days.html +7 -0
  31. package/bin/ossiq/_internal/ossiq/ui/html_templates/spa_app.html +18125 -0
  32. package/bin/ossiq/_internal/ossiq/ui/html_templates/tag_versions_difference.html +33 -0
  33. package/bin/ossiq/_internal/ossiq/ui/renderers/export/README.md +207 -0
  34. package/bin/ossiq/_internal/ossiq/ui/renderers/export/schemas/export_schema_v1.5.json +1637 -0
  35. package/bin/ossiq/_internal/ossiq/ui/renderers/export/schemas/export_schema_v1.5_standard.json +1164 -0
  36. package/bin/ossiq/_internal/ossiq-0.1.12.dist-info/INSTALLER +1 -0
  37. package/bin/ossiq/_internal/ossiq-0.1.12.dist-info/METADATA +385 -0
  38. package/bin/ossiq/_internal/ossiq-0.1.12.dist-info/RECORD +13 -0
  39. package/bin/ossiq/_internal/ossiq-0.1.12.dist-info/REQUESTED +0 -0
  40. package/bin/ossiq/_internal/ossiq-0.1.12.dist-info/WHEEL +4 -0
  41. package/bin/ossiq/_internal/ossiq-0.1.12.dist-info/direct_url.json +1 -0
  42. package/bin/ossiq/_internal/ossiq-0.1.12.dist-info/entry_points.txt +2 -0
  43. package/bin/ossiq/_internal/ossiq-0.1.12.dist-info/licenses/AUTHORS +8 -0
  44. package/bin/ossiq/_internal/ossiq-0.1.12.dist-info/licenses/LICENSE +661 -0
  45. package/bin/ossiq/_internal/ossiq-0.1.12.dist-info/uv_build.json +1 -0
  46. package/bin/ossiq/_internal/ossiq-0.1.12.dist-info/uv_cache.json +1 -0
  47. package/bin/ossiq/_internal/prompt_toolkit-3.0.53.dist-info/INSTALLER +1 -0
  48. package/bin/ossiq/_internal/prompt_toolkit-3.0.53.dist-info/METADATA +171 -0
  49. package/bin/ossiq/_internal/prompt_toolkit-3.0.53.dist-info/RECORD +154 -0
  50. package/bin/ossiq/_internal/prompt_toolkit-3.0.53.dist-info/REQUESTED +0 -0
  51. package/bin/ossiq/_internal/prompt_toolkit-3.0.53.dist-info/WHEEL +5 -0
  52. package/bin/ossiq/_internal/prompt_toolkit-3.0.53.dist-info/licenses/AUTHORS.rst +11 -0
  53. package/bin/ossiq/_internal/prompt_toolkit-3.0.53.dist-info/licenses/LICENSE +27 -0
  54. package/bin/ossiq/_internal/prompt_toolkit-3.0.53.dist-info/top_level.txt +1 -0
  55. package/bin/ossiq/_internal/pycard.cpython-313-darwin.so +0 -0
  56. package/bin/ossiq/_internal/pydantic-2.13.5.dist-info/INSTALLER +1 -0
  57. package/bin/ossiq/_internal/pydantic-2.13.5.dist-info/METADATA +1307 -0
  58. package/bin/ossiq/_internal/pydantic-2.13.5.dist-info/RECORD +113 -0
  59. package/bin/ossiq/_internal/pydantic-2.13.5.dist-info/REQUESTED +0 -0
  60. package/bin/ossiq/_internal/pydantic-2.13.5.dist-info/WHEEL +4 -0
  61. package/bin/ossiq/_internal/pydantic-2.13.5.dist-info/licenses/LICENSE +21 -0
  62. package/bin/ossiq/_internal/pydantic_core/_pydantic_core.cpython-313-darwin.so +0 -0
  63. package/bin/ossiq/_internal/pyformula.cpython-313-darwin.so +0 -0
  64. package/bin/ossiq/_internal/pysolvers.cpython-313-darwin.so +0 -0
  65. package/bin/ossiq/_internal/python3.13/lib-dynload/_asyncio.cpython-313-darwin.so +0 -0
  66. package/bin/ossiq/_internal/python3.13/lib-dynload/_bisect.cpython-313-darwin.so +0 -0
  67. package/bin/ossiq/_internal/python3.13/lib-dynload/_blake2.cpython-313-darwin.so +0 -0
  68. package/bin/ossiq/_internal/python3.13/lib-dynload/_bz2.cpython-313-darwin.so +0 -0
  69. package/bin/ossiq/_internal/python3.13/lib-dynload/_codecs_cn.cpython-313-darwin.so +0 -0
  70. package/bin/ossiq/_internal/python3.13/lib-dynload/_codecs_hk.cpython-313-darwin.so +0 -0
  71. package/bin/ossiq/_internal/python3.13/lib-dynload/_codecs_iso2022.cpython-313-darwin.so +0 -0
  72. package/bin/ossiq/_internal/python3.13/lib-dynload/_codecs_jp.cpython-313-darwin.so +0 -0
  73. package/bin/ossiq/_internal/python3.13/lib-dynload/_codecs_kr.cpython-313-darwin.so +0 -0
  74. package/bin/ossiq/_internal/python3.13/lib-dynload/_codecs_tw.cpython-313-darwin.so +0 -0
  75. package/bin/ossiq/_internal/python3.13/lib-dynload/_contextvars.cpython-313-darwin.so +0 -0
  76. package/bin/ossiq/_internal/python3.13/lib-dynload/_csv.cpython-313-darwin.so +0 -0
  77. package/bin/ossiq/_internal/python3.13/lib-dynload/_ctypes.cpython-313-darwin.so +0 -0
  78. package/bin/ossiq/_internal/python3.13/lib-dynload/_curses.cpython-313-darwin.so +0 -0
  79. package/bin/ossiq/_internal/python3.13/lib-dynload/_datetime.cpython-313-darwin.so +0 -0
  80. package/bin/ossiq/_internal/python3.13/lib-dynload/_decimal.cpython-313-darwin.so +0 -0
  81. package/bin/ossiq/_internal/python3.13/lib-dynload/_hashlib.cpython-313-darwin.so +0 -0
  82. package/bin/ossiq/_internal/python3.13/lib-dynload/_heapq.cpython-313-darwin.so +0 -0
  83. package/bin/ossiq/_internal/python3.13/lib-dynload/_json.cpython-313-darwin.so +0 -0
  84. package/bin/ossiq/_internal/python3.13/lib-dynload/_lzma.cpython-313-darwin.so +0 -0
  85. package/bin/ossiq/_internal/python3.13/lib-dynload/_md5.cpython-313-darwin.so +0 -0
  86. package/bin/ossiq/_internal/python3.13/lib-dynload/_multibytecodec.cpython-313-darwin.so +0 -0
  87. package/bin/ossiq/_internal/python3.13/lib-dynload/_multiprocessing.cpython-313-darwin.so +0 -0
  88. package/bin/ossiq/_internal/python3.13/lib-dynload/_opcode.cpython-313-darwin.so +0 -0
  89. package/bin/ossiq/_internal/python3.13/lib-dynload/_pickle.cpython-313-darwin.so +0 -0
  90. package/bin/ossiq/_internal/python3.13/lib-dynload/_posixshmem.cpython-313-darwin.so +0 -0
  91. package/bin/ossiq/_internal/python3.13/lib-dynload/_posixsubprocess.cpython-313-darwin.so +0 -0
  92. package/bin/ossiq/_internal/python3.13/lib-dynload/_queue.cpython-313-darwin.so +0 -0
  93. package/bin/ossiq/_internal/python3.13/lib-dynload/_random.cpython-313-darwin.so +0 -0
  94. package/bin/ossiq/_internal/python3.13/lib-dynload/_scproxy.cpython-313-darwin.so +0 -0
  95. package/bin/ossiq/_internal/python3.13/lib-dynload/_sha1.cpython-313-darwin.so +0 -0
  96. package/bin/ossiq/_internal/python3.13/lib-dynload/_sha2.cpython-313-darwin.so +0 -0
  97. package/bin/ossiq/_internal/python3.13/lib-dynload/_sha3.cpython-313-darwin.so +0 -0
  98. package/bin/ossiq/_internal/python3.13/lib-dynload/_socket.cpython-313-darwin.so +0 -0
  99. package/bin/ossiq/_internal/python3.13/lib-dynload/_sqlite3.cpython-313-darwin.so +0 -0
  100. package/bin/ossiq/_internal/python3.13/lib-dynload/_ssl.cpython-313-darwin.so +0 -0
  101. package/bin/ossiq/_internal/python3.13/lib-dynload/_statistics.cpython-313-darwin.so +0 -0
  102. package/bin/ossiq/_internal/python3.13/lib-dynload/_struct.cpython-313-darwin.so +0 -0
  103. package/bin/ossiq/_internal/python3.13/lib-dynload/_uuid.cpython-313-darwin.so +0 -0
  104. package/bin/ossiq/_internal/python3.13/lib-dynload/_zoneinfo.cpython-313-darwin.so +0 -0
  105. package/bin/ossiq/_internal/python3.13/lib-dynload/array.cpython-313-darwin.so +0 -0
  106. package/bin/ossiq/_internal/python3.13/lib-dynload/binascii.cpython-313-darwin.so +0 -0
  107. package/bin/ossiq/_internal/python3.13/lib-dynload/fcntl.cpython-313-darwin.so +0 -0
  108. package/bin/ossiq/_internal/python3.13/lib-dynload/grp.cpython-313-darwin.so +0 -0
  109. package/bin/ossiq/_internal/python3.13/lib-dynload/math.cpython-313-darwin.so +0 -0
  110. package/bin/ossiq/_internal/python3.13/lib-dynload/mmap.cpython-313-darwin.so +0 -0
  111. package/bin/ossiq/_internal/python3.13/lib-dynload/pyexpat.cpython-313-darwin.so +0 -0
  112. package/bin/ossiq/_internal/python3.13/lib-dynload/readline.cpython-313-darwin.so +0 -0
  113. package/bin/ossiq/_internal/python3.13/lib-dynload/resource.cpython-313-darwin.so +0 -0
  114. package/bin/ossiq/_internal/python3.13/lib-dynload/select.cpython-313-darwin.so +0 -0
  115. package/bin/ossiq/_internal/python3.13/lib-dynload/syslog.cpython-313-darwin.so +0 -0
  116. package/bin/ossiq/_internal/python3.13/lib-dynload/termios.cpython-313-darwin.so +0 -0
  117. package/bin/ossiq/_internal/python3.13/lib-dynload/unicodedata.cpython-313-darwin.so +0 -0
  118. package/bin/ossiq/_internal/python3.13/lib-dynload/zlib.cpython-313-darwin.so +0 -0
  119. package/bin/ossiq/_internal/semantic_version-2.10.0.dist-info/INSTALLER +1 -0
  120. package/bin/ossiq/_internal/semantic_version-2.10.0.dist-info/LICENSE +22 -0
  121. package/bin/ossiq/_internal/semantic_version-2.10.0.dist-info/METADATA +324 -0
  122. package/bin/ossiq/_internal/semantic_version-2.10.0.dist-info/RECORD +10 -0
  123. package/bin/ossiq/_internal/semantic_version-2.10.0.dist-info/REQUESTED +0 -0
  124. package/bin/ossiq/_internal/semantic_version-2.10.0.dist-info/WHEEL +6 -0
  125. package/bin/ossiq/_internal/semantic_version-2.10.0.dist-info/top_level.txt +1 -0
  126. package/bin/ossiq/_internal/setuptools/_vendor/jaraco/text/Lorem ipsum.txt +2 -0
  127. package/bin/ossiq/_internal/wcwidth/py.typed +0 -0
  128. package/bin/ossiq/_internal/wcwidth/textwrap.py,cover +664 -0
  129. package/bin/ossiq/_internal/yaml/_yaml.cpython-313-darwin.so +0 -0
  130. package/bin/ossiq/ossiq +0 -0
  131. package/package.json +24 -0
@@ -0,0 +1,373 @@
1
+ # Domain Model Specifications
2
+
3
+ This document lists the external specifications that define or inform the domain models in this package.
4
+
5
+ ---
6
+
7
+ ## Registry Health Fields
8
+
9
+ `Package` and `PackageVersion` carry explicit flags for the two ways a package can become unavailable or inadvisable to use. These are set by the registry adapters and surfaced through `ScanRecord` into all export formats and UI renderers.
10
+
11
+ ### `Package` flags
12
+
13
+ | Field | Registry | Meaning |
14
+ |-------|----------|---------|
15
+ | `is_deprecated: bool` | npm | The package's latest version carries a `deprecated` field — the whole package is considered deprecated. Set via `npm deprecate <pkg>`. |
16
+ | `is_unpublished: bool` | npm | `time.unpublished` is present in the packument — the entire package was removed from the registry. |
17
+ | `canonical_name: str \| None` | npm | When a package is installed under an npm alias (e.g. `"chalk-legacy": "npm:chalk@4"`), `name` is the alias and `canonical_name` is the real registry name used for lookups. `None` when `name` already is the canonical registry name. |
18
+
19
+ ### `PackageVersion` flags
20
+
21
+ | Field | Registry | Meaning |
22
+ |-------|----------|---------|
23
+ | `is_deprecated: bool` | npm | This specific version's manifest has a truthy `deprecated` field. |
24
+ | `is_yanked: bool` | PyPI | The version exists on PyPI but is marked yanked — excluded from normal resolution. |
25
+ | `is_unpublished: bool` | npm | The version was individually deleted after release (present in `time` but absent from `versions`), or the entire package was unpublished (all versions inherit this flag). |
26
+ | `is_prerelease: bool` | npm / PyPI | Version string contains a pre-release segment (e.g. `alpha`, `beta`, `rc`, `dev`). Set by registry adapters during version list construction. |
27
+
28
+ ### `ScanRecord` derived flags
29
+
30
+ `ScanRecord` lives in `service/project.py` (not a domain entity). Its status flags are derived from `Package` and `PackageVersion` at scan time:
31
+
32
+ | Field | Derived from | Meaning |
33
+ |-------|-------------|---------|
34
+ | `is_installed_prerelease` | `version.is_prerelease` | Installed version is a pre-release. |
35
+ | `is_installed_yanked` | `version.is_yanked or version.is_unpublished` | Installed version was pulled from the registry (covers both PyPI yanked and npm unpublished). |
36
+ | `is_installed_deprecated` | `version.is_deprecated or package.is_deprecated` | Installed version or its parent package is deprecated. |
37
+ | `is_installed_package_unpublished` | `package.is_unpublished` | The entire package has been removed from the registry. |
38
+
39
+ Display priority in all renderers: **UNPUBLISHED** > **YANKED** > **DEPRECATED** > pre-release.
40
+
41
+ ---
42
+
43
+ ## Versioning
44
+
45
+ ### Semantic Versioning (SemVer)
46
+ **Spec**: https://semver.org/
47
+ **Applies to**: `Version`, `PackageVersion`, `RepositoryVersion`, `normalize_version()`
48
+
49
+ The foundational versioning scheme used by both npm and (loosely) Python packaging. A version takes the form `MAJOR.MINOR.PATCH[-pre-release][+build]`. The `VersionsDifference` model maps version drift to one of: `DIFF_MAJOR`, `DIFF_MINOR`, `DIFF_PATCH`, `DIFF_PRERELEASE`, `DIFF_BUILD`.
50
+
51
+ ### PEP 440 — Python Version Identification
52
+ **Spec**: https://peps.python.org/pep-0440/
53
+ **Applies to**: `normalize_version()`, `PackageVersion.version`
54
+
55
+ Python's versioning scheme. Supports epochs (`1!1.0`), pre-releases (`1.0a1`, `1.0rc1`), post-releases (`1.0.post1`), dev releases (`1.0.dev1`), and local versions (`1.0+local`). The `normalize_version()` function strips PEP 440 operators (`==`, `>=`, `~=`, etc.) to extract a bare version string.
56
+
57
+ ---
58
+
59
+ ## Version Constraints
60
+
61
+ ### Python — Dependency Specifiers (PEP 508)
62
+ **Spec**: https://packaging.python.org/en/latest/specifications/dependency-specifiers/
63
+ **Applies to**: `Dependency.version_defined`, `PackageVersion.version_constraint`
64
+
65
+ Defines the syntax for expressing Python package requirements:
66
+
67
+ | Operator | Meaning |
68
+ |----------|---------|
69
+ | `==` | Exact match (supports `.*` wildcard) |
70
+ | `!=` | Exclusion |
71
+ | `>=`, `<=`, `>`, `<` | Inclusive/exclusive bounds |
72
+ | `~=` | Compatible release (`~=1.4` means `>=1.4,<2`) |
73
+ | `===` | Arbitrary equality (string match, discouraged) |
74
+
75
+ Multiple constraints are combined with commas: `>=1.2,<2.0,!=1.3`.
76
+
77
+ Extras are specified in brackets: `requests[security,tests]>=2.28`.
78
+
79
+ Environment markers make dependencies conditional:
80
+ ```
81
+ argparse; python_version < "2.7"
82
+ pywin32; sys_platform == "win32"
83
+ ```
84
+
85
+ Available marker variables: `python_version`, `python_full_version`, `os_name`, `sys_platform`, `platform_machine`, `platform_python_implementation`, `implementation_name`, `implementation_version`, `extra`.
86
+
87
+ ### npm — Semver Ranges
88
+ **Spec**: https://docs.npmjs.com/cli/v11/configuring-npm/package-json#dependencies
89
+ **Applies to**: `Dependency.version_defined`, `PackageVersion.version_constraint`
90
+
91
+ npm version constraints use the [node-semver](https://github.com/npm/node-semver) library:
92
+
93
+ | Syntax | Meaning |
94
+ |--------|---------|
95
+ | `1.2.3` | Exact version |
96
+ | `>1.2.3`, `>=1.2.3`, `<2.0.0`, `<=2.0.0` | Comparators |
97
+ | `^1.2.3` | Compatible with (same major): `>=1.2.3 <2.0.0` |
98
+ | `~1.2.3` | Approximately equivalent (same minor): `>=1.2.3 <1.3.0` |
99
+ | `1.2.x` or `1.2.*` | Any patch in minor |
100
+ | `*` or `""` | Any version |
101
+ | `1.2.3 - 2.0.0` | Hyphen range (inclusive) |
102
+ | `1.0.0 \|\| 2.0.0` | Union of ranges |
103
+
104
+ Non-registry forms (stored in `Dependency.source`): git URLs, GitHub shorthand (`user/repo`), local paths (`file:../pkg`), and tarball URLs.
105
+
106
+ ### `ConstraintType` — domain classification
107
+
108
+ Both specifier classifiers (`classify_npm_specifier`, `classify_pypi_specifier` in `version.py`) map a raw specifier string to a `ConstraintType` (defined in `common.py`):
109
+
110
+ | Value | Applies to | Meaning |
111
+ |-------|-----------|---------|
112
+ | `DECLARED` | both | Loose/default constraint — any, `^x`, `~x`, `>=x` (lower-bound only), `*`, `latest`. |
113
+ | `NARROWED` | both | Explicit bounded range — `>=x <y`, `~=x`, `==x.*`, compound `,`, any `<`/`<=`. |
114
+ | `PINNED` | both | Exactly one version — `==x.y.z` (PyPI) or bare `x.y.z` (npm). |
115
+ | `ADDITIVE` | PyPI | Narrows range without adding a direct dependency (`pip -c`, `uv constraint-dependencies`). |
116
+ | `OVERRIDE` | both | Completely replaces resolver output (`npm overrides`, `uv override-dependencies`). |
117
+
118
+ Priority ordering for display: **OVERRIDE** > **ADDITIVE** > **PINNED** > **NARROWED** > **DECLARED**.
119
+
120
+ ### `ConstraintSource` — constraint provenance
121
+
122
+ `ConstraintSource` (in `project.py`) records how a constraint was introduced:
123
+
124
+ | Field | Type | Meaning |
125
+ |-------|------|---------|
126
+ | `type` | `ConstraintType` | Classification of the constraint (see above). |
127
+ | `source_file` | `str \| None` | The file that declared it (e.g. `"package.json"`, `"pyproject.toml"`). |
128
+ | `scope_path` | `list[str] \| None` | For npm nested overrides, the path through the dependency tree; `None` for flat constraints. |
129
+
130
+ ### `PeerRequirement` — peer constraint tracking
131
+
132
+ `PeerRequirement` (in `project.py`) represents a peer dependency constraint that another installed package places on this one:
133
+
134
+ | Field | Type | Meaning |
135
+ |-------|------|---------|
136
+ | `requirer_name` | `str` | The package that declared the peer requirement. |
137
+ | `spec` | `str` | The version specifier it requires (e.g. `">=18.0.0"`). |
138
+
139
+ Violations (installed version not satisfying `spec`) are surfaced in `ScanRecord.peer_violations`.
140
+
141
+ ---
142
+
143
+ ## Dependency Categories
144
+
145
+ ### npm — Dependency Fields
146
+ **Spec**: https://docs.npmjs.com/cli/v11/configuring-npm/package-json#devdependencies
147
+ **Applies to**: `Dependency.categories`
148
+
149
+ | Field | Category value | Meaning |
150
+ |-------|---------------|---------|
151
+ | `dependencies` | _(none / default)_ | Required at runtime |
152
+ | `devDependencies` | `development` | Development/build tooling only |
153
+ | `peerDependencies` | `peer` | Must be provided by the consuming project |
154
+ | `optionalDependencies` | `optional` | Non-fatal if missing; overlaps with `dependencies` |
155
+ | `bundleDependencies` | `bundled` | Included in the package tarball on publish |
156
+
157
+ `peerDependenciesMeta` can mark individual peer deps as `optional: true`.
158
+
159
+ ### Python — Optional Dependencies (Extras)
160
+ **Spec**: https://packaging.python.org/en/latest/specifications/pyproject-toml/#project-optional-dependencies
161
+ **Applies to**: `Dependency.categories`
162
+
163
+ Declared under `[project.optional-dependencies]` in `pyproject.toml`. Keys are extra names (e.g., `dev`, `test`, `docs`). These become part of package metadata and are installable via `pip install pkg[extra]`. Stored in `Dependency.categories` as the extra name.
164
+
165
+ ### Python — Dependency Groups (PEP 735)
166
+ **Spec**: https://packaging.python.org/en/latest/specifications/dependency-groups/
167
+ **Applies to**: `Dependency.categories`
168
+
169
+ Declared under `[dependency-groups]` in `pyproject.toml`. Unlike `optional-dependencies`, groups are **not** included in built distribution metadata — they are strictly for local development tooling. Groups support `{include-group = "other"}` for composition.
170
+
171
+ ```toml
172
+ [dependency-groups]
173
+ test = ["pytest>7", "coverage"]
174
+ docs = ["sphinx", {include-group = "test"}]
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Manifest Files
180
+
181
+ ### pyproject.toml
182
+ **Spec**: https://packaging.python.org/en/latest/specifications/pyproject-toml/
183
+ **Applies to**: `Manifest`, `PackageManagerType` (UV, POETRY, PDM, PIP)
184
+
185
+ The standard Python project manifest. Key tables for dependency management:
186
+
187
+ | Table | Purpose |
188
+ |-------|---------|
189
+ | `[build-system]` | Build backend declaration (`requires`, `build-backend`) |
190
+ | `[project]` | Core metadata: `name`, `version`, `dependencies`, `optional-dependencies` |
191
+ | `[dependency-groups]` | Dev-only dependency groups (PEP 735) |
192
+ | `[tool.uv]` | uv-specific configuration |
193
+ | `[tool.poetry.dependencies]` | Poetry dependency declarations |
194
+
195
+ ### package.json
196
+ **Spec**: https://docs.npmjs.com/cli/v11/configuring-npm/package-json
197
+ **Applies to**: `Manifest`, `PackageManagerType` (NPM, YARN, PNPM)
198
+
199
+ The npm ecosystem manifest. Shared by npm, Yarn, and pnpm. Relevant fields: `name`, `version`, `dependencies`, `devDependencies`, `peerDependencies`, `optionalDependencies`, `bundleDependencies`, `engines`.
200
+
201
+ ### requirements.txt
202
+ **Spec**: https://pip.pypa.io/en/stable/reference/requirements-file-format/
203
+ **Applies to**: `Manifest` (PIP_CLASSIC)
204
+
205
+ Legacy pip format. One requirement per line using PEP 508 syntax, with pip-specific options (`-r`, `-c`, `-e`, `--index-url`). No standard for extras or groups. Used by `PIP_CLASSIC` package manager type.
206
+
207
+ ---
208
+
209
+ ## Lockfiles
210
+
211
+ ### pylock.toml (PEP 751)
212
+ **Spec**: https://packaging.python.org/en/latest/specifications/pylock-toml/
213
+ **Applies to**: `Lockfile` (PIP)
214
+
215
+ The emerging standard Python lockfile format. Key structure:
216
+
217
+ ```toml
218
+ lock-version = "1.0"
219
+ created-by = "uv 0.5.0"
220
+ requires-python = ">=3.11"
221
+
222
+ [[packages]]
223
+ name = "requests"
224
+ version = "2.31.0"
225
+ marker = "python_version >= '3.8'"
226
+ ```
227
+
228
+ Each `[[packages]]` entry may have a mutually exclusive source: `[packages.sdist]`, `[[packages.wheels]]`, `[packages.vcs]`, `[packages.directory]`, or `[packages.archive]`. Cryptographic hashes (SHA-256) are required for reproducibility.
229
+
230
+ ### uv.lock
231
+ **Spec**: https://docs.astral.sh/uv/concepts/resolution/#lockfile
232
+ **Applies to**: `Lockfile` (UV)
233
+
234
+ uv's proprietary TOML lockfile. Uses CEL-based version parsing for format compatibility. Contains resolved package versions with hashes and source information.
235
+
236
+ ### poetry.lock
237
+ **Spec**: https://python-poetry.org/docs/basic-usage/#installing-with-poetrylock
238
+ **Applies to**: `Lockfile` (POETRY)
239
+
240
+ Poetry's TOML lockfile. Contains `[[package]]` entries with `name`, `version`, `description`, `category`, `optional`, `python-versions`, and `dependencies`.
241
+
242
+ ### package-lock.json
243
+ **Spec**: https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json
244
+ **Applies to**: `Lockfile` (NPM)
245
+
246
+ npm's lockfile. Supports `lockfileVersion` 1, 2, and 3. Version 3 (npm 7+) uses a flat `packages` map keyed by `node_modules/` path:
247
+
248
+ | Field | Meaning |
249
+ |-------|---------|
250
+ | `version` | Exact resolved version |
251
+ | `resolved` | Download URL |
252
+ | `integrity` | SRI hash (`sha512-...`) |
253
+ | `dev` | `true` if dev dependency |
254
+ | `optional` | `true` if optional |
255
+ | `peer` | `true` if peer dependency |
256
+ | `dependencies` | Nested overrides (v1 compat) |
257
+
258
+ ### yarn.lock
259
+ **Spec**: https://yarnpkg.com/features/lock-protocol
260
+ **Applies to**: `Lockfile` (YARN)
261
+
262
+ Yarn's custom YAML-like lockfile. Yarn v1 uses its own format; Yarn v2+ (Berry) uses a stricter YAML format with `__metadata` and `cacheKey`.
263
+
264
+ ### pnpm-lock.yaml
265
+ **Spec**: https://pnpm.io/lockfile
266
+ **Applies to**: `Lockfile` (PNPM)
267
+
268
+ pnpm's YAML lockfile. Uses a content-addressable store. Key fields: `lockfileVersion`, `packages` (flat map of all packages by specifier), `importers` (per-workspace dependency declarations).
269
+
270
+ ---
271
+
272
+ ## Package Registries
273
+
274
+ ### PyPI JSON API
275
+ **Spec**: https://warehouse.pypa.io/api-reference/json.html
276
+ **Applies to**: `Package`, `PackageVersion`, `ProjectPackagesRegistry.PYPI`
277
+
278
+ Endpoint: `https://pypi.org/pypi/{name}/json` and `https://pypi.org/pypi/{name}/{version}/json`.
279
+
280
+ Key response fields used:
281
+ - `info.name`, `info.version`, `info.author`, `info.summary`, `info.home_page`
282
+ - `info.requires_dist` — list of PEP 508 dependency strings
283
+ - `info.requires_python` — Python version constraint
284
+ - `releases` — map of version → list of distribution file objects (with `upload_time`, `yanked`)
285
+
286
+ `yanked` maps to `PackageVersion.is_yanked`. A yanked version still exists on PyPI but is excluded from normal resolution; pip will refuse to install it unless the version is pinned exactly.
287
+
288
+ ### npm Registry API
289
+ **Spec**: https://github.com/npm/registry/blob/master/docs/REGISTRY.md
290
+ **Applies to**: `Package`, `PackageVersion`, `ProjectPackagesRegistry.NPM`
291
+
292
+ Endpoint: `https://registry.npmjs.org/{name}` (full packument) or `https://registry.npmjs.org/{name}/{version}`.
293
+
294
+ Key response fields used:
295
+ - `name`, `description`, `author`, `homepage`, `repository.url`
296
+ - `versions.{ver}.dependencies`, `versions.{ver}.devDependencies`, `versions.{ver}.peerDependencies`
297
+ - `versions.{ver}.deprecated` — non-empty string means this version is deprecated → `PackageVersion.is_deprecated`
298
+ - `dist-tags.latest` — current latest version; if `versions[latest].deprecated` is set → `Package.is_deprecated`
299
+ - `time.{ver}` — publish timestamp per version
300
+ - `time.unpublished` — present when the entire package was unpublished → `Package.is_unpublished`; every version also gets `PackageVersion.is_unpublished=True`
301
+ - `dist.integrity`, `dist.tarball` — download and verification
302
+
303
+ **Individually deleted versions**: a version key present in `time` but absent from `versions` was individually unpublished after release. These phantom entries are surfaced as `PackageVersion` objects with `is_unpublished=True` and `published_date_iso` from the `time` value. Non-semver keys (`created`, `modified`, and any registry metadata keys) are excluded.
304
+
305
+ ---
306
+
307
+ ## Further Reading — Versioning Practices
308
+
309
+ These articles expand on the motivation and trade-offs behind versioning strategies, which inform why OSS IQ tracks version lag as a risk signal.
310
+
311
+ ### Against Upper-Bound Version Constraints in Libraries
312
+ **Article**: https://iscinumpy.dev/post/bound-version-constraints/
313
+
314
+ Argues that upper-bound caps (`package<2.0`) in library dependencies cause more harm than good in Python's flat dependency model. Key points:
315
+ - Users **cannot fix** an overly restrictive cap themselves — the library maintainer must release a patch — whereas a missing lower bound is trivially overridden.
316
+ - Caps force immediate maintenance releases whenever an upstream dependency cuts a new major, creating unsustainable pressure at scale.
317
+ - They hide real incompatibilities rather than surfacing them early when changesets are small.
318
+ - **Recommended practice**: set lower bounds to express what features are required; add upper bounds only when a known incompatibility exists and remove them as soon as possible. Never cap Python itself.
319
+ - Applications (not libraries) should use lock files for reproducibility instead of tightening manifest constraints.
320
+
321
+ ### Version Pinning Debate (HN thread)
322
+ **Discussion**: https://news.ycombinator.com/item?id=16422916
323
+
324
+ A Hacker News thread that captures the core tension in dependency management:
325
+
326
+ - **Against pinning**: locked versions accumulate technical debt, making future upgrades progressively harder; they can bloat bundles with duplicate transitive versions; they reduce pressure on maintainers to preserve compatibility.
327
+ - **For pinning**: reproducible builds are "unquestionably superior" — code that built six months ago must build the same way today; developers should consciously choose when to update, not have it happen randomly.
328
+ - **Consensus**: the manifest/lockfile split (Cargo, Bundler, Composer pattern) is the right middle ground — express *intent* as version ranges in the manifest, express *reality* as exact versions in the lockfile. Pinning belongs in applications, not library manifests.
329
+
330
+ This tension — between flexibility for library consumers and reproducibility for application builders — is exactly what `Dependency.version_defined` (manifest constraint) vs. `Dependency.version_installed` (lockfile reality) models.
331
+
332
+ ---
333
+
334
+ ## Vulnerability Databases
335
+
336
+ The `CveDatabase` enum in `common.py` tracks which sources a CVE record came from. Not all sources are currently implemented.
337
+
338
+ ### OSV (Open Source Vulnerabilities) — ✅ Implemented
339
+ **Spec**: https://ossf.github.io/osv-schema/
340
+ **Applies to**: `CVE`, `CveDatabase.OSV`
341
+
342
+ The primary vulnerability source used by ossiq. The OSV API (`https://api.osv.dev/v1/`) aggregates records from NVD, GHSA, and ecosystem-specific databases (PyPA, npm advisories, etc.) into a single unified schema. Key fields: `id`, `aliases` (cross-references to CVE, GHSA IDs), `affected[].package.name`, `affected[].package.ecosystem`, `affected[].ranges`, `severity`, `summary`, `published`, `modified`.
343
+
344
+ OSV covers both PyPI and npm ecosystems natively, making it well-suited as the single integration point — GHSA and CVE IDs appear as `aliases` on OSV records, so direct integration with those APIs is largely redundant.
345
+
346
+ ### GitHub Security Advisories (GHSA) — 🔲 Not yet implemented
347
+ **Spec**: https://docs.github.com/en/rest/security-advisories
348
+ **Applies to**: `CVE`, `CveDatabase.GHSA`
349
+
350
+ GitHub's advisory database, accessible via REST (`GET /advisories`) and GraphQL APIs. IDs take the form `GHSA-xxxx-xxxx-xxxx`. Includes CVSS scores, CWE identifiers, affected version ranges, and patched versions. Most GHSA records are already surfaced through OSV aliases; a direct integration would be useful for accessing GitHub-specific metadata (e.g., `cvss_severities`, `credits`, `cwe_ids`) not always present in the OSV response.
351
+
352
+ ### NVD (National Vulnerability Database) — 🔲 Not yet implemented
353
+ **Spec**: https://nvd.nist.gov/developers/vulnerabilities
354
+ **Applies to**: `CVE`, `CveDatabase.NVD`
355
+
356
+ NIST's authoritative CVE database. IDs take the form `CVE-YYYY-NNNNN`. The NVD API v2 provides CVSS v3.1/v4.0 scores, CPE applicability statements, and CWE classifications. NVD is the canonical source for CVE identifiers and CVSS scores; OSV records typically reference NVD for severity data, so direct integration would mainly be useful for richer CPE matching or accessing CVSS v4.0 scores before OSV propagates them.
357
+
358
+ ### Snyk Vulnerability DB — 🔲 Not yet implemented
359
+ **Applies to**: `CVE`, `CveDatabase.SNYK`
360
+
361
+ Snyk maintains a proprietary database with additional vulnerability research not always present in OSV/NVD. Accessible via the Snyk API (requires API key). May surface earlier disclosures and more granular affected-version ranges for npm packages.
362
+
363
+ ### CVSSv3 Severity Bands
364
+ **Spec**: https://www.first.org/cvss/specification-document
365
+ **Applies to**: `Severity`
366
+
367
+ | Score | `Severity` value |
368
+ |-------|----------------|
369
+ | 0.0 | _(informational)_ |
370
+ | 0.1 – 3.9 | `LOW` |
371
+ | 4.0 – 6.9 | `MEDIUM` |
372
+ | 7.0 – 8.9 | `HIGH` |
373
+ | 9.0 – 10.0 | `CRITICAL` |
@@ -0,0 +1,155 @@
1
+ # HPDR — High-Performance Dependency Resolver
2
+
3
+ Weighted MaxSAT solver that recommends the best version for each dependency: the highest version satisfying all hard constraints while maximising freshness and avoiding deprecated/unsafe packages.
4
+
5
+ ---
6
+
7
+ ## Module Map
8
+
9
+ ```
10
+ solver/
11
+ ├── driver.py Types + AbstractSolverDriver ABC
12
+ ├── driver_glucose.py GlucoseDriver — DEFAULT: greedy sequential-assumption SAT (Glucose42)
13
+ ├── driver_pysat.py VarAllocator + PySATDriver (exact RC2 MaxSAT — pluggable alternative)
14
+ ├── kernel.py HPDRKernel — thin delegating wrapper
15
+ ├── problem.py SolverProblem, CandidateVersion, PackageConstraint
16
+ ├── universe.py SolvablePool — builds SolverProblem from registry
17
+ ├── version_matchers.py npm semver + PEP 440 range matching — raw constraint → bool
18
+ ├── encoder.py ConstraintEncoder — produces EncodedProblem (WCNF)
19
+ ├── weights.py Constraint weight constants + semver_rank_weight()
20
+ └── dependencies_solver.py Public API: solve_direct(), solve_transitive()
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Inputs
26
+
27
+ ### `solve_direct(deps, registry, engine_context)`
28
+
29
+ | Argument | Type | Description |
30
+ |---|---|---|
31
+ | `deps` | `Sequence[_DepLike]` | Direct dependency descriptors from the scan pass |
32
+ | `registry` | `AbstractPackageRegistryApi` | Registry with warm in-memory cache (no extra HTTP) |
33
+ | `engine_context` | `dict[str, str]` | Project runtime versions, e.g. `{"python": "3.11.9"}` — currently `{}` (L2 inactive until Phase 6) |
34
+
35
+ `_DepLike` is a structural Protocol requiring: `canonical_name`, `version`, `version_constraint`, `constraint_info`.
36
+
37
+ ### `solve_transitive(transitive_records, registry, engine_context)`
38
+
39
+ Same registry/engine_context contract. Input is `ScanRecord`-compatible objects exposing `package_name`, `installed_version`, `version_constraint`, `constraint_info`, `cve`, `version_age_days`.
40
+
41
+ Only **flagged** records are solved: those with ≥1 CVE or `version_age_days < 7`.
42
+
43
+ ---
44
+
45
+ ## Pipeline
46
+
47
+ ```
48
+ deps / transitive_records
49
+ │
50
+ ▼
51
+ SolvablePool.build() universe.py
52
+ – deduplicates by canonical_name (highest ConstraintType priority wins)
53
+ – fetches candidates from warm registry cache
54
+ – filters yanked, unpublished, prerelease (unless allowed)
55
+ – sorts candidates descending (newest first)
56
+ – stamps has_cve=True on versions any of the package's CVEs affects (cves_by_package;
57
+ judged by version_matchers.cve_affects_version, ranges included)
58
+ │
59
+ ▼ SolverProblem
60
+ ConstraintEncoder.encode() encoder.py
61
+ – allocates SAT variables via VarAllocator (1-based bijection)
62
+ – emits hard + soft WCNF clauses (see Constraint Levels below)
63
+ │
64
+ ▼ EncodedProblem
65
+ HPDRKernel.solve() kernel.py → driver_glucose.py (default)
66
+ – Glucose42 on hard clauses; per-package preferred version (highest net
67
+ soft weight) greedily committed as assumptions; 10 s timeout → ConflictSet
68
+ – exact RC2 Weighted MaxSAT (driver_pysat.py) remains a pluggable driver
69
+ – positive vars in model → selected (package, version) pairs
70
+ │
71
+ ▼ SolverResult | ConflictSet
72
+ dict[canonical_name, version] — or {} on ConflictSet (logged DEBUG)
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Constraint Levels
78
+
79
+ Hard clauses must be satisfied. Soft weights express preferences: the default GlucoseDriver
80
+ **approximates** the optimum by committing each package's highest-net-soft-weight version
81
+ greedily; the pluggable RC2 driver **minimises** total violated soft cost exactly.
82
+
83
+ | Level | Kind | Weight | Trigger |
84
+ |---|---|---|---|
85
+ | L1 | Hard | — | Version outside declared `version_constraint` (PEP 440 or npm semver) |
86
+ | L2 | Soft-Hard | `W_ENGINE = 100_000` | Engine mismatch (`python`/`node` requirement vs `engine_context`) |
87
+ | L3 | Soft | `max(80_000 − rank × 5_000, 1_000)` | Semver-rank preference — rank 0 = latest eligible semver |
88
+ | L4 | Soft | `W_DEPRECATED = 10_000` | Deprecated flag — penalty on selection |
89
+ | L5 | Hard | — | CVE-affected version |
90
+ | L6 | Soft-Hard | `W_VERY_FRESH = 100_000` | Published < 7 days ago — both direct and transitive passes |
91
+ | L7 | — | *(reserved)* | Health score — not implemented |
92
+
93
+ Soft clause semantics (defined in `weights.py`, applied in `encoder.py`):
94
+ - `soft([+var], W)` — penalty W if this version is **not** selected (encourages selection; L3)
95
+ - `soft([-var], W)` — penalty W if this version **is** selected (discourages selection; L2, L4, L6)
96
+
97
+ **L3 — Semver-rank preference (not age-based)**
98
+ Candidates are sorted newest-semver-first by `SolvablePool.build()`. The rank of each eligible
99
+ candidate in this sorted list determines its L3 weight: `semver_rank_weight(rank) = max(80_000 − rank × 5_000, 1_000)`.
100
+ Rank 0 (highest semver in the constraint) always has the highest preference. The step size (5 000)
101
+ is intentionally smaller than `W_DEPRECATED` (10 000), so a deprecated rank-0 version loses to a
102
+ clean rank-1 version. Engine mismatch (100 000) overrides any rank gap.
103
+
104
+ This design handles parallel major-version streams correctly: a freshly-released 7.x patch can
105
+ never beat an older 8.x release when both are eligible — semver order dominates calendar age.
106
+
107
+ **L6 — Very fresh penalty (< 7 days)**
108
+ Applied in both `solve_direct` and `solve_transitive` (both use `penalize_fresh_days=VERY_FRESH_THRESHOLD_DAYS=7`).
109
+ A candidate published fewer than 7 days ago receives a 100 000 soft penalty. If an alternative with
110
+ the same or adjacent semver rank is ≥ 7 days old, it wins. If no older alternative exists, the fresh
111
+ version is still selected (soft penalty, not a hard ban).
112
+
113
+ Structural clauses per package (encoder):
114
+ - **AMO** (At-Most-One): pairwise `[-vi, -vj]` over eligible candidates — exactly one version selected
115
+ - **ALO** (At-Least-One): `[v1, v2, …]` over eligible candidates — solver must pick something; omitted when no eligible candidate exists (deliberate UNSAT → `ConflictSet`)
116
+
117
+ ### Version constraint dispatch (encoder.py)
118
+
119
+ `version_satisfies_constraint(version, constraint, registry)` (in `version_matchers.py`) dispatches directly to PyPI (PEP 440) or npm (semver) based on the `ProjectPackagesRegistry` passed in — no exception-based fallback. Bare npm versions (e.g. `"14"`) are treated as caret ranges (`^14.0.0`). Unparseable constraints pass through (`True`) — unknown format is never a hard block.
120
+
121
+ ---
122
+
123
+ ## Outputs
124
+
125
+ | Scenario | Return value |
126
+ |---|---|
127
+ | Solution found | `dict[str, str]` — `{canonical_name: recommended_version}` |
128
+ | Hard constraint unsatisfiable (`ConflictSet`) | `{}` — DEBUG-logged; scan always completes |
129
+ | No input / no flagged records | `{}` |
130
+
131
+ `SolverResult.selected` is a list of `(package_name, version)` tuples emitted only for variables that were **true** in the solver model and present in `var_map`.
132
+
133
+ `SolverProblem.fingerprint()` returns a SHA-256 over canonical JSON — reserved as a cache key for future memoisation (Phase 6).
134
+
135
+ ---
136
+
137
+ ## Key Implementation Decisions
138
+
139
+ **Why a Weighted MaxSAT formulation — and why the default driver isn't exact MaxSAT?**
140
+ The problem is naturally a Partial MaxSAT instance: some constraints are inviolable (declared ranges, CVEs) while others express preferences with quantified importance (age, engine, deprecation). The encoding is shared; the driver is pluggable. The **default `GlucoseDriver`** commits each package's highest-net-soft-weight version as a SAT assumption greedily (Glucose42, 10 s timeout) — it captures the dominant per-package preference without MaxSAT's NP-hard optimisation cost. **`PySATDriver`** (RC2 from `python-sat==1.9.dev2`) remains available for exact optimisation. Reference: [RC2 in the pysat handbook](https://pysathq.github.io/docs/html/api/examples/rc2.html).
141
+
142
+ **Why a Protocol (`_DepLike`) instead of importing `DependencyDescriptor`?**
143
+ `service/` already imports `unit_of_work/`; importing back would create a cycle. The Protocol is duck-typed and zero-cost at runtime. A future refactor moving `DependencyDescriptor` to `domain/` would let us drop the Protocol (noted as TODO in `universe.py:28`).
144
+
145
+ **Why is `engine_context={}` in Phase 4/5?**
146
+ L2 clauses only fire when both a candidate's `runtime_requirements` and `engine_context` are non-empty. Passing `{}` makes them structurally inactive without any conditional logic. Population from project metadata (`.python-version`, `.nvmrc`, `sys.version_info`) is deferred to Phase 6.
147
+
148
+ **Why deduplicate by highest `ConstraintType` priority?**
149
+ The same package can appear as both a direct and transitive dependency with different constraint strengths. `OVERRIDE > ADDITIVE > PINNED > NARROWED > DECLARED` — the strongest constraint wins so the solver sees the binding restriction.
150
+
151
+ **Why candidates sorted descending (newest first)?**
152
+ L3 assigns rank-based weights where rank 0 (index 0 in the sorted list) = highest semver = highest preference. Descending sort is therefore required for the L3 rank calculation to be meaningful, and also ensures `var_map` iteration and debug output are intuitively readable.
153
+
154
+ **On `ConflictSet` — silent `{}`?**
155
+ Returning `{}` lets the scan pipeline always complete and show `Latest` even when the solver conflicts. Phase 6 will surface a user-facing warning identifying the conflicting package(s). Currently only DEBUG-logged.