cognix-sdk 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.
@@ -0,0 +1,51 @@
1
+ # CogniX — un repo, un módulo, una versión.
2
+ # Las cinco features viven aquí dentro con su historia (git filter-repo, 2026-07-29).
3
+ # La cabecera anterior decía que cada miembro era un repo aparte: ya no lo es.
4
+
5
+ # Local / temporales
6
+ .claude/settings.local.json
7
+ .DS_Store
8
+ *.swp
9
+ scratchpad/
10
+
11
+ # Python — el workspace uv pone UN entorno en la raiz (antes habia uno por feature, y
12
+ # cada .gitignore de miembro lo ignoraba por su cuenta). Son ~5,5 GB: si esto falta,
13
+ # el primer `git add -A` intenta versionar el entorno entero.
14
+ .venv/
15
+ venv/
16
+ __pycache__/
17
+ *.py[cod]
18
+ *.egg-info/
19
+ # Ficheros de trabajo por sesión (encargos cross-repo): locales, no se versionan (§6).
20
+ SSO-NEXT-SESSION.md
21
+
22
+ # Portal (Fase 1b) — runtime artefacts y config con secretos.
23
+ # El binario, la base de datos de usuarios y la config con admin keys NO se versionan;
24
+ # se versiona portal.example.json como plantilla.
25
+ portal.json
26
+ cognix-portal.db
27
+ *.db
28
+ /cognix-portal
29
+ /cognix-portal.exe
30
+
31
+ # Portal SPA — dependencias y build.
32
+ web/node_modules/
33
+ web/dist/
34
+
35
+ # Diagramas de estrategia — el HTML (+ render.sh) son la fuente; el PNG/PDF se regeneran.
36
+ docs/estrategia/**/*.png
37
+ docs/estrategia/**/*.pdf
38
+
39
+ # Binaries built by the root Taskfile (task build).
40
+ /bin/
41
+
42
+ # The umbrella binary when built at the repo root by hand (task build uses bin/).
43
+ /cognix
44
+
45
+ # Compiled example binaries. A 22 MB ELF was tracked in decidix/examples/eino-router,
46
+ # imported with that repo history: it is a build artifact, it is machine-specific, and it
47
+ # dwarfs every source file in the tree.
48
+ decidix/examples/*/eino-router
49
+
50
+ # Cross-compiled cgx binaries staged for the PyPI wheels (scripts/build-wheels.py).
51
+ /build/
@@ -0,0 +1,141 @@
1
+ CogniX Software License Agreement
2
+ Copyright (c) 2026 Carlos Prados. All rights reserved.
3
+
4
+ This is not open source software. Read this before installing or using it.
5
+
6
+ 1. DEFINITIONS
7
+
8
+ "Licensor" means Carlos Prados, the copyright holder.
9
+
10
+ "Software" means the `cognix-sdk` Python package, the `cgx` executable distributed
11
+ with it, and any documentation, examples or files supplied with either.
12
+
13
+ "Platform" means the CogniX inference, training and serving products operated or
14
+ licensed by the Licensor, including any host system into which they are embedded.
15
+
16
+ "Bundle" means a directory in the format the Software produces and validates: a
17
+ manifest, a model file, a feature extractor and its samples.
18
+
19
+ "You" means the individual or legal entity exercising the rights granted here.
20
+
21
+ 2. GRANT OF LICENSE
22
+
23
+ Subject to Your compliance with this Agreement, the Licensor grants You a
24
+ non-exclusive, non-transferable, non-sublicensable, revocable, worldwide,
25
+ royalty-free license to install and use the Software, for Your internal purposes
26
+ only, in order to:
27
+
28
+ (a) create, inspect and validate Bundles intended for deployment on the Platform;
29
+ (b) evaluate the Software and the Platform, including before entering into any
30
+ commercial agreement with the Licensor; and
31
+ (c) develop and test Your own models and feature extractors against the Bundle
32
+ format.
33
+
34
+ No fee is charged for this license. It confers no right to the Platform itself,
35
+ which is licensed separately.
36
+
37
+ 3. RESTRICTIONS
38
+
39
+ You may not, and may not permit any third party to:
40
+
41
+ (a) distribute, publish, sell, rent, lease, lend, host or otherwise make the
42
+ Software available to any third party, in whole or in part, whether alone or
43
+ bundled with other software;
44
+ (b) modify, adapt, translate or create derivative works of the Software;
45
+ (c) reverse engineer, decompile or disassemble the Software, or attempt to derive
46
+ its source code, except and only to the extent that this restriction is
47
+ expressly prohibited by applicable law that cannot be waived by agreement (see
48
+ Section 4);
49
+ (d) use the Software, or any knowledge of the Bundle format obtained from it, to
50
+ develop, or to assist any third party in developing, a product or service that
51
+ competes with the Software or the Platform;
52
+ (e) implement, or assist any third party in implementing, an inference, scoring or
53
+ serving system that consumes Bundles, other than the Platform;
54
+ (f) remove, obscure or alter any copyright, trademark or other proprietary notice
55
+ in or displayed by the Software; or
56
+ (g) circumvent any technical measure that limits the functionality of the Software.
57
+
58
+ For the avoidance of doubt: models You create remain Yours, and nothing in this
59
+ Agreement restricts what You do with a model of Your own in any system, provided
60
+ You do not do so by means of the Software or of a Bundle-consuming implementation
61
+ prohibited by Section 3(e).
62
+
63
+ 4. MANDATORY LEGAL RIGHTS
64
+
65
+ Nothing in this Agreement excludes or limits any right You have under applicable
66
+ law that cannot be excluded or limited by agreement. In particular, where Directive
67
+ 2009/24/EC of the European Parliament and of the Council, or the national law
68
+ implementing it, grants You a right to decompile the Software to achieve
69
+ interoperability with an independently created program, that right applies
70
+ notwithstanding Section 3(c), on the conditions that Directive lays down.
71
+
72
+ 5. OWNERSHIP
73
+
74
+ The Software is licensed, not sold. The Licensor and its suppliers retain all
75
+ right, title and interest in and to the Software, including all intellectual
76
+ property rights. All rights not expressly granted in Section 2 are reserved.
77
+
78
+ 6. THIRD-PARTY COMPONENTS
79
+
80
+ The `cgx` executable statically links open source components which are licensed to
81
+ You under their own terms, not under this Agreement. Those terms are reproduced in
82
+ the accompanying THIRD-PARTY-NOTICES.md file and prevail over this Agreement in
83
+ respect of those components.
84
+
85
+ 7. FEEDBACK
86
+
87
+ If You send the Licensor suggestions, bug reports or other feedback about the
88
+ Software, the Licensor may use it without restriction or obligation to You. This
89
+ does not give the Licensor any right in Your models, Your data or Your Bundles.
90
+
91
+ 8. NO SUPPORT
92
+
93
+ This Agreement does not oblige the Licensor to provide support, maintenance,
94
+ updates or new versions of the Software.
95
+
96
+ 9. TERM AND TERMINATION
97
+
98
+ This Agreement takes effect when You first install or use the Software and
99
+ continues until terminated. It terminates automatically, without notice, if You
100
+ breach any of its terms. On termination You must stop using the Software and delete
101
+ all copies of it in Your possession. Sections 3, 5, 10, 11 and 12 survive
102
+ termination.
103
+
104
+ 10. NO WARRANTY
105
+
106
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
107
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
108
+ FOR A PARTICULAR PURPOSE, ACCURACY AND NON-INFRINGEMENT. THE SOFTWARE VALIDATES
109
+ BUNDLES; IT DOES NOT WARRANT THAT ANY MODEL, PREDICTION OR DECISION PRODUCED WITH
110
+ ITS HELP IS CORRECT, SAFE OR FIT FOR ANY PURPOSE.
111
+
112
+ 11. LIMITATION OF LIABILITY
113
+
114
+ TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSOR SHALL NOT BE
115
+ LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR PUNITIVE DAMAGES,
116
+ OR FOR ANY LOSS OF PROFITS, REVENUE, DATA OR BUSINESS, ARISING OUT OF OR RELATED
117
+ TO THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. THE
118
+ LICENSOR'S TOTAL AGGREGATE LIABILITY UNDER THIS AGREEMENT SHALL NOT EXCEED ONE
119
+ HUNDRED EUROS (EUR 100).
120
+
121
+ Nothing in this Section limits liability for death or personal injury caused by
122
+ negligence, for fraud, or for any other liability that cannot be limited under
123
+ applicable law.
124
+
125
+ 12. GOVERNING LAW AND JURISDICTION
126
+
127
+ This Agreement is governed by the laws of Spain, without regard to its conflict of
128
+ law rules. The courts of the city of Madrid, Spain, shall have exclusive
129
+ jurisdiction over any dispute arising out of or in connection with it, except that
130
+ the Licensor may seek injunctive relief in any competent court. This Section does
131
+ not deprive a consumer of the protection of the mandatory rules of the law of the
132
+ country in which they are resident.
133
+
134
+ 13. ENTIRE AGREEMENT
135
+
136
+ This Agreement is the entire agreement between You and the Licensor regarding the
137
+ Software and supersedes any prior understanding on that subject. If any provision
138
+ is held unenforceable, it shall be modified to the minimum extent necessary to
139
+ make it enforceable, and the remaining provisions shall remain in full force.
140
+
141
+ Contact: carlosprados@users.noreply.github.com
@@ -0,0 +1,160 @@
1
+ Metadata-Version: 2.4
2
+ Name: cognix-sdk
3
+ Version: 0.1.0
4
+ Summary: Empaqueta tu modelo como un bundle de CogniX y compruébalo antes de publicarlo
5
+ Project-URL: Homepage, https://github.com/carlosprados/cognix
6
+ Project-URL: Documentation, https://github.com/carlosprados/cognix/blob/main/sdk/README.md
7
+ Project-URL: Source, https://github.com/carlosprados/cognix
8
+ Author: Carlos Prados
9
+ License-Expression: LicenseRef-Proprietary
10
+ License-File: LICENSE
11
+ License-File: THIRD-PARTY-NOTICES.md
12
+ Keywords: inference,iot,mlops,model-packaging,onnx,starlark
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: License :: Other/Proprietary License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: numpy>=1.26.0
26
+ Provides-Extra: sklearn
27
+ Requires-Dist: onnx>=1.16.0; extra == 'sklearn'
28
+ Requires-Dist: scikit-learn>=1.5.0; extra == 'sklearn'
29
+ Requires-Dist: skl2onnx>=1.17.0; extra == 'sklearn'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # cognix-sdk
33
+
34
+ > Se instala `cognix-sdk` y se importa `cgx`, como `pillow`→`PIL`. En PyPI, `cognix` es
35
+ > de otro proyecto y `cgx` está reservado por alguien más; `cgx` es el identificador que
36
+ > CogniX ya usa en sus API keys (`cgx_<feature>_…`), así que se queda en el módulo y en
37
+ > el binario.
38
+
39
+ Empaqueta tu modelo como un bundle de CogniX y **compruébalo en tu máquina** antes de
40
+ publicarlo.
41
+
42
+ ```bash
43
+ uv add cognix-sdk
44
+ uv add "cognix-sdk[sklearn]" # si quieres exportar un modelo de scikit-learn a ONNX
45
+ ```
46
+
47
+ ## Qué problema resuelve
48
+
49
+ El modelo lo entrenas tú, con lo que quieras. Lo que hay que hacer bien es el **artefacto**,
50
+ y ahí hay una trampa que no da ningún error:
51
+
52
+ > el extractor de features se escribe **dos veces** — tu `featurize()` en Python para
53
+ > entrenar, y `features.star` (Starlark) que evalúa Go en producción. Cuando se separan, el
54
+ > modelo sigue contestando: sólo que **sobre otra entrada**.
55
+
56
+ No hay excepción, ni traza, ni métrica que baje. Este paquete existe para que eso falle en
57
+ tu portátil.
58
+
59
+ ## Uso
60
+
61
+ ```python
62
+ import cgx
63
+ from cgx import starlark
64
+ import pandas as pd
65
+ from sklearn.linear_model import LogisticRegression
66
+ from sklearn.preprocessing import StandardScaler
67
+
68
+ # 1. tus datos (exportados con: wolfctl exports download-export --id <uuid> > datos.parquet)
69
+ df = pd.DataFrame([json.loads(r) for r in pd.read_parquet("datos.parquet")["RESULT"]])
70
+ schema = ["current_a", "vibration", "temperature"]
71
+
72
+ # 2. tu modelo, como lo entrenas siempre
73
+ scaler = StandardScaler().fit(df[schema].to_numpy())
74
+ clf = LogisticRegression(max_iter=1000).fit(scaler.transform(df[schema].to_numpy()), df["fault_label"])
75
+
76
+ # 3. el artefacto
77
+ onnx = cgx.to_onnx(clf, n_features=len(schema))
78
+
79
+ b = cgx.Bundle(
80
+ domain="motor_faults",
81
+ feature="classix",
82
+ schema=schema,
83
+ featurizer=starlark.identity_featurizer(schema),
84
+ starlark=starlark.identity("motor_faults", schema),
85
+ onnx_path=onnx,
86
+ output_names=cgx.onnx.scores_output(onnx), # el tensor de scores, no `label`
87
+ output_kind="probabilities", # skl2onnx ya las emite normalizadas
88
+ payloads=df[schema].head(8).to_dict("records"), # payloads representativos
89
+ labels=sorted(df["fault_label"].unique()),
90
+ mean=scaler.mean_.tolist(),
91
+ std=scaler.scale_.tolist(),
92
+ )
93
+
94
+ b.check() # escribe el bundle y lo verifica; levanta BundleError si algo no cuadra
95
+ ```
96
+
97
+ `check()` hace dos cosas, en este orden:
98
+
99
+ 1. **En Python**: que tu `featurize()` devuelva tantos números como features declaras, para
100
+ cada payload. Un fallo aquí es tuyo y se ve al instante.
101
+ 2. **Con el binario `cgx`**, que viene dentro de este paquete: valida el manifest contra el
102
+ contrato de la feature y corre `verify-parity` — tu Python contra el Starlark real.
103
+
104
+ El paso 2 lo hace **el binario**, no una reimplementación de Starlark en Python. Un segundo
105
+ intérprete sería una segunda implementación que puede diferir del motor de producción, o
106
+ sea el mismo problema que esto viene a detectar, movido un nivel. Si el binario no está en
107
+ el PATH, `check()` lo dice y **no finge un OK**.
108
+
109
+ Falta una tercera comprobación, `verify-bundle`, que reproduce el resultado completo
110
+ —clase, probabilidades, puerta OOD— contra una fixture grabada al entrenar. Esa ejecuta el
111
+ modelo, o sea el motor, y vive en el binario `cognix`: si lo tienes en el PATH, `check()`
112
+ lo usa; si no, dice exactamente qué queda sin cubrir. Un bundle traído de fuera casi nunca
113
+ trae esa fixture de todos modos, porque la escribe el trainer de la feature.
114
+
115
+ Y ya se puede servir:
116
+
117
+ ```bash
118
+ cognix classix serve --bundles dist
119
+ cognix classix infer --bundle dist/motor_faults --json '{"current_a":18.5,"vibration":1.2,"temperature":97.0}'
120
+ ```
121
+
122
+ ## Si tu featurización no es la identidad
123
+
124
+ `starlark.identity()` sólo cubre el caso "cada feature es un campo numérico del payload".
125
+ Para cualquier otra cosa —una razón entre dos campos, un one-hot, una saturación— escribes
126
+ tu `features.star` a mano y se lo pasas en `starlark=`.
127
+
128
+ ⛔ **No hay traducción automática de Python a Starlark, y es deliberado.** Traducir un
129
+ `featurize()` cualquiera leyendo su AST es la funcionalidad que enamora y la que puede
130
+ traducir mal **en silencio** — exactamente la clase de fallo que este SDK existe para cazar.
131
+ `check()` funciona con el extractor que sea, y esa es la garantía que importa.
132
+
133
+ ## Lo que este paquete NO es
134
+
135
+ **No es un cliente de la API REST de WolfOps**, y no lo será a mano: un wrapper de ~100
136
+ endpoints envejece con cada endpoint nuevo y se convierte en otro sitio donde algo se queda
137
+ fuera en silencio. Para los datos ya tienes dos líneas que no hay que mantener:
138
+
139
+ ```bash
140
+ wolfctl exports download-export --id <uuid> > datos.parquet
141
+ ```
142
+ ```python
143
+ df = pd.read_parquet("datos.parquet")
144
+ ```
145
+
146
+ Su contrato es el **formato del bundle** (v3), que está versionado y cambia a propósito, no
147
+ una superficie que cambia cada semana.
148
+
149
+ ## Referencia
150
+
151
+ | | |
152
+ |---|---|
153
+ | `Bundle(...)` | el artefacto: `.manifest()`, `.payload_schema()`, `.save(dir)`, `.check(dir)` |
154
+ | `to_onnx(model, n_features)` | export de scikit-learn con el opset y la salida que espera el motor |
155
+ | `starlark.identity(domain, schema)` | el `features.star` del caso identidad |
156
+ | `starlark.identity_featurizer(schema)` | su gemelo Python, para pasarlo a `featurizer=` |
157
+
158
+ El bloque de tarea de cada feature (calibración, intervalos conformes, puerta OOD,
159
+ fallbacks) se declara en `extra_manifest=`: es lo específico de cada una y pertenece a la
160
+ feature, no a este paquete.
@@ -0,0 +1,219 @@
1
+ # Publicar el SDK en PyPI
2
+
3
+ Ya tienes cuenta. Queda **una cosa que solo puedes hacer tú** (autorizar este repo, 5
4
+ minutos) y después publicar es un comando:
5
+
6
+ ```bash
7
+ task sdk:release:test -- 0.1.0.dev1 # ensayo en TestPyPI
8
+ task sdk:release -- 0.1.0 # de verdad
9
+ ```
10
+
11
+ `scripts/release-sdk.py` comprueba antes todo lo que se puede comprobar en local —árbol
12
+ limpio, rama al día, versión libre en el índice, `go test`, `pytest`, los wheels
13
+ construidos y el wheel **instalado en un venv vacío** verificando un bundle de verdad— y
14
+ solo entonces pregunta. El push del tag es lo que dispara la publicación.
15
+
16
+ ---
17
+
18
+ ## ⛔ Antes de nada: publicar es hacer público el nombre
19
+
20
+ Un paquete en PyPI es **visible para cualquiera**, con su README, y aparece en buscadores.
21
+ Eso adelanta el disclosure que aplazaste a Q4 2026 por el conflicto con Amplía — el mismo
22
+ motivo por el que el post de julio salió sin nombres.
23
+
24
+ Por eso nada se publica sin el tag `sdk-v*`: hasta que lo lances, aquí no pasa nada. Para
25
+ probar el flujo completo antes de esa fecha está **TestPyPI**, que es un índice separado,
26
+ no reserva el nombre en el PyPI real y nadie lo mira.
27
+
28
+ ---
29
+
30
+ ## 1. Autorizar este repositorio (Trusted Publishing) — lo único pendiente
31
+
32
+ En PyPI, en los ajustes de tu **cuenta** — <https://pypi.org/manage/account/publishing/> —,
33
+ formulario **GitHub**, *Add*.
34
+
35
+ ⚠️ Es a nivel de cuenta, no de proyecto: la página de *Your projects → Publishing* solo
36
+ existe para proyectos ya creados, y `cognix-sdk` todavía no existe. Eso es lo que significa
37
+ *pending*: la autorización queda esperando, y el proyecto se crea con la primera
38
+ publicación que la use.
39
+
40
+ Rellena **exactamente** esto:
41
+
42
+ | campo | valor |
43
+ |---|---|
44
+ | PyPI Project Name | `cognix-sdk` |
45
+ | Owner | `carlosprados` |
46
+ | Repository name | `cognix` |
47
+ | Workflow name | `release-sdk.yml` |
48
+ | Environment name | `pypi` |
49
+
50
+ Repite en **TestPyPI** con Environment name = `testpypi`, en
51
+ <https://test.pypi.org/manage/account/publishing/>.
52
+
53
+ ⚠️ **TestPyPI es un sitio distinto con cuentas distintas.** Tu usuario de PyPI no vale
54
+ allí: hay que registrarse aparte (y activar 2FA aparte). No es un entorno de pruebas del
55
+ mismo servicio, es otro índice.
56
+
57
+ ⚠️ Los cinco campos tienen que coincidir carácter a carácter. Si algo no cuadra, el error
58
+ de PyPI es genérico (*"not a trusted publisher"*) y no dice cuál de los cinco falla —
59
+ compáralos uno a uno contra `.github/workflows/release-sdk.yml`.
60
+
61
+ ⚠️ **No crees ningún token de API.** No hace falta ninguno y es el peor activo que puedes
62
+ tener suelto: un token de PyPI filtrado permite subir cualquier versión de tu paquete a
63
+ quien lo tenga. Trusted Publishing es OIDC — PyPI verifica que la petición viene de este
64
+ repositorio y este workflow, y emite un permiso de un solo uso.
65
+
66
+ ### Y los dos entornos de GitHub
67
+
68
+ En el repo: **Settings → Environments → New environment**, dos, llamados `pypi` y
69
+ `testpypi`. Sin secretos dentro: son solo el nombre que PyPI verifica.
70
+
71
+ En `pypi`, **Deployment branches and tags → Selected branches and tags**, y una regla de
72
+ tipo **Tag** con el patrón `sdk-v*`. Al PyPI real solo se llega por un tag; un
73
+ `workflow_dispatch` apuntando ahí desde una rama lo bloquea GitHub, no la confianza.
74
+
75
+ `testpypi` se queda en *No restriction*: el ensayo se lanza con `workflow_dispatch` desde
76
+ una rama, y restringirlo a tags lo dejaría sin poder ejecutarse nunca.
77
+
78
+ ⛔ **No hay revisor humano, y no es un olvido.** *Required reviewers* y las demás
79
+ *deployment protection rules* exigen plan Pro/Team/Enterprise cuando el repositorio es
80
+ privado — comprobado el 2026-08-01 en este repo: `protection_rules: []`, y la sección ni
81
+ aparece en la página. Publicar en PyPI es **irreversible** (una versión no se reemplaza,
82
+ solo se borra, y el número queda quemado), así que lo que hace de red son tres cosas
83
+ automáticas: sin tag `sdk-v*` no se dispara nada, `release-sdk.py` obliga a teclear la
84
+ versión completa tras sus seis comprobaciones, y el workflow verifica que el tag y
85
+ `sdk/pyproject.toml` coinciden antes de construir.
86
+
87
+ Si algún día el repo se hace público —o sea, en el disclosure— las protection rules pasan
88
+ a estar disponibles gratis: entonces sí, añade el revisor.
89
+
90
+ ---
91
+
92
+ ## 2. El ensayo
93
+
94
+ ```bash
95
+ task sdk:release:test -- 0.1.0.dev1
96
+ ```
97
+
98
+ Lanza el workflow contra TestPyPI. Cuando termine:
99
+
100
+ ```bash
101
+ uv pip install --index-url https://test.pypi.org/simple/ \
102
+ --extra-index-url https://pypi.org/simple/ cognix-sdk==0.1.0.dev1
103
+ python -c "import cgx; print(cgx.FEATURES)" && cgx version
104
+ ```
105
+
106
+ El `--extra-index-url` hace falta porque TestPyPI no tiene numpy, y sin él la instalación
107
+ falla por algo que no tiene nada que ver con tu paquete.
108
+
109
+ ⚠️ Una versión de TestPyPI tampoco se puede repetir: usa `0.1.0.dev1`, `dev2`… y reserva
110
+ `0.1.0` para el real.
111
+
112
+ ## 3. Publicar
113
+
114
+ ```bash
115
+ task sdk:release -- 0.1.0
116
+ ```
117
+
118
+ Comprueba después:
119
+
120
+ ```bash
121
+ uv pip install cognix-sdk && python -c "import cgx; print(cgx.FEATURES)" && cgx version
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Qué se sube, exactamente
127
+
128
+ **Una sola distribución, `cognix-sdk`**, con nueve artefactos:
129
+
130
+ | artefacto | para quién |
131
+ |---|---|
132
+ | 4 wheels Linux (`manylinux` y `musllinux`, x86_64 y aarch64) | glibc y Alpine |
133
+ | 2 wheels macOS (arm64, x86_64) | Apple Silicon e Intel |
134
+ | 1 wheel Windows (`win_amd64`) | |
135
+ | 1 wheel `py3-none-any` **sin binario** | cualquier otra plataforma |
136
+ | 1 sdist | quien lo exija |
137
+
138
+ pip elige siempre el más específico que le encaje. Los siete primeros llevan dentro el
139
+ binario **`cgx`**, que aterriza en el `bin/` del entorno: `pip install cognix-sdk` y `check()`
140
+ funciona sin más pasos. En una plataforma sin wheel propio se instala el puro y el SDK
141
+ dice, en voz alta, qué comprobación no puede hacer — mejor que un `pip install` que falla.
142
+
143
+ Se compila todo desde un runner: `cgx` es Go sin cgo, así que cross-compilar es una
144
+ variable de entorno. Es la otra razón por la que el binario público no enlaza ONNX
145
+ Runtime — con cgo harían falta siete runners y una copia del runtime nativo por
146
+ plataforma.
147
+
148
+ ### ⛔ El binario que se publica es `cgx`, nunca `cognix`
149
+
150
+ `cognix` lleva los cinco motores, ONNX Runtime, el control plane y el servidor: **es el
151
+ producto, y es privado** (regla de Charlie, 2026-08-01). `cgx` lleva el *contrato* del
152
+ bundle —los tipos del manifest y su validación, más el featurizador Starlark— y con eso
153
+ cubre `verify-parity`, `bundle inspect` y `schema`.
154
+
155
+ Lo único que queda fuera es `verify-bundle`, que reproduce predicciones grabadas pasando
156
+ por el modelo: eso **es** el motor. `cgx` declara el comando para rechazarlo con un mensaje
157
+ que explica dónde está, y el SDK usa `cognix` para esa comprobación si lo encuentra en el
158
+ PATH. No es una mutilación: un bundle traído de fuera casi nunca trae esa fixture, porque
159
+ la escribe el trainer de la feature.
160
+
161
+ El guardarraíl es `cmd/cgx/deps_test.go`, una **lista blanca** de paquetes del repo que el
162
+ binario público puede enlazar. Falla el build nombrando al intruso, y ya cazó uno el primer
163
+ día: `clikit` arrastraba el almacén de API keys (y sqlite) dentro del binario público.
164
+
165
+ ## Versiones
166
+
167
+ `sdk/pyproject.toml` lleva la versión y **el tag tiene que coincidir**: `sdk-v0.1.0` ↔
168
+ `version = "0.1.0"`. El script lo comprueba en local y el workflow lo vuelve a comprobar.
169
+
170
+ El contrato del SDK es el **formato del bundle** (v3 hoy), no la API REST:
171
+
172
+ - **parche** (`0.1.1`) — arreglos, mensajes, validaciones nuevas;
173
+ - **menor** (`0.2.0`) — funcionalidad nueva compatible;
174
+ - **mayor** (`1.0.0`) — cuando el formato del bundle cambie de versión, porque un bundle
175
+ escrito por el SDK dejaría de cargar en el motor anterior. Es el único cambio que rompe
176
+ de verdad a un usuario.
177
+
178
+ ## ⛔ El nombre: distribución `cognix-sdk`, módulo `cgx`
179
+
180
+ ```bash
181
+ pip install cognix-sdk # la distribución
182
+ import cgx # el módulo
183
+ ```
184
+
185
+ No coinciden a propósito, como `pillow`→`PIL`. Los dos candidatos naturales estaban
186
+ cogidos, y ninguno es reclamable:
187
+
188
+ > **`cognix` 0.2.5** — *"AI-powered CLI development assistant"*, de Shichiroh.Shigeh.
189
+ > Proyecto **activo**: 0.1.0 en agosto de 2025, 0.2.5 en febrero de 2026.
190
+ > <https://github.com/cognix-dev/cognix>
191
+ >
192
+ > **`cgx`** — registrado en PyPI, **cero versiones publicadas**. Un nombre reservado que
193
+ > nunca se usó, pero ocupado igual.
194
+
195
+ El módulo se queda en `cgx` porque es el identificador que el producto ya usa (las API
196
+ keys son `cgx_<feature>_<hex>`, el prefijo de issues es `[CGX]`) y porque llamarlo
197
+ `cognix` pondría un directorio `cognix/` en el `site-packages` de quien tenga el otro
198
+ paquete: dos distribuciones escribiendo la misma ruta se sobreescriben y el import
199
+ devuelve el que ganó la carrera.
200
+
201
+ ### ⛔ Cómo se comprueba si un nombre está libre
202
+
203
+ Con **`https://pypi.org/simple/<nombre>/`** y solo con eso: 200 existe, 404 libre.
204
+
205
+ | endpoint | por qué NO vale |
206
+ |---|---|
207
+ | `pypi.org/pypi/<n>/json` | devuelve **404 también para un nombre registrado sin releases** — el caso ambiguo, y justo el de `cgx` |
208
+ | `pypi.org/project/<n>/` | tiene un desafío JavaScript delante: **200 siempre**, exista o no |
209
+
210
+ Se descubrió por las malas: `cgx` se dio por libre con el endpoint JSON y el formulario
211
+ del pending publisher contestó *"This project already exists"*.
212
+
213
+ ⛔ **Y un pending publisher NO reserva el nombre**: medido el 2026-08-01, el índice seguía
214
+ dando 404 después de crearlo. El nombre no es tuyo hasta la primera publicación — con el
215
+ disclosure aplazado, es tiempo expuesto. Si eso llega a preocupar, la salida es publicar
216
+ un `0.0.1` vacío que solo ocupe el nombre, y eso YA es el disclosure.
217
+
218
+ ⚠️ Y **TestPyPI es otro índice con otras cuentas**: que un nombre esté libre allí no dice
219
+ nada del PyPI real.