Skip to content

Provider protocol

Thiago Botelho edited this page Aug 15, 2026 · 1 revision

Provider protocol

Protocol 1.0.0. Every command accepts --json and answers with an envelope on stdout. Failures go to stderr with exit code 2.

The normative version of this page is docs/provider-protocol.md. This page adds the full payloads and the consumer checklist.

Two version numbers

protocolVersion describes the envelope and the capability names. contractVersion, inside result, describes the payload shape for that command. They move independently: a new command can ship without touching the protocol major, and a report can gain a field without renaming any capability.

Both are majors you must check. providerVersion is informational.

Discovery

mestre-yoda handshake --json
{
  "protocolVersion": "1.0.0",
  "provider": "mestre-yoda",
  "providerVersion": "0.1.0",
  "command": "handshake",
  "status": "success",
  "result": {
    "protocolVersion": "1.0.0",
    "provider": "mestre-yoda",
    "providerVersion": "0.1.0",
    "mode": "read-only",
    "capabilities": [
      "profiles.list",
      "profiles.describe",
      "project.inspect",
      "project.recommend",
      "project.check",
      "project.plan",
      "catalog.validate"
    ],
    "mutations": false
  }
}

A consumer should call this first, reject an unknown protocol major, and then only invoke commands whose capability appears in the list. Feature detection through the capability array is what lets a consumer work across provider versions without a compatibility table.

Capabilities

Command Capability Result
profiles profiles.list Identity, version, and digest of each profile
describe <id> profiles.describe The complete public manifest
inspect --root <dir> project.inspect Inventory and detected signals
recommend --root <dir> project.recommend Profiles ranked by confidence and conformity
check <id> --root <dir> project.check Compliance report
plan <id> --root <dir> project.plan Ordered tasks, without effects
validate catalog.validate Internal integrity of the catalog

Success envelope

Every successful response has the same five fields around a command-specific result:

{
  "protocolVersion": "1.0.0",
  "provider": "mestre-yoda",
  "providerVersion": "0.1.0",
  "command": "check",
  "status": "success",
  "result": {}
}

Failure envelope

{
  "protocolVersion": "1.0.0",
  "provider": "mestre-yoda",
  "providerVersion": "0.1.0",
  "command": "describe",
  "status": "failure",
  "error": {
    "code": "INVALID_REQUEST",
    "summary": "Unknown Golden Path profile: missing-profile."
  }
}

This goes to stderr, with exit code 2. Read both streams if you want to handle failures programmatically.

Failure summaries never carry local paths, stack traces, or content from the analyzed project. A test asserts the summary contains no stack frame.

inspect

{
  "contractVersion": "1.0.0",
  "root": "/absolute/path/to/project",
  "inventory": {
    "files": 8,
    "inspectedTextFiles": 8,
    "inspectedBytes": 565,
    "skippedSymlinks": []
  },
  "signals": {
    "apiVersioning": false,
    "applicationLayer": true,
    "architectureTests": false,
    "...": "21 keys in total, alphabetically ordered"
  }
}

root is the resolved absolute path, so it is the one field in a success payload that reveals the local filesystem. Strip it before storing a report somewhere less trusted than the machine that produced it.

recommend

{
  "contractVersion": "1.0.0",
  "recommended": {
    "id": "dotnet-modular-react-postgresql",
    "version": "1.0.0",
    "digest": "sha256:89963b03fac95b7af4972176a46b92143948ddf280b3bc627d53539af797b56f",
    "confidence": 100,
    "conformity": 77
  },
  "candidates": [
    {
      "id": "dotnet-modular-react-postgresql",
      "version": "1.0.0",
      "digest": "sha256:89963b03fac95b7af4972176a46b92143948ddf280b3bc627d53539af797b56f",
      "confidence": 100,
      "conformity": 77
    }
  ]
}

recommended is null when the catalog is empty. It is otherwise the first element of candidates.

check

{
  "contractVersion": "1.0.0",
  "profile": {
    "id": "dotnet-modular-react-postgresql",
    "version": "1.0.0",
    "digest": "sha256:89963b03fac95b7af4972176a46b92143948ddf280b3bc627d53539af797b56f"
  },
  "compliant": false,
  "score": 77,
  "summary": { "passed": 16, "failed": 5, "blocking": 2, "total": 21 },
  "rules": [
    {
      "id": "stack.no-nestjs",
      "severity": "blocking",
      "description": "The backend does not use NestJS.",
      "passed": true,
      "signal": "forbiddenNestAbsent",
      "expected": true,
      "observed": true,
      "remediation": "Remove NestJS dependencies and keep the backend on ASP.NET Core."
    }
  ]
}

rules contains every rule, passing and failing, in manifest order. Both expected and observed are present so a consumer can render why a rule failed without re-deriving it.

plan

{
  "contractVersion": "1.0.0",
  "planId": "sha256:e58b0438750d3f3a8be336f57f383f24d36c7b67c3028912428be06f53f7aa84",
  "profile": {
    "id": "dotnet-modular-react-postgresql",
    "version": "1.0.0",
    "digest": "sha256:89963b03fac95b7af4972176a46b92143948ddf280b3bc627d53539af797b56f"
  },
  "readOnly": true,
  "effects": [],
  "tasks": [
    {
      "order": 1,
      "ruleId": "api.version-v1",
      "severity": "blocking",
      "action": "Version public routes under /api/v1 and document the evolution policy.",
      "requiresHumanDecision": true
    }
  ],
  "summary": { "taskCount": 5, "blockingDecisions": 2 }
}

planId is a SHA-256 over the profile digest, the serialized signals, and the serialized tasks. Two plans with the same id were produced from the same standard against the same observed state.

Read-only guarantee

In protocol 1.0.0, handshake.result.mutations is false, plan.readOnly is true, and plan.effects is always empty.

Consumers must not infer operations from the text of tasks. Those strings are guidance for a human reviewer. Parsing "Add versioned migrations" into a filesystem operation is exactly the failure this boundary exists to prevent, and a future protocol version will declare typed operations explicitly rather than leaving them to be inferred from prose.

JSON Schemas

Four schemas ship in schemas/:

Schema Describes
golden-path-manifest.v1.schema.json A profile manifest
golden-path-report.v1.schema.json The check result
golden-path-plan.v1.schema.json The plan result
provider-result.v1.schema.json The envelope itself

They are published as https://mestre-yoda.dev/schemas/... $id values. Resolve them from the repository rather than over the network.

Compatibility rules

  • an incompatible change requires a new major of protocolVersion;
  • an incompatible change to a manifest, a report, or a plan requires a new major of contractVersion and a new schema;
  • changing the content of a profile changes its version and its digest;
  • consumers should reject unknown majors and digests they have not approved;
  • local paths, error stacks, and project content are never part of a failure envelope.

Consumer checklist

Before trusting a result in an automated flow:

  1. Call handshake and reject an unknown protocolVersion major.
  2. Confirm the capability you need is in capabilities.
  3. Confirm mutations is false if you expect a read-only provider.
  4. Check contractVersion inside the payload you consume.
  5. Compare the profile digest against the one you approved, not just version.
  6. Read stderr and the exit code, not only stdout.
  7. Treat tasks[].action as text for humans.

Clone this wiki locally