diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 9f6c677..10de540 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -38,3 +38,7 @@ jobs: python -m pytest tests/test_validate_compatibility.py -q - name: Check generated integration index run: python scripts/generate_integration_index.py --check + - name: Test and check generated Marketplace catalog + run: | + python -m pytest tests/test_generate_marketplace_catalog.py -q + python scripts/generate_marketplace_catalog.py --check diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f09a02d..edb69c3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,11 @@ integrations/-/ Start by copying `integrations/_template/`. +The `marketplace` block in `integration.yaml` controls discovery in the public +[AgenTrust Marketplace](https://agentrust-io.com/marketplace/). After editing a +manifest, run `python scripts/generate_marketplace_catalog.py`; CI rejects a +stale catalog so the repository index and public Marketplace cannot silently drift. + ## Hard rules These come from operating large OSS governance projects. PRs that break them are closed, repeat offenses are banned, and merged integrations that turn out to break them are removed: diff --git a/claude-code/integration.yaml b/claude-code/integration.yaml index 6e99a3f..7732299 100644 --- a/claude-code/integration.yaml +++ b/claude-code/integration.yaml @@ -9,6 +9,12 @@ maintainer: repository: https://github.com/agentrust-io/integrations license: Apache-2.0 tier: community +marketplace: + display_name: Claude Code + category: Developer tools + mark: CC + featured: 1 + keywords: [Claude, session, drift] trace_conformance_level: 0 trace_roles: - record-producer diff --git a/integrations/_template/integration.yaml b/integrations/_template/integration.yaml index 0ae1bee..97d7112 100644 --- a/integrations/_template/integration.yaml +++ b/integrations/_template/integration.yaml @@ -11,6 +11,11 @@ maintainer: repository: https://github.com/example/example-product license: Apache-2.0 tier: community # set by maintainers during review; submissions start here +marketplace: + category: Developer tools # choose the closest category allowed by the schema + mark: EX # 1-3 letters/numbers shown when no logo is available +# featured is maintainer-curated; do not add it to new submissions +# keywords: [framework, use-case] # Required if you emit or consume TRACE records: # trace_conformance_level: 1 # trace_roles: diff --git a/integrations/aeoess-aps/integration.yaml b/integrations/aeoess-aps/integration.yaml index a2e8189..eb6259e 100644 --- a/integrations/aeoess-aps/integration.yaml +++ b/integrations/aeoess-aps/integration.yaml @@ -11,6 +11,10 @@ repository: https://github.com/aeoess/agent-passport-python homepage: https://agent-passport.org license: Apache-2.0 tier: community +marketplace: + category: Identity & trust + mark: AP + keywords: [passport, identity, policy] # Level 0 passes with an explicit TR-SIG-005 UNVERIFIED finding: the graded # artifact is the unsigned record, so trace-tests reports it as not # cryptographically verified. The signed form is written alongside it and diff --git a/integrations/comply54/integration.yaml b/integrations/comply54/integration.yaml index 4930b6a..f2c9f97 100644 --- a/integrations/comply54/integration.yaml +++ b/integrations/comply54/integration.yaml @@ -12,6 +12,10 @@ repository: https://github.com/comply54/comply54 homepage: https://comply54.io license: Apache-2.0 tier: community +marketplace: + category: Compliance + mark: C5 + keywords: [Africa, regulation, compliance] trace_conformance_level: 0 trace_roles: - record-producer diff --git a/integrations/decisionassure/integration.yaml b/integrations/decisionassure/integration.yaml index fe8c51c..a7df5de 100644 --- a/integrations/decisionassure/integration.yaml +++ b/integrations/decisionassure/integration.yaml @@ -8,6 +8,10 @@ maintainer: repository: https://github.com/a1k7/DecisionAssure-Runtime-Governance license: MIT tier: community +marketplace: + category: Security & policy + mark: DA + keywords: [runtime, governance, policy] trace_roles: - record-producer trace_conformance_level: 0 diff --git a/integrations/langchain/integration.yaml b/integrations/langchain/integration.yaml index 616bdd9..819a49c 100644 --- a/integrations/langchain/integration.yaml +++ b/integrations/langchain/integration.yaml @@ -10,6 +10,11 @@ maintainer: repository: https://github.com/agentrust-io/integrations license: Apache-2.0 tier: community +marketplace: + category: Frameworks + mark: LC + featured: 3 + keywords: [callback, framework, tools] trace_roles: - record-producer trace_conformance_level: 0 diff --git a/integrations/llamaindex/integration.yaml b/integrations/llamaindex/integration.yaml index 5f3067c..25e09bb 100644 --- a/integrations/llamaindex/integration.yaml +++ b/integrations/llamaindex/integration.yaml @@ -8,6 +8,11 @@ maintainer: repository: https://github.com/agentrust-io/integrations license: Apache-2.0 tier: community +marketplace: + category: Frameworks + mark: LI + featured: 4 + keywords: [instrumentation, framework, events] trace_roles: - record-producer trace_conformance_level: 0 diff --git a/integrations/nobulex/integration.yaml b/integrations/nobulex/integration.yaml index 95a5ef5..02cea38 100644 --- a/integrations/nobulex/integration.yaml +++ b/integrations/nobulex/integration.yaml @@ -13,5 +13,9 @@ repository: https://github.com/arian-gogani/nobulex homepage: https://nobulex.com license: MIT tier: community +marketplace: + category: Evidence & receipts + mark: NX + keywords: [receipts, Ed25519, evidence] trace_roles: - external-evidence-source diff --git a/integrations/openshell/integration.yaml b/integrations/openshell/integration.yaml index b416ba8..cf958d1 100644 --- a/integrations/openshell/integration.yaml +++ b/integrations/openshell/integration.yaml @@ -9,6 +9,11 @@ repository: https://github.com/agentrust-io/integrations homepage: https://github.com/NVIDIA/OpenShell license: Apache-2.0 tier: community +marketplace: + category: Security & policy + mark: OS + featured: 5 + keywords: [OpenShell, NVIDIA, OCSF] trace_roles: - record-producer trace_conformance_level: 0 diff --git a/integrations/otel-genai/integration.yaml b/integrations/otel-genai/integration.yaml index 9f91e4e..c22f5f7 100644 --- a/integrations/otel-genai/integration.yaml +++ b/integrations/otel-genai/integration.yaml @@ -8,6 +8,11 @@ maintainer: repository: https://github.com/agentrust-io/integrations license: Apache-2.0 tier: community +marketplace: + category: Observability + mark: OT + featured: 6 + keywords: [OpenTelemetry, spans, telemetry] trace_roles: - record-producer trace_conformance_level: 0 diff --git a/integrations/ramen-ai-cmcp/integration.yaml b/integrations/ramen-ai-cmcp/integration.yaml index 2d568e3..dcfc7f2 100644 --- a/integrations/ramen-ai-cmcp/integration.yaml +++ b/integrations/ramen-ai-cmcp/integration.yaml @@ -13,6 +13,10 @@ repository: https://github.com/ramen-ai-dev/ramen-ai-integrations homepage: https://ramenai.dev license: MIT tier: community +marketplace: + category: Evidence & receipts + mark: RA + keywords: [receipts, exporter, verification] trace_conformance_level: 0 trace_roles: - record-producer diff --git a/integrations/sage-agenttrust/integration.yaml b/integrations/sage-agenttrust/integration.yaml index ccb9e96..bdd768e 100644 --- a/integrations/sage-agenttrust/integration.yaml +++ b/integrations/sage-agenttrust/integration.yaml @@ -14,6 +14,10 @@ repository: https://github.com/l33tdawg/sage-agenttrust homepage: https://github.com/l33tdawg/sage license: Apache-2.0 tier: community +marketplace: + category: Security & policy + mark: SG + keywords: [memory, proxy, consensus] # Level the cMCP-RuntimeClaim (envelope) path passes; the bare C-2 TRACE record is not graded # by agentrust-trace-tests (LoadError), so it carries no conformance level — see README. trace_conformance_level: 0 diff --git a/integrations/sentinel/integration.yaml b/integrations/sentinel/integration.yaml index 09d3802..c8ddb8f 100644 --- a/integrations/sentinel/integration.yaml +++ b/integrations/sentinel/integration.yaml @@ -9,6 +9,10 @@ maintainer: repository: https://github.com/agentrust-io/integrations/tree/main/integrations/sentinel license: MIT tier: community +marketplace: + category: Security & policy + mark: AS + keywords: [fleet, enforcement, detection] trace_conformance_level: 0 trace_roles: - record-producer diff --git a/integrations/shadow-ai/integration.yaml b/integrations/shadow-ai/integration.yaml index 16ddc5e..0fb479e 100644 --- a/integrations/shadow-ai/integration.yaml +++ b/integrations/shadow-ai/integration.yaml @@ -12,6 +12,10 @@ maintainer: repository: https://github.com/agentrust-io/integrations license: Apache-2.0 tier: community +marketplace: + category: Identity & trust + mark: SH + keywords: [discovery, undeclared, remediation] tested_against: cmcp-runtime: "0.2.0" agent-manifest: "1.0.0" diff --git a/integrations/spendguard/integration.yaml b/integrations/spendguard/integration.yaml index 7989aa8..93d1991 100644 --- a/integrations/spendguard/integration.yaml +++ b/integrations/spendguard/integration.yaml @@ -10,6 +10,10 @@ maintainer: repository: https://github.com/m24927605/agentic-spendguard license: Apache-2.0 tier: community +marketplace: + category: Security & policy + mark: $G + keywords: [budget, spend, cost] # Level 0 passes with the explicit TR-SIG-005 UNVERIFIED finding: trace-tests # 0.1.0 does not grade signatures on plain trace records (see README). trace_conformance_level: 0 diff --git a/marketplace/catalog.json b/marketplace/catalog.json new file mode 100644 index 0000000..72f6db3 --- /dev/null +++ b/marketplace/catalog.json @@ -0,0 +1,363 @@ +{ + "catalog_version": 1, + "count": 16, + "integrations": [ + { + "name": "Claude Code", + "package_name": "claude-code", + "vendor": "agentrust-io", + "description": "Claude Code plugin that emits a signed Agent Manifest and TRACE record per session and detects agent composition drift from an approved baseline.", + "path": "claude-code", + "url": "https://github.com/agentrust-io/integrations/tree/main/claude-code", + "homepage": null, + "repository": "https://github.com/agentrust-io/integrations", + "tier": "community", + "stack": [ + "Agent Manifest", + "TRACE" + ], + "category": "Developer tools", + "mark": "CC", + "featured": 1, + "keywords": [ + "Claude", + "session", + "drift" + ] + }, + { + "name": "Codex", + "package_name": "agentrust-codex", + "vendor": "agentrust-io", + "description": "Codex plugin that fingerprints agent configuration, detects workspace-scoped drift, and emits signed Agent Manifest and TRACE Level 0 records.", + "path": "plugins/agentrust-codex", + "url": "https://github.com/agentrust-io/integrations/tree/main/plugins/agentrust-codex", + "homepage": null, + "repository": "https://github.com/agentrust-io/integrations", + "tier": "community", + "stack": [ + "Agent Manifest", + "TRACE" + ], + "category": "Developer tools", + "mark": "CX", + "featured": 2, + "keywords": [ + "Codex", + "workspace", + "drift" + ] + }, + { + "name": "LangChain", + "package_name": "LangChain", + "vendor": "agentrust-io", + "description": "Emits a TRACE Trust Record from LangChain callbacks, including LangGraph tool runs, with tool identity but no payloads.", + "path": "integrations/langchain", + "url": "https://github.com/agentrust-io/integrations/tree/main/integrations/langchain", + "homepage": null, + "repository": "https://github.com/agentrust-io/integrations", + "tier": "community", + "stack": [ + "TRACE" + ], + "category": "Frameworks", + "mark": "LC", + "featured": 3, + "keywords": [ + "callback", + "framework", + "tools" + ] + }, + { + "name": "LlamaIndex", + "package_name": "LlamaIndex", + "vendor": "agentrust-io", + "description": "Emits a TRACE Trust Record from LlamaIndex instrumentation events, reading an allow-list so payloads cannot leak.", + "path": "integrations/llamaindex", + "url": "https://github.com/agentrust-io/integrations/tree/main/integrations/llamaindex", + "homepage": null, + "repository": "https://github.com/agentrust-io/integrations", + "tier": "community", + "stack": [ + "TRACE" + ], + "category": "Frameworks", + "mark": "LI", + "featured": 4, + "keywords": [ + "instrumentation", + "framework", + "events" + ] + }, + { + "name": "OpenShell TRACE Adapter", + "package_name": "OpenShell TRACE Adapter", + "vendor": "agentrust-io", + "description": "Builds a software-only TRACE record from NVIDIA OpenShell OCSF events and AGT ACS policy decisions.", + "path": "integrations/openshell", + "url": "https://github.com/agentrust-io/integrations/tree/main/integrations/openshell", + "homepage": "https://github.com/NVIDIA/OpenShell", + "repository": "https://github.com/agentrust-io/integrations", + "tier": "community", + "stack": [ + "TRACE" + ], + "category": "Security & policy", + "mark": "OS", + "featured": 5, + "keywords": [ + "OpenShell", + "NVIDIA", + "OCSF" + ] + }, + { + "name": "OpenTelemetry GenAI", + "package_name": "OpenTelemetry GenAI", + "vendor": "agentrust-io", + "description": "Builds a TRACE Trust Record from OpenTelemetry GenAI spans, marked as an unsigned telemetry import.", + "path": "integrations/otel-genai", + "url": "https://github.com/agentrust-io/integrations/tree/main/integrations/otel-genai", + "homepage": null, + "repository": "https://github.com/agentrust-io/integrations", + "tier": "community", + "stack": [ + "TRACE" + ], + "category": "Observability", + "mark": "OT", + "featured": 6, + "keywords": [ + "OpenTelemetry", + "spans", + "telemetry" + ] + }, + { + "name": "Agent Passport System", + "package_name": "Agent Passport System", + "vendor": "aeoess", + "description": "Maps one Ed25519-signed Agent Passport System policy decision onto a TRACE Trust Record, refusing decisions that fail signature or expiry verification.", + "path": "integrations/aeoess-aps", + "url": "https://github.com/agentrust-io/integrations/tree/main/integrations/aeoess-aps", + "homepage": "https://agent-passport.org", + "repository": "https://github.com/aeoess/agent-passport-python", + "tier": "community", + "stack": [ + "TRACE" + ], + "category": "Identity & trust", + "mark": "AP", + "featured": null, + "keywords": [ + "passport", + "identity", + "policy" + ] + }, + { + "name": "Agent Sentinel", + "package_name": "Agent Sentinel", + "vendor": "a1k7", + "description": "Runtime enforcement sidecar for agent fleets: detectors for delegation escalation, tool and identity drift, and policy avoidance, emitting Ed25519-signed TRACE Level 0 records.", + "path": "integrations/sentinel", + "url": "https://github.com/agentrust-io/integrations/tree/main/integrations/sentinel", + "homepage": null, + "repository": "https://github.com/agentrust-io/integrations/tree/main/integrations/sentinel", + "tier": "community", + "stack": [ + "TRACE" + ], + "category": "Security & policy", + "mark": "AS", + "featured": null, + "keywords": [ + "fleet", + "enforcement", + "detection" + ] + }, + { + "name": "Agentic SpendGuard", + "package_name": "Agentic SpendGuard", + "vendor": "SpendGuard", + "description": "Spend firewall for LLM agents that reserves budget and gates tool calls before the provider is called, with a signed, hash-chained decision record.", + "path": "integrations/spendguard", + "url": "https://github.com/agentrust-io/integrations/tree/main/integrations/spendguard", + "homepage": null, + "repository": "https://github.com/m24927605/agentic-spendguard", + "tier": "community", + "stack": [ + "TRACE" + ], + "category": "Security & policy", + "mark": "$G", + "featured": null, + "keywords": [ + "budget", + "spend", + "cost" + ] + }, + { + "name": "comply54", + "package_name": "comply54", + "vendor": "comply54", + "description": "Converts a comply54 ComplianceResult into a signed TRACE v0.1 JWT, mapping African regulatory policy decisions to verifiable attestation claims.", + "path": "integrations/comply54", + "url": "https://github.com/agentrust-io/integrations/tree/main/integrations/comply54", + "homepage": "https://comply54.io", + "repository": "https://github.com/comply54/comply54", + "tier": "community", + "stack": [ + "TRACE" + ], + "category": "Compliance", + "mark": "C5", + "featured": null, + "keywords": [ + "Africa", + "regulation", + "compliance" + ] + }, + { + "name": "DecisionAssure", + "package_name": "DecisionAssure", + "vendor": "DecisionAssure (a1k7)", + "description": "Converts a DecisionAssure runtime governance trace into a TRACE v0.2 Trust Record, marked as third-party-asserted.", + "path": "integrations/decisionassure", + "url": "https://github.com/agentrust-io/integrations/tree/main/integrations/decisionassure", + "homepage": null, + "repository": "https://github.com/a1k7/DecisionAssure-Runtime-Governance", + "tier": "community", + "stack": [ + "TRACE" + ], + "category": "Security & policy", + "mark": "DA", + "featured": null, + "keywords": [ + "runtime", + "governance", + "policy" + ] + }, + { + "name": "Nobulex", + "package_name": "Nobulex", + "vendor": "Nobulex", + "description": "Emits Ed25519-signed JCS-canonical action receipts; each carries action_ref = SHA-256(JCS({agent_id, action_type, scope, timestamp_ms})) usable as TRACE external execution evidence (trace-spec #34).", + "path": "integrations/nobulex", + "url": "https://github.com/agentrust-io/integrations/tree/main/integrations/nobulex", + "homepage": "https://nobulex.com", + "repository": "https://github.com/arian-gogani/nobulex", + "tier": "community", + "stack": [ + "TRACE" + ], + "category": "Evidence & receipts", + "mark": "NX", + "featured": null, + "keywords": [ + "receipts", + "Ed25519", + "evidence" + ] + }, + { + "name": "ramen-ai cMCP Adapter", + "package_name": "ramen-ai cMCP Adapter", + "vendor": "ramen-ai", + "description": "TRACE v0.2 exporter that verifies V5 Ed25519 receipts produced by the ramen-ai cMCP adapter and emits a natively signed software-only Trust Record.", + "path": "integrations/ramen-ai-cmcp", + "url": "https://github.com/agentrust-io/integrations/tree/main/integrations/ramen-ai-cmcp", + "homepage": "https://ramenai.dev", + "repository": "https://github.com/ramen-ai-dev/ramen-ai-integrations", + "tier": "community", + "stack": [ + "cMCP", + "TRACE" + ], + "category": "Evidence & receipts", + "mark": "RA", + "featured": null, + "keywords": [ + "receipts", + "exporter", + "verification" + ] + }, + { + "name": "SAGE AgenTrust Bridge", + "package_name": "SAGE AgenTrust Bridge", + "vendor": "SAGE AgenTrust Bridge", + "description": "Reverse proxy that edge-verifies a TRACE record (key-equal) or cMCP RuntimeClaim (gateway-asserted) before admitting a memory submit to a stock, unmodified SAGE consensus-memory node.", + "path": "integrations/sage-agenttrust", + "url": "https://github.com/agentrust-io/integrations/tree/main/integrations/sage-agenttrust", + "homepage": "https://github.com/l33tdawg/sage", + "repository": "https://github.com/l33tdawg/sage-agenttrust", + "tier": "community", + "stack": [ + "cMCP", + "TRACE" + ], + "category": "Security & policy", + "mark": "SG", + "featured": null, + "keywords": [ + "memory", + "proxy", + "consensus" + ] + }, + { + "name": "Scheduled Agents", + "package_name": "scheduled-agents", + "vendor": "agentrust-io", + "description": "Claude Code plugin that fingerprints scheduled-agent routine specs and auto-run hooks, warns when what runs without you watching drifts from an approved baseline, and emits a signed TRACE record.", + "path": "scheduled-agents", + "url": "https://github.com/agentrust-io/integrations/tree/main/scheduled-agents", + "homepage": null, + "repository": "https://github.com/agentrust-io/integrations", + "tier": "community", + "stack": [ + "TRACE" + ], + "category": "Developer tools", + "mark": "SA", + "featured": null, + "keywords": [ + "scheduled", + "unattended", + "drift" + ] + }, + { + "name": "Shadow AI Discovery", + "package_name": "Shadow AI Discovery", + "vendor": "agentrust-io", + "description": "Detects unregistered agents making MCP tool calls not declared in a cmcp catalog.json, emitting DiscoveryEvents that map to Agent Manifest records for remediation or quarantine.", + "path": "integrations/shadow-ai", + "url": "https://github.com/agentrust-io/integrations/tree/main/integrations/shadow-ai", + "homepage": null, + "repository": "https://github.com/agentrust-io/integrations", + "tier": "community", + "stack": [ + "cMCP", + "Agent Manifest" + ], + "category": "Identity & trust", + "mark": "SH", + "featured": null, + "keywords": [ + "discovery", + "undeclared", + "remediation" + ] + } + ] +} diff --git a/plugins/agentrust-codex/integration.yaml b/plugins/agentrust-codex/integration.yaml index 26fe525..37afdbd 100644 --- a/plugins/agentrust-codex/integration.yaml +++ b/plugins/agentrust-codex/integration.yaml @@ -9,6 +9,12 @@ maintainer: repository: https://github.com/agentrust-io/integrations license: Apache-2.0 tier: community +marketplace: + display_name: Codex + category: Developer tools + mark: CX + featured: 2 + keywords: [Codex, workspace, drift] trace_conformance_level: 0 trace_roles: - record-producer diff --git a/scheduled-agents/integration.yaml b/scheduled-agents/integration.yaml index d63ea03..7d14eba 100644 --- a/scheduled-agents/integration.yaml +++ b/scheduled-agents/integration.yaml @@ -8,6 +8,11 @@ maintainer: repository: https://github.com/agentrust-io/integrations license: Apache-2.0 tier: community +marketplace: + display_name: Scheduled Agents + category: Developer tools + mark: SA + keywords: [scheduled, unattended, drift] trace_conformance_level: 0 trace_roles: - record-producer diff --git a/schema/integration.schema.json b/schema/integration.schema.json index 86487a3..eade720 100644 --- a/schema/integration.schema.json +++ b/schema/integration.schema.json @@ -4,7 +4,7 @@ "title": "agentrust-io integration manifest", "type": "object", "additionalProperties": false, - "required": ["name", "vendor", "integrates_with", "description", "maintainer", "repository", "license", "tier"], + "required": ["name", "vendor", "integrates_with", "description", "maintainer", "repository", "license", "tier", "marketplace"], "properties": { "name": { "type": "string", @@ -70,6 +70,43 @@ "enum": ["community", "verified"], "description": "Set by maintainers during review, never self-declared. New submissions use community." }, + "marketplace": { + "type": "object", + "additionalProperties": false, + "required": ["category", "mark"], + "properties": { + "display_name": { + "type": "string", + "minLength": 2, + "maxLength": 64, + "description": "Optional human-facing name when the package identifier is not suitable for display." + }, + "category": { + "type": "string", + "enum": ["Compliance", "Developer tools", "Evidence & receipts", "Frameworks", "Identity & trust", "Observability", "Security & policy"] + }, + "mark": { + "type": "string", + "minLength": 1, + "maxLength": 3, + "pattern": "^[A-Za-z0-9$]+$", + "description": "Short text mark shown when a listing has no approved logo asset." + }, + "featured": { + "type": "integer", + "minimum": 1, + "maximum": 10, + "description": "Optional maintainer-curated display order; lower values appear first." + }, + "keywords": { + "type": "array", + "maxItems": 10, + "uniqueItems": true, + "items": { "type": "string", "minLength": 2, "maxLength": 32 } + } + }, + "description": "Discoverability metadata for the AgenTrust Marketplace." + }, "trace_conformance_level": { "type": "integer", "minimum": 0, diff --git a/scripts/generate_marketplace_catalog.py b/scripts/generate_marketplace_catalog.py new file mode 100644 index 0000000..92108e3 --- /dev/null +++ b/scripts/generate_marketplace_catalog.py @@ -0,0 +1,85 @@ +"""Generate the public AgenTrust Marketplace catalog from integration manifests.""" + +from __future__ import annotations + +import argparse +import difflib +import json +from pathlib import Path + +import yaml + +try: + from scripts.validate_integrations import discover_manifests, validate_repository +except ModuleNotFoundError: # Direct execution puts scripts/ rather than the repo root on sys.path. + from validate_integrations import discover_manifests, validate_repository + + +CATALOG_VERSION = 1 +STACK_LABELS = {"trace": "TRACE", "cmcp": "cMCP", "agent-manifest": "Agent Manifest"} +REPOSITORY_TREE = "https://github.com/agentrust-io/integrations/tree/main/" + + +def build_catalog(root: Path) -> dict: + failures = validate_repository(root) + if failures: + raise ValueError("invalid integration repository:\n" + "\n".join(failures)) + + integrations = [] + for manifest in discover_manifests(root): + data = yaml.safe_load(manifest.read_text(encoding="utf-8")) + path = manifest.parent.relative_to(root).as_posix() + market = data["marketplace"] + integrations.append( + { + "name": market.get("display_name", data["name"]), + "package_name": data["name"], + "vendor": data["vendor"], + "description": data["description"], + "path": path, + "url": REPOSITORY_TREE + path, + "homepage": data.get("homepage"), + "repository": data["repository"], + "tier": data["tier"], + "stack": [STACK_LABELS[value] for value in data["integrates_with"]], + "category": market["category"], + "mark": market["mark"], + "featured": market.get("featured"), + "keywords": market.get("keywords", []), + } + ) + + integrations.sort(key=lambda item: (item["featured"] or 999, item["name"].casefold())) + return {"catalog_version": CATALOG_VERSION, "count": len(integrations), "integrations": integrations} + + +def render(root: Path) -> str: + return json.dumps(build_catalog(root), indent=2, ensure_ascii=False) + "\n" + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--check", action="store_true") + parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parents[1]) + args = parser.parse_args() + root = args.root.resolve() + output = root / "marketplace" / "catalog.json" + expected = render(root) + if args.check: + if not output.is_file() or output.read_text(encoding="utf-8") != expected: + print("marketplace/catalog.json is stale; run scripts/generate_marketplace_catalog.py") + if output.is_file(): + print("".join(difflib.unified_diff(output.read_text(encoding="utf-8").splitlines(True), expected.splitlines(True), fromfile="committed", tofile="generated"))) + return 1 + print(f"marketplace/catalog.json is current ({build_catalog(root)['count']} integrations)") + return 0 + output.parent.mkdir(exist_ok=True) + # Commit byte-stable LF JSON on every platform; Path.write_text translates + # newlines on Windows and makes the Linux CI parity check report false drift. + output.write_bytes(expected.encode("utf-8")) + print(f"wrote {output.relative_to(root)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_generate_marketplace_catalog.py b/tests/test_generate_marketplace_catalog.py new file mode 100644 index 0000000..8de731f --- /dev/null +++ b/tests/test_generate_marketplace_catalog.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import json +from pathlib import Path + +import yaml + +from scripts.generate_marketplace_catalog import build_catalog, render + + +def _write_repository(root: Path, entries: list[tuple[str, dict]]) -> None: + schema = json.loads((Path(__file__).parents[1] / "schema" / "integration.schema.json").read_text()) + (root / "schema").mkdir() + (root / "schema" / "integration.schema.json").write_text(json.dumps(schema), encoding="utf-8") + for relative, document in entries: + directory = root / relative + directory.mkdir(parents=True) + (directory / "README.md").write_text("# Integration\n", encoding="utf-8") + (directory / "integration.yaml").write_text(yaml.safe_dump(document), encoding="utf-8") + + +def _manifest(name: str, *, featured: int | None = None) -> dict: + marketplace = {"category": "Developer tools", "mark": name[:2].upper()} + if featured is not None: + marketplace["featured"] = featured + return { + "name": name, "vendor": "Example", "integrates_with": ["agent-manifest"], + "description": f"Connects {name} to Agent Manifest for deterministic testing.", + "maintainer": {"github": "example"}, "repository": "https://example.com/source", + "license": "Apache-2.0", "tier": "community", "marketplace": marketplace, + } + + +def test_catalog_contains_every_manifest_and_prefers_featured_order(tmp_path: Path) -> None: + _write_repository(tmp_path, [("integrations/zulu", _manifest("Zulu")), ("plugins/alpha", _manifest("Alpha", featured=2))]) + catalog = build_catalog(tmp_path) + assert catalog["count"] == 2 + assert [item["name"] for item in catalog["integrations"]] == ["Alpha", "Zulu"] + assert catalog["integrations"][0]["stack"] == ["Agent Manifest"] + assert catalog["integrations"][0]["url"].endswith("plugins/alpha") + + +def test_render_is_deterministic(tmp_path: Path) -> None: + _write_repository(tmp_path, [("integrations/alpha", _manifest("Alpha"))]) + assert render(tmp_path) == render(tmp_path) + assert render(tmp_path).endswith("\n") + + +def test_catalog_rejects_invalid_manifest_before_generation(tmp_path: Path) -> None: + broken = _manifest("Broken") + del broken["marketplace"] + _write_repository(tmp_path, [("integrations/broken", broken)]) + try: + build_catalog(tmp_path) + except ValueError as exc: + assert "marketplace" in str(exc) + else: + raise AssertionError("invalid manifest was accepted") diff --git a/tests/test_integration_schema.py b/tests/test_integration_schema.py index c280dcb..ec13efd 100644 --- a/tests/test_integration_schema.py +++ b/tests/test_integration_schema.py @@ -21,6 +21,7 @@ def manifest(**overrides: object) -> dict[str, object]: "repository": "https://example.com/repository", "license": "Apache-2.0", "tier": "community", + "marketplace": {"category": "Developer tools", "mark": "EX"}, } value.update(overrides) return value @@ -59,3 +60,14 @@ def test_external_evidence_source_does_not_claim_record_conformance() -> None: trace_roles=["external-evidence-source"], ) ) + + +def test_marketplace_rejects_unknown_category() -> None: + value = manifest(marketplace={"category": "Whatever", "mark": "EX"}) + + try: + validate(value) + except jsonschema.ValidationError as exc: + assert "Whatever" in exc.message + else: + raise AssertionError("unknown Marketplace category was accepted")