Product

The whole platform, one part at a time.

A catalog your teams publish to, templates that chain their products, five ways to order, a gateway for the APIs teams already run, a page that shows what everything did, a chat agent that answers from the docs, and one CLI to install and run it all. Here is each part, as it works today in 0.x.

01 · Catalog and products

A product is a folder: a Dockerfile, its tests, and one product.yaml.

Each team keeps its products in a catalog repository. The product.yaml says what the container's actions take and return, who may use them, how hard they may be pushed, and what to tell people when they fail. CI checks it, builds and scans the image, and publishes one signed release that every install pulls.

samples/local/products/ticket/product.yaml abridged
apiVersion: vennir.dev/v2
kind: Product
name: ticket
team: local
check: check            # is it ready on this install?
maxRunning: 2          # at most two of its steps at once
errors:
  MISSING_INPUT:
    means: It wasn't given what it needs.
    do: Fill in the missing input and run it again.
actions:
  - name: open
    idempotent: true     # the same title, the same ticket
    maxAttempts: 2
    shareWith: ["*"]
    cleanup: {action: close}
    plan: plan           # for Preview
    input:
      required: [title]
      properties:
        title: {type: string, maxLength: 200}
    output:
      properties:
        ticket:
          type: string
          x-vennir-provides: local.ticket
  • Actions with schemas

    Each action's input and output are JSON Schema, enforced at submit, before the step starts and when it finishes. The run form is built from the same schema.

    INPUT_INVALID · OUTPUT_INVALID
  • Readiness checks

    A check action asks the cloud who the product is and whether it may do its job, changing nothing. Each install runs it on registration, on a new image, daily, and on Check now.

    ready · principal · missing
  • Errors explained by their owners

    A product says what each of its codes means and what to do; team.yaml says where to ask. The failed step shows both, and whether a retry is safe.

    errors · help · idempotent
  • Capacity

    Cap a product's steps per install, keep two runs off the same project, choose Spot or on-demand nodes where the install offers both, and size each step.

    maxRunning · concurrencyKey · capacity · resources
  • Approvals

    A step can wait for a maintainer of the product's team: always, only when another team runs it, or for named teams. Whoever started the run can never approve it.

    APPROVAL_DECLINED · APPROVAL_EXPIRED
  • Sharing and targets

    shareWith names the teams that may run an action; visibility: hidden keeps it to yours. team.yaml lists which projects or accounts each team may aim your identity at.

    TARGET_NOT_ALLOWED
Readiness, per install The same product can be ready on one install and not on another.
Ready Not ready Check failed Checking Not checked yet
gcp/cloud-sqlNot ready Acts as gcp-cloud-sql Missing: roles/cloudsql.admin on a project its instances go in
A failure, in its owner's words From product.yaml's errors and team.yaml's help.
ticket.open · FailedMISSING_INPUT Means It wasn't given what it needs. Do Fill in the missing input and run it again. Safe to retry? Yes: the action is idempotent. Ask in #local-samples
Two people agree before it runs Undecided for 3 days by default, it expires.
app-hosting.createAwaiting approval Waiting for an approval from a maintainer of its team. Approve Decline… Preview what it would do

Every field: the catalog guide and product.schema.json.

02 · Templates and DAGs

Chain actions once. Let anyone run the chain with a form.

A template names actions, never images, so it keeps working as products release. Its parameters become the form; each step's inputs are values, parameters, or another step's output. Every step still runs as its own product's identity, in its own team's namespace, whoever runs the template.

samples/local/dags/shout-backwards.yaml kind: Template
apiVersion: vennir.dev/v2
kind: Template
name: shout-backwards
shareWith: ["*"]
parameters:                  # becomes the run form
  required: [text]
  properties:
    text: {type: string, title: Text, maxLength: 1000}
examples:                    # offered under "Use this"
  - {name: a palindrome, parameters: {text: never odd or even}}
steps:
  - id: shout
    action: upper-case.transform
    inputs: {text: {$param: text}}
  - id: backwards
    action: reverse.transform
    dependsOn: [shout]
    inputs:
      text: {$fromStep: shout, $output: text}
