Repository navigation
[AAASM-6196] ✅ (examples): Pin the gateway endpoint so sdk-only smoke tests are hermetic - #628
Conversation
…e hermetic test_init_assembly_sdk_only_requires_no_gateway asserted that sdk-only needs no gateway, but nothing in it made a gateway unreachable -- it relied on nothing happening to listen on the gRPC port. With a gateway running locally the test registered for real and passed regardless of what the SDK did: measured against released 0.0.1rc7, which breaks that contract, the test passed with a live gateway on :50051 and failed without one. gateway_url cannot fix this. The resolver takes only the URL's host and substitutes the fixed gRPC port 50051, so every loopback value lands on the one port a local gateway occupies. AA_GATEWAY_ENDPOINT is the one lever honoured verbatim, and port 1 is privileged, so no user process can occupy it and the connection is refused immediately. Refs AAASM-6196 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… back The pin has to hold for the 17th example too. EX-MOCK-05 reports an init_assembly call with no AA_GATEWAY_ENDPOINT pin in the same function; EX-MOCK-06 reports a pin that names port 50051, which reinstates the ambient dependency while looking like a fix. Scoping is per innermost function, so a pin in a sibling test does not vouch for an unguarded one. The success line now prints pinned/total sites, so a parser that stops recognising these calls reports 0 of 0 rather than a clean tree. AFTER in the self-tests gains the pin: a fixture frozen at the previous fix would quietly stop exercising the rule added after it. BEFORE stays verbatim and now reports EX-MOCK-05 alongside EX-MOCK-01 -- both defects really were in that text at once. Refs AAASM-6196 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Matching `setenv` with `endswith` also matched `os.unsetenv`, which clears the variable rather than pinning it -- the opposite of the rule's intent. Compare the final attribute exactly. An endpoint value the gate cannot read counted as a pin, so a runtime-built string could have been :50051 with nothing reporting it. Refuse it and say so, the way EX-MOCK-04 already refuses a computed attribute name. `gateway_coverage` sat above the helper it calls; moved below it so the file reads in dependency order. Refs AAASM-6196 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Chisanan232
left a comment
There was a problem hiding this comment.
Review of record for the merge, under the repository's solo-maintainer arrangement: APPROVE is impossible here (422 Review Can not approve your own pull request — probed on this PR, not assumed), CODEOWNERS is * @Chisanan232, and that account is the repository's only collaborator, so no account in existence can supply the required approval. Merged as a true merge commit with an admin merge once every check was green, which is the same shape all 12 prior self-authored merges on this branch took (parents=2, reviewDecision: REVIEW_REQUIRED, zero reviews). Squash and rebase are disabled repository-wide, so the merge method was not a choice.
Gate state at the merged head c2778927: 28 check runs, 28 success, 0 failure, 0 cancelled, 0 neutral, 0 still reported as running. The earlier neutral CodeQL observed on the previous head resolved to success on this one; the decorative-neutral behaviour itself is tracked separately as AAASM-6185 and is not a finding against this change.
Self-review found three real holes in my own diff, all fixed in c277892 before merge. Recording them because each one would have left the new gate quietly weaker than it reads:
| Hole | Why it mattered | Fix |
|---|---|---|
endswith("setenv") also matches os.unsetenv |
os.unsetenv("AA_GATEWAY_ENDPOINT") clears the variable, and the detector counted it as a valid pin — the exact inversion of the rule's intent |
exact final-segment comparison; the unsetenv form now yields ['EX-MOCK-05'] |
| A pin whose value cannot be read statically counted as a pin | A runtime-built endpoint string could be :50051, which is the one port that restores the ambient dependency being removed |
EX-MOCK-06 now refuses an unreadable value, the same way EX-MOCK-04 already refuses a computed attribute name |
gateway_coverage was defined above the helper it calls |
Worked by late binding, but read as though the ordering were meaningful | moved below its dependency |
Anti-vacuity, measured rather than argued. The proof used two genuinely published SDK versions instead of a synthetic mutant, with a real gateway listening on 127.0.0.1:50051 throughout, running the actual tests/test_smoke.py of python/custom-tool-policy (the one example whose real test file runs in isolation — its only dependency is agent-assembly plus pytest):
| SDK under test | test file | result |
|---|---|---|
0.0.1rc6 (what main pins) |
before this PR | pass |
0.0.1rc6 |
after this PR | pass |
0.0.1rc7 |
before this PR | pass — this is the defect |
0.0.1rc7 |
after this PR | fail |
Exactly one cell moved, which is what makes rows 3 and 4 evidence rather than decoration. The failure is ConfigurationError: Failed to initialize assembly runtime: gateway gRPC endpoint is unreachable for registration at agent_assembly/core/assembly.py:327, raised from _register_agent_with_gateway at assembly.py:506 — byte-for-byte the failure the 16 per-example jobs report on #613. Rows 1 and 2 are the evidence that main stays green.
Worth stating plainly, because it is the one thing the four rows above do not literally show: the guarded runs never consulted port 50051 at all. If they had, row 4 could not fail, because a gateway was listening there the whole time. That is why the guarded rc6 pass is not contingent on whether a local gateway happens to be up.
The new rules were falsified against three mutations, each reverted afterwards: the pre-PR text reports EX-MOCK-05 at the init_assembly line and exits 1; pinning to :50051 reports EX-MOCK-06 and exits 1; a runtime-built variable name reports EX-MOCK-05 and exits 1. The gate also now prints 16 of 16 init_assembly site(s) pin AA_GATEWAY_ENDPOINT, so a parser that stops recognising these calls reports 0 of 0 rather than a clean tree. Before writing the rule I measured that exactly 16 init_assembly call sites exist across all 32 gate-scanned files, all of them in the target test, so the rule has no false positives on the current tree.
No collateral damage: scripts/ suite 92 passed; generate_example_metadata.py --check clean; check_pnpm_overrides_parity.py 0 mismatches; check_claim_vocabulary.py 8 non-blocking findings whose count is byte-identical to main (verified by stashing), all in READMEs this PR does not touch.
What this deliberately does not do. It does not unblock #613, and it must not be read as doing so. rc7 predates the AAASM-6155 fix by five days, so no published version can contain it, and that remains the release-side condition. It also mocks no SDK internal — the whole point of AAASM-6196 is to stop the test depending on something outside itself, and swapping an ambient gateway for a patched private seam would just relocate the same problem.
Evidence is textual by deliberate choice, per this workstation's data-handling policy.



