Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ python examples/resolve-and-execute.py

`resolve-and-execute.py` will still show the ranked providers plus any machine-readable recovery handoff Rhumb already identified. Set `RHUMB_API_KEY` only when you want to continue into estimate and execute.

For the current Rhumb-managed P0 launch catalog, see
[`docs/RESOLVE-LAUNCH-CATALOG-2026-06-04.md`](docs/RESOLVE-LAUNCH-CATALOG-2026-06-04.md).

---

## Docs
Expand All @@ -221,6 +224,7 @@ python examples/resolve-and-execute.py
- [AN Score Methodology](docs/AN-SCORE-V2-SPEC.md) — scoring dimensions, weights, and rubrics
- [Architecture](docs/ARCHITECTURE.md) — scoring engine design
- [API Reference](docs/API.md) — endpoint details
- [Resolve Launch Catalog](docs/RESOLVE-LAUNCH-CATALOG-2026-06-04.md) — current P0 Rhumb-managed capability contract
- [Repo Boundary](docs/REPO-BOUNDARY.md) — what stays public here vs. what lives in the private ops workspace
- [Security Policy](SECURITY.md) — vulnerability reporting and security architecture

Expand Down
136 changes: 136 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,142 @@ curl "https://api.rhumb.dev/v1/capabilities/search.query/resolve"
curl "https://api.rhumb.dev/v1/capabilities/search.query/execute/estimate?credential_mode=rhumb_managed"
```

## Resolve v2 first-call proof: `search.query`

Use this path when you want the boring first successful Rhumb-managed official API call with one governed key and no provider credential setup:

`search -> resolve -> estimate -> execute -> receipt -> verify`

Requirements:

- a funded governed Rhumb API key in `RHUMB_API_KEY`, sent as `X-Rhumb-Key`;
- no Brave/provider key, wallet, browser login, Agent Vault, or BYOK setup;
- curl with `--fail-with-body` so typed 401/402 JSON remains visible when a step fails. If your curl is too old, rerun the same command with `-sS` and without `--fail-with-body` to print the body.

Copy-paste flow:

```bash
set -euo pipefail

: "${RHUMB_API_KEY:?Set RHUMB_API_KEY to a funded governed Rhumb key}"
RHUMB_API_BASE="${RHUMB_API_BASE:-https://api.rhumb.dev}"

curlf() {
curl --fail-with-body -sS "$@"
}

# 1) Search for the capability slug.
curlf \
-X GET \
-H "X-Rhumb-Key: $RHUMB_API_KEY" \
"$RHUMB_API_BASE/v2/capabilities?search=web%20research&limit=5"

# 2) Resolve the Rhumb-managed route. This is a read/preflight step.
curlf \
-X GET \
-H "X-Rhumb-Key: $RHUMB_API_KEY" \
"$RHUMB_API_BASE/v2/capabilities/search.query/resolve?credential_mode=rhumb_managed" \
| tee /tmp/rhumb-first-call-resolve.json

# 3) Estimate the concrete execution rail before spend.
curlf \
-X GET \
-H "X-Rhumb-Key: $RHUMB_API_KEY" \
"$RHUMB_API_BASE/v2/capabilities/search.query/execute/estimate?provider=brave-search-api&credential_mode=rhumb_managed" \
| tee /tmp/rhumb-first-call-estimate.json

# 4) Execute one bounded read-only official API search.
curlf \
-X POST \
-H "Content-Type: application/json" \
-H "X-Rhumb-Key: $RHUMB_API_KEY" \
"$RHUMB_API_BASE/v2/capabilities/search.query/execute" \
-d '{
"credential_mode": "rhumb_managed",
"interface": "first-call-quickstart",
"parameters": {"query": "agent-native tool access", "numResults": 3},
"policy": {"max_cost_usd": 0.05, "provider_preference": ["brave-search-api"]}
}' \
| tee /tmp/rhumb-first-call-execute.json

RECEIPT_ID="$(
python3 - <<'PY'
import json
from pathlib import Path

payload = json.loads(Path('/tmp/rhumb-first-call-execute.json').read_text())
data = payload.get('data') if isinstance(payload, dict) and 'data' in payload else payload
receipt_id = (
data.get('receipt_id')
or (data.get('_rhumb_v2') or {}).get('receipt_id')
or (data.get('_rhumb') or {}).get('receipt_id')
)
if not receipt_id:
raise SystemExit('No receipt_id in execute response')
print(receipt_id)
PY
)"
echo "receipt_id=$RECEIPT_ID"

