From 268a796cb9e69b107bda167be6722b5a4497cd37 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:41:57 +0000 Subject: [PATCH] Revert "Second corrections: A2A payment settled the wrong product, x402 was mistaken for money, experiments were promoted by unrelated revenue (#30)" This reverts commit 061dcea00656d28e22f6a44c2ade5f093d110314. --- docs/INTERFACE.md | 1 - live/guild/app/a2a.py | 33 ++- live/guild/app/a2a_x402.py | 69 +----- live/guild/app/experiments.py | 173 +++++--------- live/guild/app/indexsources.py | 62 +---- live/guild/app/main.py | 52 ++--- live/guild/app/mcp_server.py | 23 +- live/guild/app/payments.py | 48 ---- live/guild/app/swarm/runner.py | 35 +-- live/guild/contract/contract.json | 6 - live/guild/tests/test_index_corrections.py | 8 +- live/guild/tests/test_index_fixes2.py | 253 --------------------- live/guild/tests/test_passport_offers.py | 9 +- live/guild/tests/test_trust_index.py | 20 +- render.yaml | 7 +- 15 files changed, 116 insertions(+), 683 deletions(-) delete mode 100644 live/guild/tests/test_index_fixes2.py diff --git a/docs/INTERFACE.md b/docs/INTERFACE.md index f5446a8..daa2ca6 100644 --- a/docs/INTERFACE.md +++ b/docs/INTERFACE.md @@ -28,7 +28,6 @@ guild_mediated requires two-party cryptographic participation, a Guild-observed - `GET /.well-known/glama.json` - `POST /adjudicators/enroll` - `POST /admin/agents/{agent_id}/first-party` -- `POST /admin/index/cycle` - `POST /admin/issuer/rotate` - `GET /agents` - `GET /agents.md` diff --git a/live/guild/app/a2a.py b/live/guild/app/a2a.py index c0e4bda..5394701 100644 --- a/live/guild/app/a2a.py +++ b/live/guild/app/a2a.py @@ -392,23 +392,17 @@ def _agent_card(base: str) -> dict[str, Any]: "protocolVersion": "0.3.0", "name": "Agent Guild", "description": ( - "Can I safely use or pay this endpoint right now? Send " - "'preflight: ' and get back, free and live at request time, " - "what that endpoint CLAIMS and separately what it just PROVED: a " - "real protocol handshake (not merely HTTP 200), a resolvable agent " - "card, whether the card is signed, and whether an advertised " - "payment surface actually challenges with 402. Measured " - "2026-07-31: 92.9% of registry-listed agents report healthy and " - "33.9% complete a task. Send 'index' to search every endpoint we " - "have observed. 'deep-preflight: ' adds drift history, " - "cross-source corroboration and an explicit allow/caution/block " - "policy verdict (paid, x402). Also free, and supporting rather " - "than the headline: a portable Agent Passport for your own agent " - '(POST /agents/register {"name": "", "capabilities": [...], ' - '"src": "passport_offer:agent_card"} -> POST /agents/{id}/prove ' - "-> GET /agents/{id}/passport), and 'check: ' to find " - "the safest agent for a job. Richer surface over MCP and REST; see " - "documentationUrl." + "The trust and settlement layer for AI agents. Claim a portable, " + "verifiable Agent Passport — free, three calls: POST " + '/agents/register {"name": "", "capabilities": [...], ' + '"src": "passport_offer:agent_card"} → POST /agents/{id}/prove ' + "(then /prove/verify) → GET /agents/{id}/passport, a Guild-signed " + "credential any party verifies offline. Also: send a text message " + "naming a capability (e.g. 'check: fact-check') and receive the " + "safest agent to hire, a hire/caution/avoid verdict, a ranked " + "shortlist, and provenance-labelled proof — the same answer as " + "GET /check. Richer surface (attest, escrow) over MCP and REST; " + "see documentationUrl." ), "url": f"{base}/a2a", "preferredTransport": "JSONRPC", @@ -936,7 +930,7 @@ async def a2a_endpoint(request: Request): # task TEXT every A2A client renders — not only in metadata, # which actor a2a:net:bba57b53… demonstrably never parsed. task = a2a_x402.build_payment_required_task( - preq, preq.cost, demand_ctx=dem, actor=actor, ua=ua_tag) + preq, preq.cost, demand_ctx=dem) store.record_event(actor, "x402_payment_required", ua=ua_tag, endpoint="best_agent", transport="a2a", capability=caller_cap) @@ -959,8 +953,7 @@ async def a2a_endpoint(request: Request): _target = _pfd.group(2) if _x402_a2a_active(): preq = payments.deep_preflight_request(_target) - task = a2a_x402.build_payment_required_task( - preq, preq.cost, actor=actor, ua=ua_tag) + task = a2a_x402.build_payment_required_task(preq, preq.cost) store.record_event(actor, "x402_payment_required", ua=ua_tag, endpoint="preflight_deep", transport="a2a", target=_target[:300]) diff --git a/live/guild/app/a2a_x402.py b/live/guild/app/a2a_x402.py index bdb5f11..c1f8757 100644 --- a/live/guild/app/a2a_x402.py +++ b/live/guild/app/a2a_x402.py @@ -168,8 +168,6 @@ def _challenge_text(required: dict[str, Any], ctx: Optional[dict[str, Any]], def build_payment_required_task(preq: PaidRequest, credits_cost: int, demand_ctx: Optional[dict[str, Any]] = None, - actor: str = "", - ua: str = "", ) -> dict[str, Any]: """Create + persist a payment task and return the input-required Task.""" task_id = "x402task_" + uuid.uuid4().hex @@ -182,17 +180,6 @@ def build_payment_required_task(preq: PaidRequest, credits_cost: int, "request_hash": preq.request_hash, "credits_cost": credits_cost, "capability": dict(preq.query).get("capability"), - # The EXACT operation and its canonical parameters. A2A quotes in one - # message and settles in another, so the operation must survive the - # round trip; rebuilding it from a default made a deep-preflight - # challenge settle the wrong operation and return the wrong product. - # Stored here, on OUR record, and never read back from the submission. - "operation_params": dict(preq.query), - # Who was quoted. Recorded at quote time so the settled event is - # attributable to the same caller under the central attribution rule — - # an unattributable settlement can never be a customer. - "actor": actor or "", - "ua": ua or "", "required": required, "receipts": [], "created_at_epoch": time.time(), @@ -302,7 +289,8 @@ def handle_payment_submission(message: dict[str, Any], # Produce the paid result, bind receipt+evidence to its exact bytes. # demand for this request was recorded pre-authorization (B1) when the # payment-required task was created — never count it again on payment. - result = _produce_for(preq, settled, task) + result = store.check(dict(preq.query).get("capability") or "", + demand_recorded=True) body = json.dumps(result, default=str).encode("utf-8") fin = settled.finalize(body) settle_response = _settle_response({ @@ -356,58 +344,9 @@ def _settle_response(settle: dict[str, Any], return out -def _produce_for(preq: PaidRequest, settled: Any, - task: dict[str, Any]) -> dict[str, Any]: - """Produce the product that was actually paid for. - - Every branch records its own settlement metadata, because "the gateway - settled" and "money moved on mainnet" are different claims and only the - second one is revenue.""" - from . import deepcheck - facts = { - "settlement_mode": "x402", - "settlement_confirmed": bool((settled.record or {}).get("confirmed")), - "settlement_mainnet": bool((settled.record or {}).get("mainnet")), - "settlement_network": (settled.record or {}).get("network"), - "settlement_amount_atomic": (settled.record or {}).get("amount_atomic"), - "settlement_tx": (settled.record or {}).get("transaction"), - } - params = dict(preq.query) - actor = task.get("actor") or "a2a" - ua = task.get("ua") or "a2a/x402" - if preq.operation == "deep_preflight": - url = params.get("url") or "" - out = deepcheck.deep_preflight(store, url) - store.record_event(actor, "deep_preflight_run", ua=ua, - endpoint="preflight_deep", transport="a2a", - target=url, paid=True, - verdict=(out.get("policy") or {}).get("decision"), - **facts) - return out - if preq.operation == "evidence_bundle": - url = params.get("url") or "" - out = deepcheck.evidence_bundle( - store, url, ttl_s=int(params.get("ttl_seconds") or 3600)) - store.record_event(actor, "evidence_bundle_issued", ua=ua, - endpoint="evidence_bundle", transport="a2a", - target=url, paid=True, **facts) - return out - return store.check(params.get("capability") or "", demand_recorded=True) - - def _preq_from_task(task: dict[str, Any]) -> PaidRequest: - """Reconstruct the quoted request from OUR OWN stored task record. - - Only `operation` and `operation_params` are consulted, both written by us - at quote time. Nothing from the payment submission reaches this function — - a caller must not be able to steer settlement onto a different operation - than the one they were quoted.""" - operation = task.get("operation") or "best_agent" - params = task.get("operation_params") - if not isinstance(params, dict): - # legacy tasks quoted before operation_params existed - params = {"capability": task.get("capability") or ""} - return payments.request_from_stored(operation, params) + cap = task.get("capability") or "" + return payments.check_request(cap) _ERR_CODES = { diff --git a/live/guild/app/experiments.py b/live/guild/app/experiments.py index 8eb4184..de11041 100644 --- a/live/guild/app/experiments.py +++ b/live/guild/app/experiments.py @@ -118,8 +118,7 @@ def define(store: Any, key: str, *, hypothesis: str, variable: str, return rec -def qualified_exposure(store: Any, operation: Optional[str] = None - ) -> dict[str, Any]: +def qualified_exposure(store: Any) -> dict[str, Any]: """Genuinely-external actors who reached a decision surface. Uses the SAME central attribution rule as every other honest number in the @@ -128,16 +127,9 @@ def qualified_exposure(store: Any, operation: Optional[str] = None which is exactly how self-traffic gets laundered into a growth metric.""" from . import attribution - # Scoped to the experiment's own surface where one is given: exposure to a - # DIFFERENT offer is not exposure to this one. - decision_surfaces = ({"preflight_run", "deep_preflight_run"} - if operation == "deep_preflight" else - {"evidence_bundle_issued"} - if operation == "evidence_bundle" else - {"watch_provisioned"} if operation == "watch_cycle" - else {"preflight_run", "deep_preflight_run", - "evidence_bundle_issued", "watch_provisioned", - "index_view"}) + decision_surfaces = {"preflight_run", "deep_preflight_run", + "evidence_bundle_issued", "watch_provisioned", + "index_view"} actors: set[str] = set() events = 0 for e in getattr(store, "events", []): @@ -165,91 +157,71 @@ def qualified_exposure(store: Any, operation: Optional[str] = None } -#: The three INDEPENDENT conditions that must all hold before a settlement may -#: be called revenue. `mode == "x402"` alone is not money: the same rail runs -#: on Base Sepolia by default, where a successful settlement is a successful -#: payment of nothing, and a facilitator's word is not a chain receipt. +#: The ONLY settlement mode that is money. `credits_sandbox` is an internal +#: unit we mint and hand out as trial credits; `free` is the soft launch. SETTLED_MODE = "x402" -#: Which events belong to which paid operation. An experiment on the -#: deep_preflight price must be judged on deep_preflight revenue — not on -#: unrelated escrow settlement or a watch sold for a different offer. -OPERATION_EVENTS: dict[str, tuple[str, ...]] = { - "deep_preflight": ("deep_preflight_run",), - "evidence_bundle": ("evidence_bundle_issued",), - "watch_cycle": ("watch_provisioned",), -} -ALL_PAID_EVENTS = tuple(t for v in OPERATION_EVENTS.values() for t in v) - - -def is_revenue(event: dict) -> bool: - """Did real, confirmed, mainnet money move for this event? - - All three conditions, deliberately: mode (not sandbox credits we mint), - confirmed (the chain receipt was verified, not merely claimed by the - facilitator) and mainnet (not the value-less default network). Events - predating settlement metadata have none of these and are never revenue.""" - return (event.get("settlement_mode") == SETTLED_MODE - and bool(event.get("settlement_confirmed")) - and bool(event.get("settlement_mainnet"))) - - -def _is_external(event: dict) -> bool: - from . import attribution - cls = attribution.caller_class(event) - return (not event.get("fp") - and cls not in ("AG_INTERNAL", "AG_TEST", "OPERATOR", - "REGISTRY_CRAWLER") - and attribution.may_count_as_external_growth(cls) - and attribution.is_genuine_external(event)) - - -def commercial_metrics(store: Any, operation: Optional[str] = None - ) -> dict[str, Any]: +def commercial_metrics(store: Any) -> dict[str, Any]: """The primary metrics. Revenue is REAL money only. - `operation` scopes every figure to ONE paid operation. Without it, an - experiment on the deep_preflight price could be promoted by unrelated - escrow revenue or by a watch sold for a different offer — the experiment - would "work" for reasons that had nothing to do with the change it made. - Global (operation=None) figures remain available for the commercial report, - where a total is what is wanted.""" - want = (OPERATION_EVENTS.get(operation) if operation else ALL_PAID_EVENTS) + SETTLEMENT MODE, NOT `paid=True` (correction 2026-07-31). The HTTP routes + previously stamped `paid=True` after the meter passed — but the meter + passes for three completely different reasons: an independently confirmed + x402 mainnet settlement, a draw against sandbox trial credits we minted + ourselves, and the soft-launch free path when enforcement is off. Counting + all three as paying customers meant our own trial grant could promote an + experiment. Only `settlement_mode == "x402"` from a genuinely external + caller counts here; sandbox decisions are reported separately, as + supporting, and can never promote. + + Events recorded before this correction carry no `settlement_mode`. They are + counted as SANDBOX, never as settled — the conservative direction, and the + one that cannot flatter us.""" + from . import attribution payers: set[str] = set() paid_decisions = 0 repeat: dict[str, int] = {} - revenue_usd = 0.0 sandbox_decisions = 0 sandbox_actors: set[str] = set() unattributed_settled = 0 - testnet_settlements = 0 for e in getattr(store, "events", []): - if e.get("type") not in want: + if e.get("type") not in ("deep_preflight_run", "evidence_bundle_issued"): continue + mode = e.get("settlement_mode") or ("legacy_unlabelled" + if e.get("paid") else "free") key = e.get("key") or "" - if e.get("settlement_mode") != SETTLED_MODE: - if e.get("settlement_mode") == "credits_sandbox" or e.get("paid"): + if mode != SETTLED_MODE: + if mode in ("credits_sandbox", "legacy_unlabelled"): sandbox_decisions += 1 if key and key != "anon": sandbox_actors.add(key) continue - if not is_revenue(e): - # settled on the rail, but testnet and/or unconfirmed — a - # successful payment of nothing - testnet_settlements += 1 - continue - if not _is_external(e): + # A settled call still has to be EXTERNAL to be a customer. + cls = attribution.caller_class(e) + external = (not e.get("fp") + and cls not in ("AG_INTERNAL", "AG_TEST", "OPERATOR", + "REGISTRY_CRAWLER") + and attribution.may_count_as_external_growth(cls) + and attribution.is_genuine_external(e)) + if not external: unattributed_settled += 1 continue paid_decisions += 1 - revenue_usd += float(e.get("settlement_amount_atomic") or 0) / 1e6 if key and key != "anon": payers.add(key) repeat[key] = repeat.get(key, 0) + 1 + revenue_usd = 0.0 + try: + real = (store.escrow_summary() or {}).get("real_settlement") or {} + revenue_usd = float( + real.get("independently_attested_external_revenue_usd") or 0.0) + except Exception: # noqa: BLE001 + revenue_usd = 0.0 + monitored = 0 for w in getattr(store, "watches", {}).values(): if not w.get("active"): @@ -258,29 +230,23 @@ def commercial_metrics(store: Any, operation: Optional[str] = None if acct and acct.get("first_party"): continue # our own watch is not a customer monitored += 1 - if operation and operation != "watch_cycle": - monitored = 0 # not attributable to this experiment return { - "operation_scope": operation or "all", - "external_settled_revenue_usd": round(revenue_usd, 6), + "external_settled_revenue_usd": revenue_usd, "distinct_external_payers": len(payers), "paid_decisions": paid_decisions, "externally_monitored_endpoints": monitored, "repeat_paid_callers": sum(1 for n in repeat.values() if n > 1), "supporting_sandbox_decisions_NOT_REVENUE": sandbox_decisions, "supporting_sandbox_distinct_actors_NOT_PAYERS": len(sandbox_actors), - "supporting_testnet_or_unconfirmed_NOT_REVENUE": testnet_settlements, "settled_but_not_attributable_external": unattributed_settled, "settlement_rule": ( - "revenue requires ALL of: settlement_mode == 'x402', " - "settlement_confirmed (chain receipt verified, not the " - "facilitator's word), settlement_mainnet (the rail defaults to " - "Base Sepolia, where a successful settlement is a successful " - "payment of nothing), AND a genuinely external caller. Sandbox " - "credits, testnet settlements, unconfirmed settlements and " - "unattributable callers are reported separately and can never " - "promote an experiment."), + "a paid decision requires settlement_mode == 'x402' (independently " + "confirmed mainnet money) AND a genuinely external caller. Sandbox " + "trial credits, soft-launch free calls and settled-but-unattributed " + "calls are reported separately and can never promote an experiment. " + "Events predating this correction carry no settlement_mode and are " + "counted as sandbox, never as settled."), "revenue_definition": ( "independently confirmed EXTERNAL mainnet settlement only. " "Sandbox credits, first-party canaries, testnet funds and internal " @@ -288,24 +254,14 @@ def commercial_metrics(store: Any, operation: Optional[str] = None } -def experiment_operation(rec: dict) -> Optional[str]: - """The paid operation an experiment is bound to, from its variable.""" - variable = str((rec or {}).get("variable") or "") - if variable.startswith("price:"): - op = variable.split(":", 1)[1] - return op if op in OPERATION_EVENTS else None - return None - - def evaluate(store: Any, key: str) -> dict[str, Any]: """Decide an experiment — or refuse to, honestly.""" rec = store.experiments.get(key) if not rec: return {"key": key, "decision": None, "reason": "unknown experiment"} - operation = experiment_operation(rec) - exposure = qualified_exposure(store, operation) - metrics = commercial_metrics(store, operation) + exposure = qualified_exposure(store) + metrics = commercial_metrics(store) baseline = rec.get("baseline") or {} started = rec.get("started_at") try: @@ -339,8 +295,7 @@ def evaluate(store: Any, key: str) -> dict[str, Any]: "and no primary commercial metric moved. Supporting metrics " "(reach, inventory, free checks) cannot rescue this verdict.") - evidence = {"operation": operation, - "exposure": exposure, "metrics": metrics, "baseline": baseline, + evidence = {"exposure": exposure, "metrics": metrics, "baseline": baseline, "elapsed_days": round(elapsed.total_seconds() / 86400, 2), "window_expired": expired} with store.lock, store._txn(): @@ -427,18 +382,9 @@ def apply_next_action(store: Any) -> list[dict[str, Any]]: * a price already at zero is not "changed" again — the engine reports `offer_exhausted` rather than pretending a no-op was an action. - ONE CHANGE PER CYCLE, GLOBALLY. The mandate is "one independently - measurable change at a time", and that is a property of the SYSTEM, not of - each experiment: two prices moving in the same cycle makes both results - uninterpretable, because either change could explain whatever happens next. - Remaining experiments are evaluated and reported, and simply wait their - turn — the loop runs on a multi-hour schedule, so deferring costs one - cycle and buys an attributable result. - - Returns one record per experiment. Never raises: a failure to act must not - take the scheduled cycle down.""" + Returns one record per experiment acted on. Never raises: a failure to act + must not take the scheduled cycle down.""" applied: list[dict[str, Any]] = [] - acted_this_cycle = False for key in list(getattr(store, "experiments", {}) or {}): try: action = next_action(store, key) @@ -449,13 +395,6 @@ def apply_next_action(store: Any) -> list[dict[str, Any]]: applied.append({"key": key, "decision": action.get("decision"), "acted": False, "action": action.get("action")}) continue - if acted_this_cycle: - applied.append({"key": key, "decision": action.get("decision"), - "acted": False, "reason": "deferred_one_change_per_cycle", - "detail": ("another experiment already applied this " - "cycle; two simultaneous changes would " - "make both results unattributable")}) - continue change = action["change"] op, before, after = (change["operation"], change["from_credits"], change["to_credits"]) @@ -489,8 +428,7 @@ def apply_next_action(store: Any) -> list[dict[str, Any]]: }) live["changes_applied"] = live["changes_applied"][-20:] # restart the measurement window against a FRESH baseline - live["baseline"] = commercial_metrics( - store, experiment_operation(live)) + live["baseline"] = commercial_metrics(store) live["started_at"] = _now().isoformat() live["status"] = "running" live["decision"] = None @@ -500,7 +438,6 @@ def apply_next_action(store: Any) -> list[dict[str, Any]]: store._persist_kv("experiments", store.experiments) store._persist_kv("price_overrides", store.price_overrides) store._save() - acted_this_cycle = True applied.append({ "key": key, "acted": True, "operation": op, "before_credits": before, "after_credits": after, diff --git a/live/guild/app/indexsources.py b/live/guild/app/indexsources.py index 01c9703..69c2e9e 100644 --- a/live/guild/app/indexsources.py +++ b/live/guild/app/indexsources.py @@ -37,64 +37,12 @@ MAX_RECORDS_PER_RUN = 200 -#: Remote sources cleared for automatic ingest. A source appears here ONLY -#: when three things are true: it publishes a documented public read-only API, -#: its terms permit automated reading, and we can identify ourselves honestly -#: while doing it. Anything requiring a credential, an account, or a scrape of -#: a rendered page is NOT here and does not ship — see UNAVAILABLE_SOURCES for -#: what is excluded and exactly why. -CLEARED_SOURCES = ("mcp_registry",) - -#: Sources deliberately NOT enabled, with the specific gate. Documented rather -#: than silently omitted, because "we index everything" and "we index what we -#: are allowed to index" are different products and the difference matters. -UNAVAILABLE_SOURCES = { - "a2a_registry": ( - "no documented public read-only API contract we can rely on; the A2A " - "discovery specification states it prescribes no registry API, so any " - "endpoint we used would be undocumented and could change without " - "notice. Enabling it would mean scraping, which the mandate forbids."), - "x402_bazaar": ( - "listing data is reachable, but automated bulk reading is not covered " - "by a documented public API contract. A trade-listing surface is " - "exactly where an unclear permission should be resolved BEFORE we " - "read it at machine speed, not after."), -} - - def enabled() -> bool: - """Is remote registry ingest active? - - ON by default now that a bounded local-only cycle has run in production - without incident, and only for CLEARED_SOURCES. The explicit "0" kill - switch still wins over everything, so this stays a one-config-change stop - with no deploy — the property that matters when the traffic is outbound and - lands on someone else's servers.""" - raw = (os.environ.get("GUILD_INDEX_INGEST") or "").strip() - if raw == "0": - return False - if raw == "1": - return True - return True - - -def active_sources() -> dict: - """What we ingest, what we do not, and why — publishable as-is.""" - return { - "enabled": enabled(), - "cleared": list(CLEARED_SOURCES), - "excluded": UNAVAILABLE_SOURCES, - "kill_switch": "GUILD_INDEX_INGEST=0", - "user_agent": USER_AGENT, - "bounds": {"max_pages": MAX_PAGES, - "max_records_per_run": MAX_RECORDS_PER_RUN, - "timeout_s": TIMEOUT_S}, - "policy": ("documented public read-only APIs only, with a truthful " - "contactable User-Agent, capped pages and records per run. " - "No authentication bypass, no scraping, no Terms-of-Service " - "circumvention. Indexed inventory is a SUPPORTING metric " - "and is never reported as adoption."), - } + """Ingest is OFF unless explicitly enabled. + + Default-off is deliberate: outbound traffic to third-party infrastructure + should never start because a container restarted.""" + return (os.environ.get("GUILD_INDEX_INGEST") or "0").strip() == "1" def _get_json(url: str, timeout: float = TIMEOUT_S) -> Optional[Any]: diff --git a/live/guild/app/main.py b/live/guild/app/main.py index 6a6efa1..98d28cf 100644 --- a/live/guild/app/main.py +++ b/live/guild/app/main.py @@ -49,7 +49,6 @@ from . import trustindex from . import indexops from . import deepcheck -from . import indexsources from . import experiments from .state import store from .store import CanonicalWriteRefused @@ -534,7 +533,7 @@ def _meter_with_demand(preq: PaidRequest, x_api_key: Optional[str], def meter(preq: PaidRequest, x_api_key: Optional[str], - response: Response) -> dict: + response: Response) -> str: """Charge one priced request through the shared paid-operation gateway (app/payments.py — the SAME gateway MCP and A2A use). Behaviour: @@ -602,15 +601,13 @@ def meter(preq: PaidRequest, x_api_key: Optional[str], holder[0] = auth.settled elif auth.mode == "credits_sandbox" and auth.account is not None: response.headers["X-Guild-Balance"] = str(auth.account["balance"]) - # RETURN THE SETTLEMENT FACTS. Callers must record HOW a request was paid, - # not merely that it passed the gate. Three independent conditions have to - # hold before anything may be called revenue: mode == "x402" (not sandbox - # credits we mint, not the soft-launch free path), `confirmed` (the chain - # receipt was verified, not just the facilitator's word), and `mainnet` - # (the same rail defaults to Base Sepolia, where a successful settlement - # is a successful payment of nothing). Any one of these alone has been - # enough to overstate revenue. - return payments.settlement_facts(auth) + # RETURN THE SETTLEMENT MODE. Callers must record HOW a request was paid, + # not merely that it passed the gate. "x402" is independently confirmed + # mainnet money; "credits_sandbox" is an internal unit we mint ourselves; + # "free" is the soft launch. Stamping paid=True for all three let sandbox + # trial credits and free calls count as paying customers — which is + # precisely the class of error the truth layer exists to prevent. + return auth.mode _LANDING_HTML = """ @@ -3029,14 +3026,12 @@ def deep_preflight_route(request: Request, response: Response, The free tier (`GET /preflight`) is not degraded to make this attractive: it returns the full live check set and verdict, and always will.""" - facts = meter(payments.deep_preflight_request(url), x_api_key, response) + mode = meter(payments.deep_preflight_request(url), x_api_key, response) out = deepcheck.deep_preflight(store, url) store.record_event(creds.sanitize_actor_key(x_api_key) if x_api_key else None, "deep_preflight_run", ua=_ua.get(), endpoint="preflight_deep", - target=url, - paid=(facts["settlement_mode"] == "x402"), - verdict=(out.get("policy") or {}).get("decision"), - **facts) + target=url, paid=(mode == "x402"), settlement_mode=mode, + verdict=(out.get("policy") or {}).get("decision")) return out @@ -3064,11 +3059,11 @@ def evidence_bundle_route(body: dict[str, Any], response: Response, "error": "evidence_issuance_refused", "code": e.code, "detail": str(e), "billing": "NOT CHARGED — issuance failed, so no meter ran"}) - facts = meter(preq, x_api_key, response) + mode = meter(preq, x_api_key, response) store.record_event(creds.sanitize_actor_key(x_api_key) if x_api_key else None, "evidence_bundle_issued", ua=_ua.get(), endpoint="evidence_bundle", target=url, - paid=(facts["settlement_mode"] == "x402"), **facts) + paid=(mode == "x402"), settlement_mode=mode) return bundle @@ -3311,27 +3306,6 @@ def _attr(url: str) -> str: """) -@app.post("/admin/index/cycle") -def admin_index_cycle(x_admin_token: Optional[str] = Header(None)): - """Force ONE bounded index cycle now. Admin-gated. - - The autonomous loop runs on a jittered multi-hour schedule, which is right - for steady state and useless when you need to verify a deploy or refresh - after an incident. This runs exactly the same code path with exactly the - same bounds — a trigger, not a second implementation, so the manual and - scheduled paths cannot drift apart.""" - if ADMIN_TOKEN and x_admin_token != ADMIN_TOKEN: - raise HTTPException(403, "an index cycle requires a valid X-Admin-Token") - from .swarm import runner as _runner - return {"cycle": _runner._run_index_cycle(store), - "autorun_enabled": _runner.index_autorun(store), - "bounds": { - "recheck_batch": trustindex.recheck_batch(), - "remote_ingest_enabled": indexsources.enabled(), - "remote_sources": indexsources.active_sources(), - "fresh_ttl_s": trustindex.fresh_ttl_s()}} - - @app.get("/commercial") def commercial_report(): """The commercial scorecard, revenue first — the number that decides. diff --git a/live/guild/app/mcp_server.py b/live/guild/app/mcp_server.py index f676de9..83ea71d 100644 --- a/live/guild/app/mcp_server.py +++ b/live/guild/app/mcp_server.py @@ -153,16 +153,15 @@ def _first_party_payer() -> "bool | None": _mcp_caller_proof: contextvars.ContextVar[tuple[bool, str]] = \ contextvars.ContextVar("mcp_caller_proof", default=(False, "")) -#: The settlement FACTS for the current paid tool call — mode plus whether the -#: chain receipt was confirmed and whether it was mainnet. All three are needed -#: before anything may be called revenue (the rail defaults to Base Sepolia). -_settlement_mode: contextvars.ContextVar[dict] = \ - contextvars.ContextVar("settlement_facts", - default={"settlement_mode": "free"}) +#: How the CURRENT paid tool call was settled: "x402" (independently confirmed +#: mainnet money), "credits_sandbox" (an internal unit we mint), or "free". +#: Only "x402" is revenue — see app/experiments.commercial_metrics. +_settlement_mode: contextvars.ContextVar[str] = \ + contextvars.ContextVar("settlement_mode", default="free") -def settlement_mode() -> dict: - return dict(_settlement_mode.get() or {"settlement_mode": "free"}) +def settlement_mode() -> str: + return _settlement_mode.get() def _meta_value(meta: Any, key: str) -> Any: @@ -423,7 +422,7 @@ def _serve_paid(preq: PaidRequest, produce: Callable[[], Any], # contextvar, so an MCP tool can record HOW it was paid without every # producer signature growing an argument. Same correction as the HTTP # meter: passing the gate is not the same as being paid. - _settlement_mode.set(payments.settlement_facts(auth)) + _settlement_mode.set(auth.mode) result = produce() # in-band inbox delivery: the paid read is many agents' ONLY interaction # with the Guild, so an authenticated subject's pending messages ride on @@ -520,13 +519,13 @@ def guild_preflight_deep(url: str, api_key: str = "", ctx: Context = None) -> di """ def _produce(): out = deepcheck.deep_preflight(store, url) - facts = settlement_mode() + mode = settlement_mode() store.record_event( _creds.sanitize_actor_key(api_key) if api_key else "mcp", "deep_preflight_run", ua=_client_ua(ctx), endpoint="preflight_deep", target=url, transport="mcp", - paid=(facts.get("settlement_mode") == "x402"), - verdict=(out.get("policy") or {}).get("decision"), **facts) + paid=(mode == "x402"), settlement_mode=mode, + verdict=(out.get("policy") or {}).get("decision")) return out return _serve_paid(payments.deep_preflight_request(url), _produce, diff --git a/live/guild/app/payments.py b/live/guild/app/payments.py index c8a8890..1d26c5d 100644 --- a/live/guild/app/payments.py +++ b/live/guild/app/payments.py @@ -125,54 +125,6 @@ def cost(self) -> int: # Builders for every priced semantic operation. MCP and A2A use these too, so # one semantic operation canonicalizes to one resource URL on every transport. -#: Rebuild a PaidRequest from a PERSISTED operation + params. Whitelist only: -#: an A2A payment task is reconstructed from our own stored fields, never from -#: anything the caller sends back, so a submission cannot redirect settlement -#: onto a different (cheaper, or simply other) operation. -def request_from_stored(operation: str, params: dict) -> "PaidRequest": - """Reconstruct the exact quoted operation from trusted stored fields. - - A2A quotes a price in one message and settles in another, so the operation - has to survive the round trip. Previously the rebuild ALWAYS produced - `check_request(capability)`, which meant a deep-preflight challenge settled - the wrong canonical operation and returned the wrong product entirely. - Reconstruction is now table-driven and refuses an unknown operation rather - than silently falling back to a default.""" - params = params or {} - if operation in ("best_agent", "signed_decision"): - return check_request(str(params.get("capability") or ""), - signed=(operation == "signed_decision"), - ttl_seconds=int(params.get("ttl_seconds") or 3600)) - if operation == "deep_preflight": - return deep_preflight_request(str(params.get("url") or "")) - if operation == "evidence_bundle": - return evidence_bundle_request( - str(params.get("url") or ""), - int(params.get("ttl_seconds") or 3600)) - if operation == "watch_cycle": - return watch_cycle_request(str(params.get("endpoint") or "")) - raise ValueError(f"unknown paid operation {operation!r}") - - -def settlement_facts(auth) -> dict: - """What actually happened, in the only terms that may be called revenue. - - `mode == "x402"` is NOT proof of money: the same rail runs on Base Sepolia - (testnet, value-less) by default, and a testnet settlement is a successful - payment of nothing. Callers record every field here and the revenue rule - requires x402 AND confirmed AND mainnet — three independent conditions, - because any one of them alone has been enough to overstate revenue before.""" - rec = getattr(getattr(auth, "settled", None), "record", None) or {} - return { - "settlement_mode": getattr(auth, "mode", "free"), - "settlement_confirmed": bool(rec.get("confirmed")), - "settlement_mainnet": bool(rec.get("mainnet")), - "settlement_network": rec.get("network"), - "settlement_amount_atomic": rec.get("amount_atomic"), - "settlement_tx": rec.get("transaction"), - } - - def check_request(capability: str, signed: bool = False, ttl_seconds: int = 3600) -> PaidRequest: operation = "signed_decision" if signed else "best_agent" diff --git a/live/guild/app/swarm/runner.py b/live/guild/app/swarm/runner.py index a98ad1f..925b642 100644 --- a/live/guild/app/swarm/runner.py +++ b/live/guild/app/swarm/runner.py @@ -181,32 +181,13 @@ def _run_scout(store: Any, *, fetch: Callable, deadline: float, return scout.run_scout(store, fetch=fetch, deadline=deadline) -def index_autorun(store: Any = None) -> bool: - """Should this cycle maintain the index? - - Three-way, and the asymmetry is deliberate: - - * ``GUILD_INDEX_AUTORUN=0`` — always OFF. The explicit kill switch wins - over everything, so an operator can stop index upkeep in one config - change without redeploying or touching the scout. - * ``GUILD_INDEX_AUTORUN=1`` — always ON. - * unset — ON only when a DURABLE backend is present. - - That last rule is the useful one. The index is a production surface whose - value is a persistent observation history; running it against an ephemeral - JSON store would produce observations that vanish on restart. It also keeps - the test suite (JSON store) from silently probing real hosts, while - production (sqlite) does not sit dormant waiting for someone to remember a - dashboard setting — which, for a system meant to run without routine human - involvement, would be a design flaw wearing a safety feature's clothes. - - Remote registry ingest remains a SEPARATE switch (``GUILD_INDEX_INGEST``).""" - raw = (os.environ.get("GUILD_INDEX_AUTORUN") or "").strip() - if raw == "0": - return False - if raw == "1": - return True - return getattr(store, "backend", None) is not None +def index_autorun() -> bool: + """Index upkeep is DEFAULT-OFF, like every other outbound loop here. + + Outbound traffic to third-party infrastructure must never begin merely + because a container restarted, and a test that exercises the scout must not + silently start probing real hosts. Enabled explicitly in render.yaml.""" + return (os.environ.get("GUILD_INDEX_AUTORUN") or "0").strip() == "1" def _run_index_cycle(store: Any) -> dict[str, Any]: @@ -219,7 +200,7 @@ def _run_index_cycle(store: Any) -> dict[str, Any]: from .. import indexops from .. import experiments as _experiments - if not index_autorun(store): + if not index_autorun(): return {"skipped": "GUILD_INDEX_AUTORUN is not enabled"} out: dict[str, Any] = {} try: diff --git a/live/guild/contract/contract.json b/live/guild/contract/contract.json index e43009b..398dcf6 100644 --- a/live/guild/contract/contract.json +++ b/live/guild/contract/contract.json @@ -179,12 +179,6 @@ ], "path": "/admin/agents/{agent_id}/first-party" }, - { - "methods": [ - "POST" - ], - "path": "/admin/index/cycle" - }, { "methods": [ "POST" diff --git a/live/guild/tests/test_index_corrections.py b/live/guild/tests/test_index_corrections.py index ca2d868..220b810 100644 --- a/live/guild/tests/test_index_corrections.py +++ b/live/guild/tests/test_index_corrections.py @@ -187,9 +187,7 @@ def test_legacy_unlabelled_events_count_as_sandbox_never_settled(store): def test_mainnet_settlement_from_an_external_caller_counts(store): store.record_event("a2a:net:payer1", "deep_preflight_run", ua="a2a:langchain/0.2.1", endpoint="preflight_deep", - paid=True, settlement_mode="x402", - settlement_confirmed=True, settlement_mainnet=True, - settlement_amount_atomic=20000) + paid=True, settlement_mode="x402") m = experiments.commercial_metrics(store) assert m["paid_decisions"] == 1 assert m["distinct_external_payers"] == 1 @@ -198,9 +196,7 @@ def test_mainnet_settlement_from_an_external_caller_counts(store): def test_settled_but_first_party_is_not_a_customer(store): store.record_event("ag-internal", "deep_preflight_run", ua="guild-release-gate", endpoint="preflight_deep", - paid=True, settlement_mode="x402", - settlement_confirmed=True, settlement_mainnet=True, - first_party=True) + paid=True, settlement_mode="x402", first_party=True) m = experiments.commercial_metrics(store) assert m["paid_decisions"] == 0 assert m["settled_but_not_attributable_external"] >= 1 diff --git a/live/guild/tests/test_index_fixes2.py b/live/guild/tests/test_index_fixes2.py deleted file mode 100644 index cb21bb9..0000000 --- a/live/guild/tests/test_index_fixes2.py +++ /dev/null @@ -1,253 +0,0 @@ -"""Second-round corrections — system-level defects the unit tests missed. - -Every defect here passed its own tests. They were found by asking what the -SYSTEM does end to end, which is a different question from what each function -returns: - - 1. an A2A deep-preflight challenge settled the wrong operation and returned - the wrong product — challenge and card tests both passed - 2. `settlement_mode == "x402"` was treated as money, but the same rail - defaults to Base Sepolia, where a successful settlement pays nothing - 3. any global metric moving could promote any experiment, so unrelated - escrow revenue would "prove" a deep-preflight price change worked - 4. the A2A agent card still opened with passport issuance - -The tests below are end-to-end or attribution-scoped for exactly that reason. -""" -from __future__ import annotations - -import json -import os -import sys - -import pytest - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) - -from app import experiments, payments, pricing # noqa: E402 -from app.store import Store # noqa: E402 - - -@pytest.fixture() -def store(tmp_path) -> Store: - pricing.load_runtime({}) - return Store(path=str(tmp_path / "guild.json")) - - -def _settled_event(store: Store, etype="deep_preflight_run", *, key="a2a:net:payer", - ua="a2a:langchain/0.2.1", mainnet=True, confirmed=True, - mode="x402", usd_atomic=20000, **extra): - store.record_event(key, etype, ua=ua, endpoint="preflight_deep", - paid=(mode == "x402"), settlement_mode=mode, - settlement_confirmed=confirmed, - settlement_mainnet=mainnet, - settlement_network="eip155:8453" if mainnet else "eip155:84532", - settlement_amount_atomic=usd_atomic, **extra) - - -# -------------------------------------------------------------------------- -# 1. A2A payment must settle the operation that was QUOTED -# -------------------------------------------------------------------------- -def test_a_deep_preflight_task_rebuilds_as_deep_preflight(store, monkeypatch): - """The defect: _preq_from_task always rebuilt check_request(capability), so - a deep-preflight challenge settled the wrong canonical operation.""" - from app import a2a_x402 - monkeypatch.setattr(a2a_x402, "store", store) - preq = payments.deep_preflight_request("https://x.example/a2a") - task = a2a_x402.build_payment_required_task(preq, preq.cost, - actor="a2a:net:abc", ua="a2a:ua") - stored = store.x402_task_get(task["id"]) - assert stored["operation"] == "deep_preflight" - assert stored["operation_params"]["url"] == "https://x.example/a2a" - rebuilt = a2a_x402._preq_from_task(stored) - assert rebuilt.operation == "deep_preflight" - assert rebuilt.resource_url == preq.resource_url - assert rebuilt.request_hash == preq.request_hash - - -def test_rebuild_uses_only_stored_fields_not_the_submission(store, monkeypatch): - """A caller must not be able to steer settlement onto another operation.""" - from app import a2a_x402 - monkeypatch.setattr(a2a_x402, "store", store) - preq = payments.deep_preflight_request("https://x.example/a2a") - task = a2a_x402.build_payment_required_task(preq, preq.cost) - stored = dict(store.x402_task_get(task["id"])) - # a hostile echo of a cheaper operation - stored_with_noise = {**stored, "capability": "fact-check"} - assert a2a_x402._preq_from_task(stored_with_noise).operation == "deep_preflight" - - -def test_unknown_operation_is_refused_not_defaulted(): - with pytest.raises(ValueError): - payments.request_from_stored("not_a_real_operation", {}) - - -def test_full_a2a_payment_round_trip_returns_the_DEEP_product(store, monkeypatch): - """payment-required -> payment-submitted -> completed, end to end. - - The challenge/card tests passed while this path returned a capability - shortlist to someone who paid for a deep endpoint check.""" - from app import a2a_x402, deepcheck, x402 - - monkeypatch.setattr(a2a_x402, "store", store) - monkeypatch.setattr(deepcheck, "deep_preflight", - lambda s, url: {"tier": "deep", "target": url, - "policy": {"decision": "allow"}}) - preq = payments.deep_preflight_request("https://x.example/a2a") - task = a2a_x402.build_payment_required_task( - preq, preq.cost, actor="a2a:net:payer", ua="a2a:langchain/0.2.1") - stored = store.x402_task_get(task["id"]) - - class _Settled: - record = {"confirmed": True, "mainnet": True, "network": "eip155:8453", - "amount_atomic": 20000, "transaction": "0x" + "a" * 64} - - def finalize(self, body): - return {"settle_response": {}, "extensions": {}} - - out = a2a_x402._produce_for(a2a_x402._preq_from_task(stored), - _Settled(), stored) - assert out["tier"] == "deep", "the caller must receive what they paid for" - assert out["target"] == "https://x.example/a2a" - ev = [e for e in store.events if e["type"] == "deep_preflight_run"] - assert len(ev) == 1, "the settled deep check must be recorded" - assert ev[0]["settlement_mode"] == "x402" - assert ev[0]["settlement_confirmed"] is True - assert ev[0]["settlement_mainnet"] is True - assert ev[0]["key"] == "a2a:net:payer", "settlement must be attributable" - - -def test_a_capability_task_still_returns_the_capability_product(store, monkeypatch): - from app import a2a_x402 - monkeypatch.setattr(a2a_x402, "store", store) - preq = payments.check_request("fact-check") - task = a2a_x402.build_payment_required_task(preq, preq.cost) - stored = store.x402_task_get(task["id"]) - assert a2a_x402._preq_from_task(stored).operation == "best_agent" - - -# -------------------------------------------------------------------------- -# 2. x402 is not the same thing as money -# -------------------------------------------------------------------------- -def test_testnet_settlement_can_never_count_as_revenue(store): - """Base Sepolia is the DEFAULT network. A successful settlement there is a - successful payment of nothing.""" - _settled_event(store, mainnet=False) - m = experiments.commercial_metrics(store, "deep_preflight") - assert m["paid_decisions"] == 0 - assert m["distinct_external_payers"] == 0 - assert m["external_settled_revenue_usd"] == 0.0 - assert m["supporting_testnet_or_unconfirmed_NOT_REVENUE"] == 1 - - -def test_unconfirmed_mainnet_settlement_does_not_count(store): - """A facilitator claiming success is not a chain receipt.""" - _settled_event(store, confirmed=False) - m = experiments.commercial_metrics(store, "deep_preflight") - assert m["paid_decisions"] == 0 - assert m["supporting_testnet_or_unconfirmed_NOT_REVENUE"] == 1 - - -def test_confirmed_mainnet_external_settlement_counts(store): - _settled_event(store) - m = experiments.commercial_metrics(store, "deep_preflight") - assert m["paid_decisions"] == 1 - assert m["distinct_external_payers"] == 1 - assert m["external_settled_revenue_usd"] == pytest.approx(0.02) - - -def test_is_revenue_requires_all_three_conditions(): - base = {"settlement_mode": "x402", "settlement_confirmed": True, - "settlement_mainnet": True} - assert experiments.is_revenue(base) is True - for drop in ("settlement_mode", "settlement_confirmed", "settlement_mainnet"): - bad = {**base, drop: (False if drop != "settlement_mode" else "credits_sandbox")} - assert experiments.is_revenue(bad) is False, drop - - -def test_testnet_volume_cannot_promote(store): - experiments.define(store, "e", hypothesis="h", variable="price:deep_preflight", - baseline={m: 0 for m in experiments.PRIMARY_METRICS}) - for i in range(30): - _settled_event(store, key=f"a2a:net:p{i}", ua=f"a2a:langchain/0.2.{i}", - mainnet=False) - assert experiments.evaluate(store, "e")["decision"] != "promote" - - -# -------------------------------------------------------------------------- -# 3. An experiment must be judged on ITS OWN operation -# -------------------------------------------------------------------------- -def test_unrelated_settlement_cannot_promote_a_deep_preflight_experiment(store): - """The defect: any global primary metric moving promoted any experiment, so - revenue from a completely different product would 'prove' a price change - worked.""" - exp = experiments.define(store, "deep", hypothesis="h", - variable="price:deep_preflight", - baseline={m: 0 for m in experiments.PRIMARY_METRICS}) - exp["min_qualified"] = 1 - store.experiments["deep"] = exp - # qualified exposure on the deep-preflight surface - store.record_event("a2a:net:looker", "deep_preflight_run", - ua="a2a:langchain/0.2.1", endpoint="preflight_deep") - # ...and a pile of REAL money from an entirely different operation - for i in range(20): - _settled_event(store, etype="evidence_bundle_issued", - key=f"a2a:net:other{i}", ua=f"a2a:langchain/0.3.{i}") - out = experiments.evaluate(store, "deep") - assert out["decision"] != "promote", out - assert out["evidence"]["operation"] == "deep_preflight" - assert out["evidence"]["metrics"]["paid_decisions"] == 0 - - -def test_the_experiments_own_operation_does_promote(store): - exp = experiments.define(store, "deep", hypothesis="h", - variable="price:deep_preflight", - baseline={m: 0 for m in experiments.PRIMARY_METRICS}) - exp["min_qualified"] = 1 - store.experiments["deep"] = exp - _settled_event(store, key="a2a:net:realpayer") - assert experiments.evaluate(store, "deep")["decision"] == "promote" - - -def test_exposure_is_scoped_to_the_experiments_surface(store): - store.record_event("a2a:net:a", "watch_provisioned", - ua="a2a:langchain/0.2.1", endpoint="watch") - deep = experiments.qualified_exposure(store, "deep_preflight") - watch = experiments.qualified_exposure(store, "watch_cycle") - assert deep["qualified_actors"] == 0 - assert watch["qualified_actors"] == 1 - - -def test_only_one_change_is_applied_per_cycle_globally(store): - """Two prices moving in one cycle makes both results uninterpretable.""" - for key, op in (("a", "deep_preflight"), ("b", "evidence_bundle")): - exp = experiments.define(store, key, hypothesis="h", - variable=f"price:{op}", - baseline={m: 0 for m in experiments.PRIMARY_METRICS}) - exp["min_qualified"] = 1 - store.experiments[key] = exp - store.record_event("a2a:net:x", "deep_preflight_run", - ua="a2a:langchain/0.2.1", endpoint="preflight_deep") - store.record_event("a2a:net:y", "evidence_bundle_issued", - ua="a2a:langchain/0.2.2", endpoint="evidence_bundle") - applied = experiments.apply_next_action(store) - acted = [r for r in applied if r.get("acted")] - assert len(acted) == 1, applied - deferred = [r for r in applied - if r.get("reason") == "deferred_one_change_per_cycle"] - assert len(deferred) == 1 - - -# -------------------------------------------------------------------------- -# 4. The A2A card leads with the decision -# -------------------------------------------------------------------------- -def test_agent_card_description_leads_with_the_endpoint_decision(): - from fastapi.testclient import TestClient - from app.main import app - card = TestClient(app).get("/.well-known/agent-card.json").json() - desc = card["description"] - assert desc.lower().startswith("can i safely use or pay this endpoint") - assert "preflight" in desc.lower() - # passports may still be mentioned — as supporting, not as the opener - head = desc[:200].lower() - assert "passport" not in head diff --git a/live/guild/tests/test_passport_offers.py b/live/guild/tests/test_passport_offers.py index 89507e5..f9625e2 100644 --- a/live/guild/tests/test_passport_offers.py +++ b/live/guild/tests/test_passport_offers.py @@ -56,19 +56,14 @@ def test_manifest_leads_with_passport_and_records_offer(): assert len(offers) == 1 and offers[0]["offer"] == "passport" -def test_agent_card_leads_with_the_endpoint_decision_and_still_offers_a_passport(): - """Copy contract UPDATED by the product-led pivot (2026-07-31): the card - now leads with allow/caution/block for an endpoint, and the passport is - supporting. The offer telemetry must keep working regardless — a change of - emphasis must not silently drop a funnel stage.""" +def test_agent_card_leads_with_passport_and_records_offer(): n0 = len(store.events) r = client.get("/.well-known/agent-card.json", headers={"User-Agent": "card-bot/1.0"}) assert r.status_code == 200 desc = r.json()["description"] assert "passport_offer:agent_card" in desc - assert desc.lower().startswith("can i safely use or pay this endpoint") - assert desc.index("preflight") < desc.index("Passport") + assert desc.index("Passport") < desc.index("check:") offers = _offers_since(n0, "agent_card") assert len(offers) == 1 and offers[0]["offer"] == "passport" # a2a attribution conventions: derived a2a: actor key + tagged real UA diff --git a/live/guild/tests/test_trust_index.py b/live/guild/tests/test_trust_index.py index 5ceb974..e2f5f77 100644 --- a/live/guild/tests/test_trust_index.py +++ b/live/guild/tests/test_trust_index.py @@ -435,26 +435,10 @@ def test_recheck_is_capped_per_cycle(store): assert len(calls) == 5 -def test_remote_ingest_has_a_working_kill_switch(monkeypatch): - """Remote ingest is now ON for CLEARED sources only (a bounded local cycle - ran clean in production first). The property that still matters is that it - can be stopped in one config change, with no deploy.""" +def test_remote_ingest_is_off_by_default(monkeypatch): from app import indexsources - monkeypatch.setenv("GUILD_INDEX_INGEST", "0") - assert indexsources.enabled() is False monkeypatch.delenv("GUILD_INDEX_INGEST", raising=False) - assert indexsources.enabled() is True - - -def test_only_cleared_sources_are_ingested(): - """A source ships only with a documented public read-only API and terms - that permit automated reading. Everything else is excluded WITH ITS - REASON, not silently omitted.""" - from app import indexsources - assert indexsources.CLEARED_SOURCES == ("mcp_registry",) - for name, why in indexsources.UNAVAILABLE_SOURCES.items(): - assert len(why) > 40, f"{name} must state its actual gate" - assert "a2a_registry" in indexsources.UNAVAILABLE_SOURCES + assert indexsources.enabled() is False def test_source_adapter_identifies_itself_truthfully(): diff --git a/render.yaml b/render.yaml index 86311c8..7e3343f 100644 --- a/render.yaml +++ b/render.yaml @@ -73,13 +73,8 @@ services: # infrastructure should never start because a container restarted. # Adapters read documented public read-only APIs with a truthful, # contactable User-Agent; no scraping, no auth bypass, no ToS evasion. - # Cleared for the documented, capped MCP Registry adapter only after a - # bounded local-only cycle ran clean in production. Set to "0" to stop - # all remote ingest in one config change, with no deploy. Excluded - # sources and their exact gates: GET /admin/index/cycle -> bounds, and - # app/indexsources.UNAVAILABLE_SOURCES. - key: GUILD_INDEX_INGEST - value: "1" + value: "0" # How long an observation stays "fresh" before the index reports it as # stale and the loop re-probes it. - key: GUILD_INDEX_FRESH_TTL_S