Pin a flow's version
This guide pins a flow at a version, tries a newer version without putting it in use, moves the pin, and reads the history.
A run that names no flow version takes the newest version its project reaches, so a version you publish is in use at once. A pin holds the flow at a version you choose, for a project, an org or the whole tenant. Runs there take the pinned version until you move the pin, and a version you publish meanwhile runs only when a run names it.
Before you start
Section titled “Before you start”- A flow with a published version (runtime 0.3.0 or later). This guide
uses
acme.process-orderfrom Write a flow, at 1.0.0, and publishes a 1.1.0. - A project's id:
kindgi projects listlists them. - For your app: Node 22.12 or later, Python 3.11 or later, or Java 17 or
later, with Kindgi 0.3's client, and
KINDGI_API_URLandKINDGI_API_TOKENset.
Which version a run takes
Section titled “Which version a run takes”A run that names its version (flowVersion) runs that version. A run that
names none takes the first of these:
- The project's own pin.
- The pin of the project's org.
- The tenant's pin.
- With none of those, the newest version the project reaches.
A pin counts only while its version is registered and active. A pin whose version doesn't reach the run's project is passed over, and the run takes the newest version the project reaches. A version reaches the project it was made in, and a project a deployment shipped it to.
A run keeps the version it started with, also when it resumes. A pin doesn't change what a version's steps run: each tool and agent step runs the version it was locked to when the flow version was published. The one exception is an agent step that names no version, in a run whose segment a release gate policy of that agent set on a segment covers: it runs the agent's live version for that segment, as a run started in that segment would. A policy for the project, its org or the tenant doesn't change what the step runs.
Try a new version without putting it in use
Section titled “Try a new version without putting it in use”- Pin the version in use, 1.0.0, for the project.
- Publish the new version, 1.1.0, as you published 1.0.0. Runs in the project still take 1.0.0.
- Try it by naming it: a run with
flowVersion1.1.0 runs it; every other run there still takes 1.0.0. - Move the pin to 1.1.0 once it does what you want. Runs take it from the next run.
Ask your coding agent: "I want to pin my flow's version in a project, then try a new version there without putting it in use." For reference, the script looks like this, with the project's id as its argument, after step 2:
import { createClient } from '@kindgi/sdk/client';
const kindgi = createClient(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi devconst projectId = process.argv[2];const flow = 'acme.process-order';
// Hold the project's runs on 1.0.0.await kindgi.flows.pinned.pin(flow, { version: '1.0.0', scope: { kind: 'project', projectId }, reason: '1.0.0 is the version we checked',});
// Which version would a run there take, and why?const inUse = await kindgi.flows.pinned.resolve(flow, { projectId });console.log(inUse.version, inUse.via, inUse.pinnedScope?.kind);
// A run that names no version takes the pin; one that names 1.1.0 tries it.const held = await kindgi.runs.start({ flow, projectId, input: { orderId: 'A-100' } });const tried = await kindgi.runs.start({ flow, flowVersion: '1.1.0', projectId, input: { orderId: 'A-100' },});console.log(held.flowVersion, held.flowVersionVia);console.log(tried.flowVersion, tried.flowVersionVia);import sys
from kindgi.client import Kindgi
kindgi = Kindgi() # KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi devproject_id = sys.argv[1]flow = "acme.process-order"
# Hold the project's runs on 1.0.0.kindgi.flows.pinned.pin( flow, version="1.0.0", scope={"kind": "project", "project_id": project_id}, reason="1.0.0 is the version we checked",)
# Which version would a run there take, and why?in_use = kindgi.flows.pinned.resolve(flow, project_id=project_id)print(in_use.version, in_use.via, in_use.pinned_scope.kind if in_use.pinned_scope else None)
# A run that names no version takes the pin; one that names 1.1.0 tries it.held = kindgi.runs.start(flow=flow, project_id=project_id, input={"orderId": "A-100"})tried = kindgi.runs.start( flow=flow, flow_version="1.1.0", project_id=project_id, input={"orderId": "A-100"})print(held.flow_version, held.flow_version_via)print(tried.flow_version, tried.flow_version_via)package com.acme.app;
import com.kindgi.client.Kindgi;import com.kindgi.client.models.PinScopeProject;import com.kindgi.client.models.PinVersionBody;import com.kindgi.client.models.PinnedVersionResolution;import com.kindgi.client.models.Run;import com.kindgi.client.models.StartRunBody;import com.kindgi.client.resources.FlowsPinnedResolveParams;import java.util.Map;import java.util.UUID;
public final class PinAFlow { public static void main(String[] args) { Kindgi kindgi = Kindgi.create(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev UUID projectId = UUID.fromString(args[0]); String flow = "acme.process-order";
// Hold the project's runs on 1.0.0. kindgi.flows().pinned().pin(flow, PinVersionBody.builder() .version("1.0.0") .scope(PinScopeProject.builder().projectId(projectId).build()) .reason("1.0.0 is the version we checked") .build());
// Which version would a run there take, and why? PinnedVersionResolution inUse = kindgi.flows().pinned().resolve(flow, FlowsPinnedResolveParams.builder().projectId(projectId).build()); System.out.println(inUse.version() + " " + inUse.via() + " " + (inUse.pinnedScope() == null ? null : inUse.pinnedScope().kind()));
// A run that names no version takes the pin; one that names 1.1.0 tries it. Run held = kindgi.runs().start(StartRunBody.WithFlow.builder() .flow(flow) .projectId(projectId) .input(Map.of("orderId", "A-100")) .build()); Run tried = kindgi.runs().start(StartRunBody.WithFlow.builder() .flow(flow) .flowVersion("1.1.0") .projectId(projectId) .input(Map.of("orderId", "A-100")) .build()); System.out.println(held.flowVersion() + " " + held.flowVersionVia()); System.out.println(tried.flowVersion() + " " + tried.flowVersionVia()); }}package acme.app
import com.kindgi.client.Kindgiimport com.kindgi.client.models.{PinScopeProject, PinVersionBody, StartRunBody}import com.kindgi.client.resources.FlowsPinnedResolveParamsimport java.util.UUIDimport scala.jdk.CollectionConverters._
object PinAFlow { def main(args: Array[String]): Unit = { val kindgi = Kindgi.create() // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev val projectId = UUID.fromString(args(0)) val flow = "acme.process-order"
// Hold the project's runs on 1.0.0. kindgi.flows().pinned().pin(flow, PinVersionBody.builder() .version("1.0.0") .scope(PinScopeProject.builder().projectId(projectId).build()) .reason("1.0.0 is the version we checked") .build())
// Which version would a run there take, and why? val inUse = kindgi.flows().pinned().resolve(flow, FlowsPinnedResolveParams.builder().projectId(projectId).build()) println(s"${inUse.version} ${inUse.via} ${Option(inUse.pinnedScope).map(_.kind).orNull}")
// A run that names no version takes the pin; one that names 1.1.0 tries it. val held = kindgi.runs().start(StartRunBody.WithFlow.builder() .flow(flow) .projectId(projectId) .input(Map("orderId" -> "A-100").asJava) .build()) val tried = kindgi.runs().start(StartRunBody.WithFlow.builder() .flow(flow) .flowVersion("1.1.0") .projectId(projectId) .input(Map("orderId" -> "A-100").asJava) .build()) println(s"${held.flowVersion} ${held.flowVersionVia}") println(s"${tried.flowVersion} ${tried.flowVersionVia}") }}It prints the version a run there would take, then the version each run ran and how it was chosen:
1.0.0 pinned project1.0.0 pinned1.1.0 explicitTo move the pin, pin 1.1.0 for the same scope. Pinning the version the scope already pins changes nothing.
Pin for an org or the whole tenant
Section titled “Pin for an org or the whole tenant”A pin's scope is { kind: 'project', projectId }, { kind: 'org', orgId }
or { kind: 'tenant' }. An org's pin covers its projects that have no pin of
their own, and the tenant's pin covers every project that has neither.
In a tenant with environment orders (the projects a version goes through on
its way to production, such as staging, then production), a flow in an order
can't be pinned for an org or the tenant: such a pin would act in every
environment of the order at once. The pin is refused with
409 pin-scope-spans-environments; pin it for each project instead. A pin
for a later environment of an order is fine: runs there take it.
A promotion into the next environment that makes what it carries live
(makeLive) also pins each flow version it carries for that project, and
the answer lists them in pinnedFlows. Once a flow is pinned in a project,
a promotion without makeLive keeps the pinned flow in use there: move the
pin, or promote with makeLive.
Remove a pin
Section titled “Remove a pin”kindgi.flows.pinned.unpin(flow, { scope }) removes the scope's own pin. The
scope then follows the next scope up, and with no pin above it, the newest
version its projects reach. A scope with no pin of its own answers
409 not-pinned.
A pinned version can't be unregistered: unregistering it answers
409 flow-version-pinned, with the scopes that pin it in details.scopes.
Move or remove those pins first.
Read the pins and their history
Section titled “Read the pins and their history”kindgi.flows.pinned.list(flow) lists every scope with a pin: its version,
when it was set and by whom. kindgi.flows.pinned.history(flow) lists every
pin and unpin, newest first: the scope, the version before and after, who
asked, and their reason.
Deleting an org removes its pins and records an unpin for each, by the deletion. Deleting a project removes its pins.
Who can pin
Section titled “Who can pin”Pinning and unpinning need pin on the flow, which its admins have. For a
flow more than one project serves (an environment order carries it to later
environments), a project's pin needs admin on that project, and an org's or
the tenant's pin admin on each of the flow's projects. Reading the pins
needs read on the flow.
- Start a run: name a version with
flowVersion. - Write a flow: publish a new version.
- The API reference has each route, and the errors reference each refusal.