Commit 14b943a
Address PR #111 review: per-scan registry + open type field + tighter signature gate
Four findings from PR #111 review — three P1s and one P2. All four
addressed; 7 regression tests added (now 28 total in
test_adapter_entry_point_discovery.py).
P1 #1 — --no-plugins did not disable adapters after a prior enabled scan.
Pre-fix, ``discover_third_party_adapters`` mutated the module-global
``REGISTRY``. A later in-process scan with ``plugins_enabled=False``
skipped discovery but ``_load_sources`` still resolved the
already-registered adapter through that same global. Reproduced:
``report.loaded_adapters == []`` on scan two but the adapter still
ran. Trust boundary violated.
P1 #2 — repeated scans misclassified stable third-party adapters as
``source_type_collision``. Same root cause: collision detection used
``registry._adapters.keys()`` AFTER scan-one's third-party
registration had mutated the global. Scan two thus saw the same
adapter as a "built-in" collision while still executing it.
Fix for both: per-scan registry clone.
- New ``AdapterRegistry.clone()`` returns a shallow-copy registry
with a fresh ``_adapters`` dict. Subsequent ``register()`` calls
on the clone do NOT propagate to the global.
- ``_load_inputs`` now builds a per-scan registry via
``scan_registry = REGISTRY.clone()``, runs discovery against
``scan_registry``, and threads ``scan_registry`` through to
``_load_sources`` via a new ``registry=`` kwarg.
- ``_load_sources`` accepts the per-scan registry, falling back to
the global only for legacy callers that bypass ``_load_inputs``
(existing ``tests/test_adapter_registry.py`` patterns).
- Global ``REGISTRY`` stays builtin-only across the process. Tests
that monkeypatch ``REGISTRY._adapters`` keep working because the
clone captures the global's state at scan-start (after monkeypatch).
Two new regression tests pin the fix:
- ``test_no_plugins_disables_third_party_after_prior_enabled_scan``:
scan-one runs with plugins enabled (adapter runs once); scan-two
in the same process with ``plugins_enabled=False`` must see
``loaded_adapters == []`` AND the adapter must NOT run a second
time. Pre-fix the invocation counter incremented; post-fix it
stays at 1.
- ``test_second_scan_does_not_misclassify_third_party_as_collision``:
two consecutive scans with plugins enabled must both report
``validation_status == "valid"`` on the same adapter. Pre-fix
scan two reported ``source_type_collision``.
The autouse ``_reset_registry_after_test`` fixture is no longer
needed — discovery doesn't mutate the global — and is replaced with
a minimal ``_populate_registry`` fixture that just triggers lazy
population.
P1 #3 — per_source third-party adapters could not be referenced in
shipgate.yaml. ``ToolSourceConfig.type`` was a closed ``Literal`` of
built-in source types, so a manifest declaring
``type: my_third_party_source`` failed Pydantic validation BEFORE
discovery had a chance to register the loader. The v0.20 third-party
adapter surface was unusable for its main advertised use case.
Fix:
- ``ToolSourceConfig.type`` relaxed from ``Literal[...]`` to ``str``.
- New module-level constant ``BUILTIN_TOOL_SOURCE_TYPES`` enumerates
the seven built-in source types for documentation and for tests
that need to iterate the curated set.
- New constant ``BUILTIN_PER_SCAN_ONLY_TOOL_SOURCE_TYPES`` ({n8n,
openai_api, anthropic_api, validation}) preserves the existing
rejection: those four built-ins are per_scan-only and ingest config
from a dedicated top-level manifest section, so they MUST NOT
appear in ``tool_sources``. A new ``reject_per_scan_only_builtins``
model validator on ``ToolSourceConfig`` raises ``ValueError`` at
manifest-load with a routable error pointing the user to the
top-level section.
- Unknown source types (typos like ``n8N``, genuinely-third-party
types without a registered adapter) still fail at scan time with
``ConfigError`` from ``AdapterRegistry.require``. Exit-2 contract
unchanged.
- ``tests/test_n8n.py::test_n8n_top_level_config_is_accepted_and_tool_source_type_is_rejected``
updated: the case-variation (``type: n8N``) assertion now uses
``run_scan`` (dispatcher-time error) instead of ``load_manifest``
(parse-time error). Same exit code, different layer.
- ``tests/test_adapter_registry.py`` + ``tests/harness/test_overlay_renderer.py``
updated to iterate ``BUILTIN_TOOL_SOURCE_TYPES`` instead of
``get_args(ToolSourceConfig.type)``.
- New regression test
``test_per_source_third_party_adapter_referenced_from_manifest``:
builds a manifest with ``type: demo_source``, registers a matching
third-party per_source adapter via entry-points, asserts ``run_scan``
succeeds and the adapter actually runs.
The JSON manifest schema (``docs/manifest-v0.1.json``) loses the
``enum`` constraint on ``tool_sources[].type`` — schema is now
``{"type": "string"}``. IDE autocomplete for built-in names is a
follow-up; the trust gate is the dispatcher, not the schema.
P2 #4 — signature validator accepted methods that crash the dispatcher.
``_protocol_error`` rejected only >3 required positional and zero
positional. Two failure modes slipped through:
- ``def load(self, source)`` — only 1 positional slot; dispatcher's
``adapter.load(source, base_dir, manifest)`` call fails with
``TypeError: too many positional arguments``.
- ``def load(self, source, base_dir, manifest, *, must_set)`` —
required keyword-only param; dispatcher passes no kwargs.
Both now caught at the ``bad_protocol`` gate before registration.
``_protocol_error`` now:
- Computes ``accepting_slots`` = count of (required + optional)
positional, and ``has_var_positional`` = whether ``*args`` is
present.
- Rejects when ``accepting_slots < 3 AND not has_var_positional``.
- Rejects any required keyword-only parameter (mirrors plugin
validation).
Four new regression tests:
- ``test_gate_bad_protocol_load_too_few_positional``: ``load(self,
source)`` lands in ``bad_protocol`` with "at least 3 positional"
in the error.
- ``test_gate_bad_protocol_load_required_keyword_only``: required
kw-only lands in ``bad_protocol`` with "keyword-only" + the param
name in the error.
- ``test_gate_bad_protocol_accepts_var_positional``: ``load(self,
*args)`` is valid (can bind 3 args).
- ``test_gate_bad_protocol_accepts_optional_keyword_only``: optional
kw-only with default is valid.
Verification: 1264 tests pass; ruff clean; schema roundtrip --check
exits 0; coverage 88.02%.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 5400fb2 commit 14b943a
9 files changed
Lines changed: 576 additions & 68 deletions
File tree
- docs
- src/agents_shipgate
- cli
- inputs
- schemas
- tests
- harness
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1502 | 1502 | | |
1503 | 1503 | | |
1504 | 1504 | | |
1505 | | - | |
1506 | | - | |
1507 | | - | |
1508 | | - | |
1509 | | - | |
1510 | | - | |
1511 | | - | |
1512 | | - | |
1513 | | - | |
1514 | 1505 | | |
1515 | 1506 | | |
1516 | 1507 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
366 | 366 | | |
367 | 367 | | |
368 | 368 | | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
369 | 379 | | |
370 | 380 | | |
371 | | - | |
| 381 | + | |
372 | 382 | | |
373 | 383 | | |
374 | 384 | | |
375 | | - | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
376 | 388 | | |
377 | 389 | | |
378 | 390 | | |
| |||
1386 | 1398 | | |
1387 | 1399 | | |
1388 | 1400 | | |
| 1401 | + | |
1389 | 1402 | | |
1390 | | - | |
| 1403 | + | |
1391 | 1404 | | |
1392 | 1405 | | |
1393 | 1406 | | |
| |||
1397 | 1410 | | |
1398 | 1411 | | |
1399 | 1412 | | |
1400 | | - | |
| 1413 | + | |
1401 | 1414 | | |
1402 | 1415 | | |
1403 | 1416 | | |
| |||
1409 | 1422 | | |
1410 | 1423 | | |
1411 | 1424 | | |
| 1425 | + | |
| 1426 | + | |
| 1427 | + | |
| 1428 | + | |
| 1429 | + | |
| 1430 | + | |
| 1431 | + | |
| 1432 | + | |
1412 | 1433 | | |
| 1434 | + | |
| 1435 | + | |
1413 | 1436 | | |
1414 | 1437 | | |
1415 | 1438 | | |
1416 | 1439 | | |
1417 | 1440 | | |
1418 | 1441 | | |
1419 | | - | |
| 1442 | + | |
1420 | 1443 | | |
1421 | 1444 | | |
1422 | 1445 | | |
1423 | 1446 | | |
1424 | | - | |
| 1447 | + | |
1425 | 1448 | | |
1426 | 1449 | | |
1427 | 1450 | | |
1428 | 1451 | | |
1429 | 1452 | | |
1430 | 1453 | | |
1431 | 1454 | | |
1432 | | - | |
| 1455 | + | |
1433 | 1456 | | |
1434 | 1457 | | |
1435 | 1458 | | |
1436 | 1459 | | |
1437 | | - | |
| 1460 | + | |
1438 | 1461 | | |
1439 | 1462 | | |
1440 | 1463 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
312 | 312 | | |
313 | 313 | | |
314 | 314 | | |
315 | | - | |
316 | | - | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
317 | 331 | | |
318 | 332 | | |
319 | 333 | | |
| |||
324 | 338 | | |
325 | 339 | | |
326 | 340 | | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
327 | 346 | | |
328 | 347 | | |
329 | 348 | | |
330 | 349 | | |
331 | 350 | | |
332 | 351 | | |
333 | | - | |
334 | | - | |
335 | | - | |
336 | 352 | | |
337 | 353 | | |
338 | 354 | | |
339 | 355 | | |
340 | 356 | | |
341 | | - | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
342 | 368 | | |
343 | | - | |
344 | | - | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
345 | 389 | | |
346 | 390 | | |
347 | 391 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
152 | 152 | | |
153 | 153 | | |
154 | 154 | | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
155 | 187 | | |
156 | 188 | | |
157 | 189 | | |
| |||
258 | 290 | | |
259 | 291 | | |
260 | 292 | | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
261 | 308 | | |
262 | 309 | | |
263 | 310 | | |
| |||
268 | 315 | | |
269 | 316 | | |
270 | 317 | | |
271 | | - | |
272 | | - | |
273 | | - | |
274 | | - | |
275 | | - | |
276 | | - | |
277 | | - | |
278 | | - | |
279 | 318 | | |
280 | 319 | | |
281 | 320 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
50 | 77 | | |
51 | 78 | | |
52 | 79 | | |
53 | 80 | | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
63 | 102 | | |
64 | 103 | | |
65 | 104 | | |
66 | 105 | | |
67 | 106 | | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
68 | 124 | | |
69 | 125 | | |
70 | 126 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
52 | 58 | | |
53 | 59 | | |
54 | | - | |
| 60 | + | |
55 | 61 | | |
56 | | - | |
| 62 | + | |
57 | 63 | | |
58 | 64 | | |
59 | 65 | | |
| |||
0 commit comments