What changed
Two commits, both test-side only. No example's source, README, governance posture or advertised behaviour changes.
python/*/tests/test_smoke.pypinAA_GATEWAY_ENDPOINT=http://127.0.0.1:1intest_init_assembly_sdk_only_requires_no_gateway, via themonkeypatchfixture.scripts/check_example_sdk_mocks.pygainsEX-MOCK-05/EX-MOCK-06so the pin holds for the next example too, with self-tests.Why
The test's name asserts that
mode="sdk-only"needs no gateway. Nothing in it made a gateway unreachable — it passedgateway_url="http://localhost:8080"and relied on the ambient fact that nothing happened to be listening on the gRPC port. On a machine running the product it registered against a real gateway and passed no matter what the SDK did.That is not hypothetical. This was found while checking whether released
0.0.1rc7really breaks the AAASM-6155 contract. The first answer was "it doesn't" — wrong, because anaa-api-serverwas listening on127.0.0.1:50051at the time.gateway_urlcannot fix it.resolve_gateway_grpc_endpointkeeps only that URL's host and substitutes the fixed gRPC port 50051 — deliberate, so a remote gateway resolves to that host's gRPC port rather than always loopback (AAASM-4547). So every loopback value ofgateway_urllands on the one port a local gateway occupies.AA_GATEWAY_ENDPOINTis the one lever honoured verbatim.Port 1 is privileged, so no user process can occupy it, and connecting is refused immediately — measured
ECONNREFUSEDin 0.000s. Binding a socket withoutlisten()to reserve a port was tried and rejected: on macOS it black-holes the connection instead of refusing it, so the test would hang for the full socket timeout and fail witherrnounset.How to verify
python/custom-tool-policydepends on nothing butagent-assemblyandpytest, so its real test file runs end to end against either SDK. Two venvs, the realtests/test_smoke.py, anaa-api-serverlistening on127.0.0.1:50051throughout:0.0.1rc6(whatmainpins)0.0.1rc60.0.1rc70.0.1rc7One cell moved, and it is the right one. The SDK in rows 3 and 4 is byte-identical and the machine is unchanged; only the pin differs. The rc7 failure is
ConfigurationError: Failed to initialize assembly runtime: gateway gRPC endpoint is unreachable for registration, raised atagent_assembly/core/assembly.py:327from aRuntimeErroratassembly.py:506in_register_agent_with_gateway— the same failure the 16 per-example jobs report on #613.Rows 1 and 2 are why this is safe to land:
mainpins rc6, and rc6 passes with the endpoint pinned. Rows 3 and 4 are the anti-vacuity evidence, obtained from two real published versions rather than a synthetic mutant — the assertion genuinely discriminates a correct SDK from a broken one once the endpoint is pinned down.To reproduce:
The new gate rules
python3 scripts/check_example_sdk_mocks.py --root .now reports coverage as well as seams:Printing
pinned/totalis deliberate: a parser that stops recognising these calls reports0 of 0, which is visibly not a clean tree.Falsified against three mutations of the tree, each reverted afterwards:
EX-MOCK-05at theinit_assemblylinehttp://127.0.0.1:50051EX-MOCK-06"AA_GATEWAY" + "_ENDPOINT"EX-MOCK-05The third case matters: a string assembled at runtime cannot be read statically, so the gate refuses it rather than assuming it spells the right variable.
EX-MOCK-05scopes the pin to the innermost function containing the call, so a pin in a sibling test does not vouch for an unguarded one — asserted directly intest_a_pin_in_a_sibling_test_does_not_vouch_for_this_one. Three pin mechanisms are accepted (monkeypatch.setenv,patch.dict(os.environ, ...), a directos.environ[...] =); the rule is about the endpoint being pinned, not about which tool pins it.scripts/test_check_example_sdk_mocks.py: 18 → 28 tests, all passing; the wholescripts/suite is 91 passing. TheAFTERfixture gains the pin, because a fixture frozen at the previous fix would quietly stop exercising the rule added after it.BEFOREstays verbatim and now reportsEX-MOCK-05besideEX-MOCK-01— both defects really were in that text at once.Other gates, run locally
check_example_sdk_mocks.py --root .pytest scripts/generate_example_metadata.py --checkcheck_claim_vocabulary.pymain, all in READMEs this PR does not touchcheck_pnpm_overrides_parity.pyWhat this deliberately does not do
It does not unblock or alter #613. Those 16 per-example jobs stay red there because rc7 genuinely breaks the contract — the correct outcome, and now for a reason that does not depend on the runner's network state. This is not a workaround for AAASM-6155.
It does not mock an SDK internal. That is what AAASM-6156 just finished removing from these files, and reintroducing one here would trade a paid-off defect for a fresh copy of it.
No assertion is relaxed and no test is skipped.
Closes AAASM-6196
Evidence is textual by deliberate choice, per this workstation's data-handling policy.