What it would do Preview · changes nothing
ticket.open Opens ticket T-dd864760.
create local/ticket · T-dd864760 · Rotate the staging certificate
A product's plan action says what the real one would do. A step that needs an earlier step's output is listed as not previewed, with why.
  • Outputs into inputs. $fromStep reads a step in dependsOn; the kernel fills it in when the next step starts, and checks it against the schema then.
  • Checked before merge. ./vennir catalog validate checks every action exists, is shared with the team, and gets the inputs it needs.
  • Or write the DAG yourself. A kind: Dag names images and teams directly; the console pins each :latest to its digest at submit.
  • Compose in the console. Build one from the actions shared with you, then propose it to the catalog as a pull request.
03 · Ordering

Pick what you need. The order wires itself.

Products type their inputs and outputs (a project, a region, a database), so the cart can work out which step feeds which, what you already have, and what to ask once. Each step is still checked as if you ran it alone: a cart never lets you do more than you could step by step.

Cart For team: web · 2 steps
Place order
Asked once for the whole order gcp.regionus-central1
gcp/app-hosting.createin your cart
projectIdUses sandbox-web-1 regionAsked once for the order: gcp.region serviceNameshop-api imageFrom build-image's imageUri
gcp/build-image.createin your cart
projectIdUses sandbox-web-1 sourceArchiveUrlgs://[YOUR BUCKET]/shop-api.zip imageUri[YOUR REPOSITORY]/shop-api:1
Optional: cloudSqlConnectionName Add cloud-sql.create
  • Catalog and run forms

    Every template and action, with its readiness. Run opens a form from its schema; targets offer only what the owning team allows yours.

  • Schedules

    A template or action on a timetable, as one of the team's bots, at most every five minutes, in your time zone.

  • The API

    Connect services under /api: submit a DAG, run a template, place a cart. A repeated requestId returns the run already started.

  • Keyless bots

    A GitHub Actions or GitLab job exchanges its own ID token for an hour as a bot, from one repository and, if you like, one branch.

  • Ordering bots

    For a storefront: it may run only the templates you list, 60 orders a minute by default, under your team's quota and approvals.

  • Callbacks and webhooks

    Signed posts when an order or a run finishes, to hosts your install allows. Chat notifications to Slack, Google Chat or Teams.

.github/workflows/order.yml no stored token
permissions:
  id-token: write          # the job's own identity, instead of a stored token
