Webhook icon
Schedule icon
Script icon
Process icon
If icon
Create icon
SlackIncomingWebhook icon
Request icon
Log icon
Sleep icon
LoopUntil icon

Publish a Model Card Automatically When a Promotion Lands

Render a model card from a registry promotion webhook, escalate missing fields to GitHub and Slack, publish the card, and poll until it is readable.

Categories
Infrastructure

Model cards record what a promoted version is for, what it scores, and what it must not be used for, yet they are almost always written late. This blueprint makes the promotion event the trigger - the registry POSTs model, version, and metrics to a keyed webhook, the card is rendered from that payload, and a promotion that arrives without its required fields is escalated instead of published.

How it works

  1. on_promotion (keyed io.kestra.plugin.core.trigger.Webhook) receives a JSON body of model, version, metrics, intended_use, limitations, and owner whenever a version is approved for production.
  2. nightly_backfill (io.kestra.plugin.core.trigger.Schedule, cron 0 6 * * *) re-renders the bundled sample promotion as a smoke test of the publishing path; it ships disabled until the webhook path is trusted.
  3. generate_model_card (python on the Process runner) uses the webhook body when it arrives or the promotion.json fixture it writes when it does not, checks the six required fields, renders a card with a details table, a metrics table, intended use, limitations, and the owner, writes model-card.md, and emits the card text, the missing fields, and the card uri through the repo outputs protocol.
  4. check_required_fields (io.kestra.plugin.core.flow.If) branches on has_missing == true. Missing fields open a labeled issue through open_card_gap_issue (io.kestra.plugin.github.issues.Create) and post alert_missing_fields (io.kestra.plugin.slack.notifications.SlackIncomingWebhook) naming the uri that was not published.
  5. A complete payload is POSTed by post_card_to_registry (io.kestra.plugin.core.http.Request) with a Bearer token, recorded by log_card_post, and held for settle_before_sync (io.kestra.plugin.core.flow.Sleep) before the first probe.
  6. poll_registry_sync (io.kestra.plugin.core.flow.LoopUntil) GETs the same uri through probe_registry_artifact and lets check_sync turn the response code into a boolean, repeating every 5 seconds for up to 2 minutes until the registry serves the artifact.
  7. publish_verdict folds the field check, the POST status, and the sync flag into verdict, model, card_uri, and metrics, while the errors block alerts Slack when the pipeline itself breaks.

What you get

  • A markdown card with the six required sections rendered straight from the promotion event, including a metrics table built from whatever the registry reported.
  • A gate that refuses to publish an incomplete card and instead opens a GitHub issue and posts a Slack alert naming the missing fields.
  • A bearer-authenticated POST plus a bounded poll, so published means the artifact is readable rather than merely accepted.
  • verdict, model, card_uri, and metrics as typed outputs for dashboards, audits, and release checks.

Who it's for

  • ML platform teams that require a card for every promoted version and want the registry event to drive the documentation.
  • Model governance owners who need intended use and limitations captured before a version serves production traffic.
  • Platform engineers standardizing publication behind one endpoint template and one set of secrets.

Why orchestrate this with Kestra

Card writing usually happens in a wiki tab nobody updates after the first release, so the record drifts from the model it describes. Kestra puts the trigger, the render, the completeness gate, the escalation, the authenticated publish, and the sync poll in one versioned file - every promotion leaves an execution with its card, its verdict, and its missing fields, and the disabled schedule gives you a rehearsal path before the webhook goes live.

Prerequisites

  • A model registry that POSTs a promotion JSON body to a Kestra webhook and accepts markdown on a docs endpoint.
  • Python 3 on the Kestra worker for the Process runner, plus outbound access to the registry endpoint.
  • A GitHub token with issue write access and a Slack incoming webhook for the escalation path.

Secrets

  • PROMOTION_WEBHOOK_KEY: key segment guarding the webhook URL, anyone holding it can submit promotion events.
  • REGISTRY_API_TOKEN: bearer token the registry accepts on the docs endpoint for the publish POST and the sync probes.
  • GITHUB_TOKEN: token open_card_gap_issue uses to create the tracking issue.
  • SLACK_WEBHOOK_URL: Slack incoming webhook for held-card alerts and failure alerts.

Quick start

  1. Add the four secrets and point registry_docs_endpoint at your docs route, keeping the {model} and {version} placeholders.
  2. POST a full promotion body to the webhook URL and confirm verdict: published with the reported metric count.
  3. Replay it with owner removed and confirm the GitHub issue, the Slack alert, and verdict: escalated_missing_fields.
  4. Keep nightly_backfill disabled until both paths behave.

How to extend

  • Add required fields, for example training_data, by extending the REQUIRED tuple in generate_model_card.
  • Fail the execution instead of alerting with io.kestra.plugin.core.execution.Fail after the escalation branch.
  • Render the card from a template file kept in git by swapping the line builder for a template read.
  • Notify a second channel, for example email with io.kestra.plugin.email.MailSend, alongside Slack.
  • Mirror the card to object storage with io.kestra.plugin.aws.s3.Upload before the registry POST.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.