Skip to main content
Version: Current

Verify and publish

What Core says at load time​

Each defect has a severity, and the severity decides what the person sees.

severityeffectexample
errorthe station refuses to starta submit call whose tool names no operation; two skills on the same kind
withholdthis skill is not exposed; the rest worksa skill whose invoke names a capability that does not exist
notewritten for the author; nothing changesa playbook that no skill opens

A few refusals worth knowing:

writtenrefused because
an update: {price: …} guard without the submit passing $item.pricethe customer would see a price the tenant never receives
a condition (require, when) badly written, or reading an undeclared fielda require that reads nothing would refuse every item
$item.id in an idempotency fieldsee The submit
a $submitted[…] citing a call no playbook task runs, or an undeclared outputit would be empty, and the tenant would receive a blank with ok: true; see The submit
two calls in one submita retry after the second failed would send the first again
a required task whose skill produces no kindthe journey would announce "ready" on an empty selection
a task that is not exactly one of skill, one_of, all_of; a one_of with fewer than two methods, or with no method without when; two methods that take the same kindnobody could tell what the task does, nor which method a line is; see Playbooks
a task's or a method's when that reads anything but $selection.kinds, looks for a kind the station does not have, or a kind of its own task (for an all_of, of the tasks it joins)the condition could not be checked, would never hold, or would close the task for ever
a skill in two tasks of a playbooka line would not say which task it belongs to
an after or an all_of naming a missing task or a task with no kind, or that loops; an all_of over tasks ordered between themthe task would never open
a required task after an optional one, directly or through an all_ofthe optional task would be required in practice
present, select, submit, countable, depends_on or on_loss on a skill without kindsits rows are not items: nothing would read these keys
several kinds with one lacking when; a kind's when that reads anything but a field of present.fields ($fields.<name>)nothing would say which kind a row is; an undeclared row key is never read
invoke with both method and native, or neither; method: search_productsmethod names an operation of the station's APIs; Convoy's capabilities are written native
an undeclared $params.x, a parameter nothing reads, an invoke.args argument the capability does not takea parameter nothing sets or nothing reads would mislead the reader; the API would ignore an unknown argument and answer as though it had not been sent
a parameter without default read by invoke.args, or that no task binds; a task's params that do not bind $submitted[<task>.<call>].<output> of another task of the playbook, never skipped, whose every method makes the callthe call would leave with a blank value; see Playbooks
an unknown render.componenta typo would silently draw something else
component: report without render.reportnothing says which field is the answer: the person would read prose instead of the report
render.report with a component other than reportnothing would read it
two skills or two playbooks with the same namethe second would replace the first, without a word
a submit_guard.validate that names no operationno submit could ever go through
catalog.mode: indexed without search_text_fieldsevery row would be indexed on empty text
a key Core does not know, at any depthit would suggest it has an effect; the provisioner's API answers 422 with unknown_keys

The tests​

# the examples in this guide, compiled by Core
pytest convoy/packages/convoy-core/tests/test_docs_examples.py

# a running station: the probes replay defects that were actually shipped
./scripts/readiness/all.sh

Publish​

  • File: drop <tenant_id>.yaml into the deployment's configs folder.
  • API: the provisioner first runs the same check as the station. A config with an error gets a 422 listing the faults (where, message, remedy), and nothing is written. Otherwise it writes the config to the database and starts the tenant's containers. It answers ready only once every server serves this tenant: the MCP has its config and exposes the skills Core compiles, the agent has its history. Otherwise the tenant is degraded, and the response (502) says why.

Between two changes, the provisioner looks at each ready station every minute. If a server stops, or no longer serves the station, it goes degraded; once the server serves again, it goes back to ready. Each move is recorded in the log (/runs).

Either way, the station recompiles at startup and shows every error and every note. Read the notes.

Update a station​

POST /v1/tenants/{id}/redeploy, with no body, puts the station on the provisioner's Core and images, with the config it already has. If that Core now refuses the config, the answer is a 422 with the faults, and no server is touched. For the whole fleet: make stations-build, then make stations-redeploy.

A station only runs images built with the same Core as the provisioner. Otherwise the change gets a 409 core mismatch, and nothing is modified. To go back to a config that worked: GET /v1/tenants/{id}/versions lists the last ten versions, and POST /v1/tenants/{id}/rollback with {"version": n} applies that version's config again.

To know beforehand which stations would be refused: python -m convoy_provisioner.admin check-configs gives each station's verdict, and the provisioner also logs it at startup.

To know whether a station still runs its author's file: POST /v1/tenants/{id}/compare with the config answers same and the list of keys that differ, and changes nothing.

Remove​

DELETE /v1/tenants/{id} sets the tenant to deleting, then removes its servers, its config and its route. Meanwhile any change gets a 409. If a step fails, the response (502) says what is already removed: sending the same DELETE again finishes the job. The log (/runs) stays in the database after the delete, so you can tell who removed what.

Two changes to the same tenant never run at once: the second gets a 409 busy straight away. Send it again once the first one is done.