@@ -193,10 +193,14 @@ def rewrite_python_manifest(path: Path, sdk: PythonSdk) -> bool:
193193 text = path .read_text (encoding = "utf-8" )
194194
195195 def _sub (match : re .Match [str ]) -> str :
196+ # Normalize the operator to exact ``==``. The core SDK pin must never be a
197+ # floor/open range (AAASM-4704 and the repo's "never a bare >=" rule),
198+ # so a bump self-heals a drifted operator; the --check audit enforces the
199+ # same invariant. The regex still captures ``op`` so a non-``==`` pin is
200+ # matched and rewritten.
196201 indent = match .group ("indent" )
197- op = match .group ("op" )
198202 tail = match .group ("tail" )
199- return f'{ indent } "{ sdk .package } { op } { sdk .version } "{ tail } '
203+ return f'{ indent } "{ sdk .package } == { sdk .version } "{ tail } '
200204
201205 new_text = _PY_PIN_RE .sub (_sub , text )
202206 return _write_if_changed (path , new_text )
@@ -755,7 +759,7 @@ def process_prereq_rows(repo_root: Path, versions: SdkVersions) -> list[Path]:
755759# exactly ``@agent-assembly/sdk``; the go id is exactly
756760# ``github.com/ai-agent-assembly/go-sdk`` followed by whitespace.
757761_PY_PIN_AUDIT_RE = re .compile (
758- r"(?<![\w/@.-])agent-assembly(?: ==|>=|~=|<=|!=|<|>)(?P<ver>[^\"'\s,]+)"
762+ r"(?<![\w/@.-])agent-assembly(?P<op> ==|>=|~=|<=|!=|<|>)(?P<ver>[^\"'\s,]+)"
759763)
760764_NODE_PIN_AUDIT_RE = re .compile (r'"@agent-assembly/sdk"\s*:\s*"(?P<ver>[^"]+)"' )
761765_GO_PIN_AUDIT_RE = re .compile (
@@ -816,6 +820,30 @@ def _audit_pins(repo_root: Path, versions: SdkVersions) -> list[str]:
816820 return problems
817821
818822
823+ # The core SDK pin must be exact (``==``): a floor (``>=``) or open range lets a
824+ # resolver pull a future major and silently break an example (AAASM-4704, and the
825+ # repo's own "never a bare >=" rule). The version-drift audit above only checks
826+ # the version token, so this pass enforces the operator separately. Python
827+ # manifests are the only core-SDK pins that carry an operator — node pins are a
828+ # bare ``"<version>"`` string and go pins are a bare ``go.mod`` version.
829+ def _audit_py_operator (repo_root : Path , versions : SdkVersions ) -> list [str ]:
830+ """Report every python core-SDK pin whose operator is not exact (``==``)."""
831+
832+ problems : list [str ] = []
833+ for path in _globbed (repo_root , _PY_PIN_GLOBS ):
834+ for lineno , line in _audit_lines (path ):
835+ if EXEMPT_MARKER in line :
836+ continue
837+ match = _PY_PIN_AUDIT_RE .search (line )
838+ if match and match .group ("op" ) != "==" :
839+ rel = path .relative_to (repo_root )
840+ problems .append (
841+ f"{ rel } :{ lineno } : uses { match .group ('op' )!r} operator on the "
842+ f"core SDK pin, expected '=='"
843+ )
844+ return problems
845+
846+
819847def _audit_prose (repo_root : Path , versions : SdkVersions ) -> list [str ]:
820848 """Report every README/doc prose line whose SDK version drifts from the SoT.
821849
@@ -854,7 +882,9 @@ def audit(repo_root: Path, versions: SdkVersions) -> list[str]:
854882 """Return every drifted SDK-version literal as a sorted ``file:line`` list."""
855883
856884 return sorted (
857- _audit_pins (repo_root , versions ) + _audit_prose (repo_root , versions )
885+ _audit_pins (repo_root , versions )
886+ + _audit_py_operator (repo_root , versions )
887+ + _audit_prose (repo_root , versions )
858888 )
859889
860890
0 commit comments