# 5) Fetch the full receipt row.
curlf \
-X GET \
-H "X-Rhumb-Key: $RHUMB_API_KEY" \
"$RHUMB_API_BASE/v2/receipts/$RECEIPT_ID" \
| tee /tmp/rhumb-first-call-receipt.json

# 6) Verify the compact receipt evidence. `verified` means the receipt row,
# hashes, route facts, route-plan hash, status, and explanation linkage are present;
# it does not claim provider-side content truth beyond returned-payload hashing.
curlf \
-X POST \
-H "X-Rhumb-Key: $RHUMB_API_KEY" \
"$RHUMB_API_BASE/v2/receipts/$RECEIPT_ID/verify" \
| tee /tmp/rhumb-first-call-verify.json

python3 - <<'PY'
import json
from pathlib import Path

payload = json.loads(Path('/tmp/rhumb-first-call-verify.json').read_text())
data = payload.get('data') if isinstance(payload, dict) and 'data' in payload else payload
status = data.get('verifier_status')
if status != 'verified':
raise SystemExit(f"receipt verifier_status={status!r}; expected 'verified'")
compact = data.get('compact_receipt') or {}
route = compact.get('route') or {}
print('verified receipt:', data.get('receipt_id'))
print('provider:', route.get('provider_id'))
print('route_id:', route.get('route_id'))
print('manifest_digest:', route.get('manifest_digest'))
PY
```

Expected proof fields:

- selected provider: `brave-search-api` unless policy or availability selects another executable official API route;
- substrate/provenance/source risk: `official_api` / `rhumb_managed` / `verified_low` for the seeded Brave route;
- receipt and verifier: `receipt_id`, `compact_receipt`, `route_plan.route_plan_id_hash`, and `verifier_status=verified`;
- cost/usage: budget fields in the compact receipt and billing events in `/v2/billing/events`.

Diagnostics that should stay typed:

- missing key: `missing_credentials` / missing `X-Rhumb-Key` guidance;
- invalid key: `invalid_rhumb_key` / invalid governed key guidance;
- valid but unfunded or over-budget key: `payment_required` with `auth_handoff` and retry/setup URLs such as `/auth/login` or `/payments/agent`.

For the hosted operator artifact, run the same flow through the dogfood harness after confirming the key is funded:

```bash
python3 scripts/resolve_v2_dogfood.py \
--profile pedro \
--force-provider-preference \
--json-out artifacts/resolve-v2-first-call-<timestamp>.json \
--summary-only
```

The harness stores `first_call_proof` and fails if the Layer 2 receipt verifier does not return `verified`.

## Resolve hint contract

`GET /v1/capabilities/{capability_id}/resolve` now returns an `execute_hint` block that is meant to answer the first-success question directly:
Expand Down
88 changes: 88 additions & 0 deletions docs/RESOLVE-LAUNCH-CATALOG-2026-06-04.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Resolve Launch Catalog

This is the current P0 Rhumb-managed capability surface for agents using a Rhumb API key. The goal is a small public catalog that works before agents wire their own provider accounts.

## Agent Contract

Use this loop:

1. Discover the capability ID.
2. Resolve the managed route.
3. Estimate the route before spend.
4. Execute only bounded, allowed capabilities.
5. Store the execution receipt.

```bash
API="https://api.rhumb.dev/v1"

curl "$API/capabilities/search.query/resolve?credential_mode=rhumb_managed"

curl "$API/capabilities/search.query/execute/estimate?credential_mode=rhumb_managed" \
-H "X-Rhumb-Key: $RHUMB_API_KEY"