env:
  VENNIR: https://[YOUR CONSOLE]
  AUD: [THE AUDIENCE THE BOT'S CARD SHOWS]
steps:
  - name: Sign in to Vennir
    run: |
      ID=$(curl -sH "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=$AUD" | jq -r .value)
      TOKEN=$(jq -n --arg t "$ID" '{idToken: $t}' | curl -s -X POST $VENNIR/api/vennir.console.v1.AuthService/ExchangeToken \
        -H 'Content-Type: application/json' -d @- | jq -r .token)
      echo "::add-mask::$TOKEN"; echo "VENNIR_TOKEN=$TOKEN" >> "$GITHUB_ENV"
  - name: Run a template
    run: |
      curl -X POST $VENNIR/api/vennir.console.v1.TemplateService/RunTemplate \
        -H "Authorization: Bearer $VENNIR_TOKEN" -H "Content-Type: application/json" \
        -d "$(jq -nc --arg r "ci-$GITHUB_RUN_ID" '{team: "local", name: "shout-backwards", requestId: $r, parameters: {text: "hello world"}}')"
04 · API products and the gateway

Publish a backend you already run. Its OpenAPI file is the allowlist.

A Service in your namespace, a Cloud Run service or a Lambda function URL: add an api.yaml and its OpenAPI file to your catalog folder. The gateway answers at /<team>/<name>/, signs callers in as the console does, and forwards only what the spec describes, with a credential it mints per request. Nothing is stored.

samples/aws/apis/orders/api.yaml kind: Api
apiVersion: vennir.dev/v2
kind: Api
name: orders
spec: openapi.yaml             # what may be forwarded
backend:
  url: https://abcdefghijklmnopqrstuvwxyz012345.lambda-url.us-east-2.on.aws
  auth:
    awsSigV4:                  # signed per request, no key
      roleArn: arn:aws:iam::123456789012:role/vennir-orders-api
      region: us-east-2
      service: lambda
shareWith: [web]
limits:
  default: 300/min
  web: 600/min
timeout: 15s
maxRequest: 16KiB
What happens to a request LiveHeldPaused
  1. 1The API exists here404
  2. 2It's open: not closed, paused or held503
  3. 3You're signed in401
  4. 4It's shared with your team403
  5. 5The body isn't too large413
  6. 6The method and path are in the spec404
  7. 7Parameters, headers and body fit400
  8. 8Your team is within its rate429
  9. 9Forwarded, with the backend's own credential and a signed Vennir-Caller→
Traffic per endpoint and per calling team for the API's own team; a maintainer can pause it with a message during an incident. Bodies and header values are never recorded.
05 · Observe

What ran, what was refused, and who changed what.

One page with five tabs: Security checks, Metrics, Usage, Logs and the Audit log. Everyone can open it; what each person sees follows what they may see elsewhere.

Logs · From: Steps · Team: local as a member of local sees it
redacted before kept
12:04:10 info local · open · attempt 1 · opened T-dd864760
12:04:11 debug local · open · attempt 1 · Authorization: [redacted bearer token]
12:04:15 info local · watch · attempt 1 · watching T-dd864760
Your teams' steps Every level
Another team's, in a run you own Its JSON info lines only

Security checks

Each check marked Enforced, Off here or Not yet, read from this install's settings; 7 days of refusals; every image with its last scan and SBOM.

Metrics

Waiting and running now, wait to start at the 95th percentile, why steps failed, step times per product: from kernel-api's own tables.

Monitoring

Prometheus metrics from every component, with alerts and a Grafana dashboard, through your Prometheus Operator, GKE's or Amazon's managed Prometheus, or a bundled one.

Audit log

Every change, allowed or refused, hash-chained; optionally copied to a bucket that refuses deletes and compared entry by entry.

Chain intact
06 · Ask Vennir

A chat agent that cites the docs and fills a cart. You place the order.

It acts as the person asking, so it sees only what they may. Its only effect is a proposed cart, which they open and place themselves. A question that isn't about Vennir gets a fixed sentence; an answer resting on neither the docs it cites nor its tools' results is replaced.

Models, chosen by an admin
Gemini on Vertex AI · no key Claude in Amazon Bedrock · no key Claude through Anthropic's API OpenAI-compatible: LM Studio, Ollama
On a cloud, choose the install's own: nothing then leaves it. With Anthropic's API or an outside server, the conversation does. Nothing is kept unless someone rates an answer (90 days).
Ask Vennir
I need a Cloud Run service for shop-api. What do I order?
Order gcp/app-hosting.create. It needs a project: your team already has sandbox-web-1, and the cart will offer it. Your runs of it wait for a gcp maintainer only when they aim at a shared-* project.1
1 · Finding and running things from the catalog
Proposed cart · 1 action · for web Open in cart
07 · The console and the vennir CLI

A console for the people who order. A CLI for the people who run it.

The console is the one front door: Catalog, Runs, What runs made, APIs, Observe, your teams, and Admin, each page linking the guide that explains it. vennir installs it on kind, Google Cloud, AWS or a cluster you have, and keeps each environment as one folder of settings, never a secret.

  • init · applyAsk the questions; show what it will create and cost, and do it on approval
  • check · statusTools, sign-in, cluster and what's installed, changing nothing
  • sleep · wakeScale to zero, keeping the data; or on a schedule, or when idle
  • backup · backups · restoreBoth databases as one pair, to the bucket or a folder; restore asks first
  • logs · consoleThe platform's logs or one run's steps; port-forward the console
  • config · tools · ssoChange a setting, fetch pinned tools, turn on Google sign-in
  • catalog create · grantMake a catalog; let another install read it, keyless
  • catalog validate · checkSchemas; then build, test with no network, and scan
  • catalog publish · release · promotePush pinned, signed images; push the signed release; version it
  • destroyRemove what it made, asking about each kind of data first
a day with vennir
# change a setting, then make the install match it
$ vennir config set -e dev size resilient
$ vennir apply dev --plan-only

# before an upgrade
$ vennir backup dev --to ~/vennir-dumps
$ vennir config set -e dev vennir [VERSION]
$ vennir apply dev

# one run's steps, while their Pods are there
$ vennir logs dev --execution 3f2c9a7e-0b1d-4a51-9c6e-2d8f4b7a1e05

# the evening
$ vennir sleep dev
$ vennir config set -e dev scaleDown.schedule "0 20 * * 1-5"

# in your catalog repository, what CI runs
$ ./vennir catalog check teams/web/products/site
$ ./vennir catalog publish teams/web/products/site && ./vennir catalog release
Vennir is 0.x

What isn't done yet.

Everything above is built. These are the gaps, from the repository's own list.

The full list: TODO.md. What is and isn't enforced: the security model.

See it on your laptop, then see what teams build with it.

Get started Use cases