mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
the gate reads the syntax tree, and now it has a runner to read it on
3.2.0 shipped silent data loss to PyPI. O11yGettableAgentCheckIn declared `integration_config` twice and `removed_at` twice; in Python the second annotation rebinds the name, so the model advertised fields it did not have and the value arriving under each shadowed name was read and dropped. 3.2.1 fixed the models. Nothing yet fixed the reason it got out. The `cloud-client` gate imports every generated module and calls that the build step. A class body with a duplicate field IMPORTS CLEAN — measured here: with the exact 3.2.0 model restored into the tree, `cloud-client` exits 0 and prints "models: 2172 modules imported". The comment above it claimed the import caught "a name collision". It never could. That claim is corrected rather than left to mislead the next reader. `duplicate-fields` reads the AST instead: repeated AnnAssign targets in one class body. Verified both directions on the real defect, not a fixture — exit 1 naming both collisions with the 3.2.0 model in place, exit 0 with 3.2.1, 2362 modules scanned in both. It refuses a zero it did not earn: scanning no modules fails rather than passes, because "found nothing" and "looked at nothing" print the same otherwise. And the gate now has somewhere to run. `.github/workflows/cicd.yml` asks for `hanzo-build-linux-amd64`, which on github.com is served by nothing since the ARC pool retired — every caller since has queued 86402s, GitHub's 24h timeout, and reported "cancelled". That is why this repo's suite has not run in weeks while looking merely flaky. The git-runner fleet serving that label lives on git.hanzo.ai, which sync-from-github.yml already mirrors onto every ten minutes, and where publish-pypi.yml already reads its token from KMS. So the caller goes there, beside the publish it guards, like the other sixteen repos. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
# ~7-line canonical caller — all real config lives in /hanzo.yml.
|
||||
#
|
||||
# This repo declared a gate and had nowhere to run it. `.github/workflows/cicd.yml`
|
||||
# imports the same pipeline, but its default label `hanzo-build-linux-amd64` is
|
||||
# served on github.com by nothing: the ARC pool that last ran it there
|
||||
# (hanzo-build-linux-amd64-lfpvh-runner-vt7lh, hanzoai/finance, 133s) was retired,
|
||||
# and every caller since has queued for 86402s — 24h exactly, GitHub's queue
|
||||
# timeout — and reported "cancelled". A gate that cannot be scheduled is not a
|
||||
# gate, and a red X that says "cancelled" reads like an infrastructure hiccup
|
||||
# rather than what it is: the suite never ran, and has not run for weeks.
|
||||
#
|
||||
# The pool that DOES serve that label is the git-runner fleet on git.hanzo.ai
|
||||
# (universe:infra/k8s/git-runner/statefulset.yaml), which is reachable only from
|
||||
# this plane — the same plane sync-from-github.yml already mirrors every GitHub
|
||||
# commit onto within ten minutes. So the gate belongs here, beside the publish
|
||||
# it guards, and this file is the ~7 lines that ask for it.
|
||||
name: CI/CD
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ['v*']
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
cicd:
|
||||
uses: hanzoai/ci/.hanzo/workflows/build.yml@v1
|
||||
secrets: inherit
|
||||
@@ -15,10 +15,17 @@
|
||||
test:
|
||||
- name: cloud-client
|
||||
# Import every generated module. For generated code this IS the build step:
|
||||
# there is no compiler to catch a bad $ref, a name collision or a model that
|
||||
# references a class the generator declined to emit — the import is what
|
||||
# turns those into a failure instead of an AttributeError in a user's app
|
||||
# six months later.
|
||||
# there is no compiler to catch a bad $ref or a model that references a class
|
||||
# the generator declined to emit — the import is what turns those into a
|
||||
# failure instead of an AttributeError in a user's app six months later.
|
||||
#
|
||||
# IT DOES NOT CATCH A NAME COLLISION, and this comment used to claim it did.
|
||||
# A class body that declares the same field twice is valid Python: the second
|
||||
# binding replaces the first and the module imports clean. 3.2.0 shipped to
|
||||
# PyPI that way — O11yGettableAgentCheckIn declared `integration_config` and
|
||||
# `removed_at` twice each, so the agent's value was read and dropped — and
|
||||
# this gate was green the whole time. `duplicate-fields` below is what sees
|
||||
# it, because it reads the syntax tree rather than the import.
|
||||
#
|
||||
# The arc runner image promises no interpreter, so provision one with uv
|
||||
# when it is missing. A gate that silently no-ops is worse than no gate.
|
||||
@@ -46,6 +53,64 @@ test:
|
||||
sys.exit(1 if bad else 0)
|
||||
'
|
||||
|
||||
- name: duplicate-fields
|
||||
# Read the syntax tree, because the import above cannot see this.
|
||||
#
|
||||
# `x: int` twice in one class body is legal Python. The second annotated
|
||||
# assignment rebinds the name, the first is gone, and nothing complains —
|
||||
# not the interpreter, not pydantic, not the import gate. The model then
|
||||
# advertises a field it does not have, and the value that arrives on the
|
||||
# wire under the shadowed name is read and dropped in silence. That is a
|
||||
# data-loss bug that presents as no bug at all.
|
||||
#
|
||||
# This is not hypothetical and it is not rare: it is what the generator does
|
||||
# every time two distinct spec properties normalise to one Python
|
||||
# identifier. hanzoai 3.2.0 is on PyPI with exactly that — two collisions in
|
||||
# O11yGettableAgentCheckIn — and the gate above was green for it.
|
||||
#
|
||||
# Stdlib `ast` only, no deps: a scan that needs an install is a scan that
|
||||
# can fail to run for reasons unrelated to the code it is reading.
|
||||
run: |
|
||||
set -e
|
||||
if command -v uv >/dev/null 2>&1; then UV=uv; else
|
||||
curl -fsSL https://astral.sh/uv/install.sh | sh
|
||||
UV="$HOME/.local/bin/uv"
|
||||
fi
|
||||
"$UV" run --no-project --python 3.12 python -c '
|
||||
import ast, pathlib, sys
|
||||
root = pathlib.Path("pkg/hanzoai/cloud")
|
||||
hits, n = [], 0
|
||||
for p in sorted(root.rglob("*.py")):
|
||||
n += 1
|
||||
try:
|
||||
tree = ast.parse(p.read_text(encoding="utf-8", errors="replace"))
|
||||
except SyntaxError as e:
|
||||
hits.append("%s: does not parse: %r" % (p, e))
|
||||
continue
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, ast.ClassDef):
|
||||
continue
|
||||
seen = {}
|
||||
for s in node.body:
|
||||
# AnnAssign is `name: Type = ...`, how every field is declared
|
||||
if isinstance(s, ast.AnnAssign) and isinstance(s.target, ast.Name):
|
||||
if not s.target.id.startswith("__"):
|
||||
seen.setdefault(s.target.id, []).append(s.lineno)
|
||||
for f, lines in sorted(seen.items()):
|
||||
if len(lines) > 1:
|
||||
hits.append("%s: %s.%s declared %d times, lines %s"
|
||||
% (p, node.name, f, len(lines), lines))
|
||||
print("duplicate-fields: scanned %d generated modules" % n)
|
||||
# A zero here must mean "looked and found nothing", never "looked at
|
||||
# nothing". If the tree moves, this fails loudly instead of passing empty.
|
||||
if n == 0:
|
||||
print("FAIL scanned no modules under", root, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
for h in hits:
|
||||
print("FAIL", h, file=sys.stderr)
|
||||
sys.exit(1 if hits else 0)
|
||||
'
|
||||
|
||||
- name: examples
|
||||
# The canonical flows, imported against the client that was just generated.
|
||||
# Each `from hanzoai.cloud import X` is an assertion that the symbol still
|
||||
|
||||
Reference in New Issue
Block a user