curl -X POST "$API/capabilities/search.query/execute" \
-H "X-Rhumb-Key: $RHUMB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"credential_mode": "rhumb_managed",
"provider": "exa",
"body": {"query": "agent native API capability routing", "numResults": 3}
}'
```

For MCP, use the same order:

```text
resolve_capability({"capability":"search.query","credential_mode":"rhumb_managed"})
estimate_capability({"capability_id":"search.query","credential_mode":"rhumb_managed"})
execute_capability({
"capability_id":"search.query",
"credential_mode":"rhumb_managed",
"provider":"exa",
"body":{"query":"agent native API capability routing","numResults":3}
})
```

## P0 Capabilities

| Capability | Preferred provider | Status | Execution policy |
| --- | --- | --- | --- |
| `search.query` | `exa` | Launch | Safe read execution |
| `scrape.extract` | `firecrawl` | Launch | Safe one-page public extract |
| `ai.generate_text` | `openai` | Launch | Bounded short text generation |
| `ai.generate_image` | `openai` | Launch | Bounded one-image generation, compact evidence only |
| `data.enrich_person` | `people-data-labs` | Launch with proof substitute | Resolve + estimate only until a consented fixture exists |
| `data.enrich_company` | `apollo` | Launch | Rhumb-owned company-domain fixture |
| `geo.lookup` | `ipinfo` | Launch | Public resolver IP fixture only |
| `maps.places_search` | `google-places` | Launch | Public place-search fixture |

## Deferred Surfaces

`email.send` is a real capability but remains gated because it sends externally. It needs an owned verified sender, one-message cap, idempotency, approval policy, and receipt review before public managed execution.

`sendgrid` is a valid pass-through provider for `email.send`, but hosted Resolve currently marks it non-callable because no `RHUMB_CREDENTIAL_SENDGRID_API_KEY` is configured. This is not a launch blocker because `email.send` is deferred as an external-write surface; do not treat any email provider as part of the P0 managed launch catalog until the email-send gates above are complete.

`email.verify` is deferred because no managed verification provider is configured. Add Emailable, ZeroBounce, NeverBounce, Kickbox, or another approved provider before advertising it as managed.

## Error Contract

If `resolve` returns no executable managed provider, agents should use the machine-readable recovery fields:

- `recovery_hint.resolve_url`
- `recovery_hint.credential_modes_url`
- `recovery_hint.supported_provider_slugs`
- `recovery_hint.setup_handoff`

If `execute` returns auth or funding errors, agents should stop and surface the returned handoff rather than switching to a browser, scraping credentials, or trying a public write.

## Proof Command

Run the current proof harness with:

```bash
python3 scripts/resolve_launch_catalog_smoke.py \
--execute-safe \
--out artifacts/resolve-launch-catalog-smoke.json
```

The artifact is redacted by design: it stores statuses, provider IDs, execution IDs, receipt IDs, hashes, and compact response shape. It does not store raw provider keys, raw generated image bytes, or full upstream payloads.
60 changes: 60 additions & 0 deletions packages/api/migrations/0165_index_command_manifests.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
-- Migration 0165: Durable Index command manifest storage
--
-- PP-1/PP-2 durable hosted storage for command-level route manifests and
-- evidence packets. This table intentionally stores full manifest/evidence JSON
-- alongside indexed route taxonomy columns so Resolve can query route truth
-- without depending on private fixture registries.

BEGIN;

CREATE TABLE IF NOT EXISTS index_command_manifests (
route_id TEXT PRIMARY KEY,
manifest_id TEXT NOT NULL,
manifest_version TEXT NOT NULL,
manifest_digest TEXT NOT NULL,
service_id TEXT NOT NULL,
provider_id TEXT NOT NULL,
capability_id TEXT NOT NULL,
substrate TEXT NOT NULL,
provenance_origin TEXT NOT NULL,
source_risk TEXT NOT NULL,
side_effect_class TEXT NOT NULL,
promotion_state TEXT,
review_status TEXT,
evidence_packet_id TEXT,
evidence_packet_digest TEXT,
evidence_expires_at TIMESTAMPTZ,
public_claim_boundary TEXT NOT NULL,
manifest_json JSONB NOT NULL,
evidence_packet_json JSONB,
owner TEXT,
reviewer TEXT,
expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_index_command_manifests_capability
ON index_command_manifests (capability_id, route_id);

CREATE INDEX IF NOT EXISTS idx_index_command_manifests_provider
ON index_command_manifests (capability_id, provider_id);

CREATE INDEX IF NOT EXISTS idx_index_command_manifests_taxonomy
ON index_command_manifests (substrate, provenance_origin, source_risk);

CREATE INDEX IF NOT EXISTS idx_index_command_manifests_manifest_digest
ON index_command_manifests (manifest_digest);

CREATE INDEX IF NOT EXISTS idx_index_command_manifests_evidence_digest
ON index_command_manifests (evidence_packet_digest)
WHERE evidence_packet_digest IS NOT NULL;

ALTER TABLE index_command_manifests ENABLE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS index_command_manifests_read ON index_command_manifests;
CREATE POLICY index_command_manifests_read ON index_command_manifests
FOR SELECT
USING (true);

COMMIT;
Loading