The project is in a healthy, maintained state
kiosk-reputation decides *when* and *how hard* to challenge a request with a proof-of-work, issues and verifies the signed challenge, and provides a pluggable reputation policy interface. Pure Ruby — no dependency on any PoW gem or kiosk-core. A PoW backend is registered via Kiosk::Reputation::Backends.register: kiosk-pow-equihash registers itself as "equihash" (the shipped default) in a Rails app; the host registers kiosk-pow-argon2id as the legacy "argon2id" and kiosk-pow-cuckoo as the opt-in "cuckatoo". Anti-DoS invariant: Challenge.verify performs cheap HMAC-sig + expiry checks BEFORE the one expensive backend eval, so floods of forged/expired proofs are rejected without running a proof check. Ships one policy, Policies::RecentSpend: it asks one proof of a session, by a chance that the customer's recent spend lowers.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 0
 Project Readme

Kiosk — OSS reference implementation

Apache-2.0 monorepo for Kiosk — the standards-based open protocol that lets the AI assistants of visitors and customers discover, transact with, and pay an operator's service, and its Ruby reference implementation (one HTTP API, several assistants on one customer account, app-layer-authorized data plane with opt-in Postgres RLS defense-in-depth, AP2 mandate trail).

Install

Every gem in the tables below is on RubyGems. The canonical install is one line, the meta-gem:

# Gemfile
gem "kiosk-all"
bundle install
bin/rails generate kiosk:install   # the initializer + the kiosk migrations
bin/rails db:migrate

kiosk-all is kiosk-core + kiosk-server — the data plane, and nothing else. The RLS backstop, the proof-of-work toll (kiosk-reputation and kiosk-pow-equihash) and the identity and payment adapters are separate gems that you add for your stack; kiosk-all/README.md says which and why.

What an adopting app needs before that line

Requirement Why, and what it rules out
Rails ~> 8.1 kiosk-server declares railties, actionpack, actioncable, activerecord and activesupport at ~> 8.1, and kiosk-pay-stripe declares activerecord at ~> 8.1. No other gemspec names a Rails gem. That excludes Rails 7.x and 8.0.x — bundler will refuse to resolve against an app on either, so this is a precondition and not a footnote. Older Rails lines are untested here, so they are not claimed; widening the floor means adding a CI job first.
Ruby >= 4.0 Every gemspec declares it, and CI runs on 4.0.
PostgreSQL Kiosk's kiosk_* tables and the optional RLS backstop are Postgres. No other database is supported.
An existing account model c.user_model names it, and c.user_id_type must match its primary key. Kiosk adds an assistant identity beside each account; it does not replace the account.

No toolchain pin ships in this repository, and that is a decision rather than an omission: .ruby-version, mise.toml and .mise.toml are gitignored so each developer manages their own (kiosk-demo-getgrocery/mise.toml.example is the tracked template). The range above is the contract, both ends of it are on the merge gate, and that is worth more to a contributor than one pinned patch release a version manager may or may not honour.

Inside this repository it is path:, and that form does not travel

Every Gemfile here consumes its siblings by path: — gem "kiosk-server", path: "../kiosk-server" — which serves the working tree rather than a built package. That is deliberate: it is how a change across gems is tested end to end. It is also a blind spot, because nothing resolving by path: can tell a file that EXISTS from a file that is PACKAGED. Do not copy the path: form into an app outside this repository.

The step-by-step operator walkthrough — install through a served protocol, with a worked operation — is kiosk.tech/onboarding.html.

Layout

The gem tables and the demo table are scored on DIFFERENT axes, so their values are not comparable and the columns are named apart below. The gems carry API stability: alpha is pre-v1.0 — the surface may still change between releases — which is the same thing each gem's own README says about itself. The demos carry deployment: active means the demo is gated on every CI run and live at its own subdomain. A demo being active says nothing about how settled the alpha engine underneath it is.

Core framework

Gem Purpose API stability
kiosk-core Value types, abstract bases, GUC constants, configuration. No Rails dep. alpha
kiosk-rls Opt-in RLS DSL + migration helpers; declares policies in a migration and compiles them to PostgreSQL DDL alpha
kiosk-server Rails engine, routes, OAuth client registration and DPoP token endpoint, executor alpha
kiosk-all Meta-gem; bundle add kiosk-all installs core + server alpha
kiosk-test-support Conformance checks, the assistant-side protocol client and a KYC provider stand-in for tests alpha

Plugins & adapters

Gem Purpose API stability
kiosk-rls-rspec RSpec journey-test helpers for RLS policies alpha
kiosk-rls-minitest Minitest journey-test helpers for RLS policies alpha
kiosk-user-idp-devise Customer sign-in adapter (Devise) alpha
kiosk-pay-stripe PSP adapter — Stripe, AP2 mandate trail alpha
kiosk-kyc-proveli KYC provider adapter — the Proveli anonymizing broker alpha

Proof-of-work

Gem Algorithm Solver memory Default?
kiosk-pow-equihash Equihash (n=168, k=7) ~1.3 GiB ✅
kiosk-pow-argon2id Argon2id (D=0..256) 64 MiB no
kiosk-pow-cuckoo Cuckatoo29 not measured no

Each backend is built on kiosk-pow, which holds the shared BLAKE2b-256. kiosk-pow/README.md tells how to add a backend. See kiosk-pow-equihash/README.md for the full comparison and rationale.

Reputation & security

Gem Purpose API stability
kiosk-reputation Customer reputation factors alpha
kiosk-redteam Red-team scenarios, adversarial test harness alpha

Demo operators

Demo Vertical Deployment
kiosk-demo-getgrocery Grocery delivery active
kiosk-demo-atablefor Restaurant table-booking — non-commerce (no payments) active
kiosk-demo-hoteling Hotel booking active
kiosk-demo-skooti Scooter rental active
kiosk-demo-stylish Salon/appointment booking (Combette on Park) — non-commerce (no payments) active
kiosk-demo-philslist Classifieds board — non-commerce (no payments) active
kiosk-demo-tudu Collaborative todo — non-commerce (no payments) active

kiosk-demo-proveli is not an operator. It is the KYC broker: a service outside the operators. A demo connects to it to confirm facts about a customer, for example an age over 18 or a driving licence. It serves no Kiosk operation, and it deploys at kyc.demo.kiosk.tech.

e2e/ is not in that table, because it is not an eighth operator. It is the end-to-end test harness — fixtures, a stripe-mock-backed pay flow and the AI-assistant drivers — so it serves no vertical and deploys nowhere. It runs as a CI gate (./e2e/run.sh) and nothing else.

Four more top-level directories are neither a gem nor a demo. bin/ holds the checks that gate this repository — one check-* script per property, each exiting non-zero when that property breaks; CI runs them as jobs of their own, not through rake. deploy/ is the runbook for hosting the live demo fleet — Caddy, Postgres, systemd and the scripts that provision and smoke-test the box (deploy/README.md). spec/ holds three GENERATED indexes the publication and conformance checks read and regenerate — not a test suite; each gem's own tests live under its own spec/. .github/ holds the one CI pipeline, workflows/ci.yml — the gems matrix, the demos matrix and e2e/run.sh, on every push and pull request.

Each demo's tests drive it over HTTP the way an assistant does; CI runs them with bin/rails test (bundle exec rspec in hoteling).

Four of the demos — atablefor, getgrocery, hoteling, skooti — also carry a before-after.md: a contrast between what an AI assistant can do at that business today and what the same errand looks like once Kiosk is installed, followed by the operator-side adoption recipe. Four rather than all seven operators is deliberate. Every fenced block in one declares what produced it. The rest say it shorter: philslist carries an inline Before / after section in its README; stylish and tudu carry neither.

The demos are standalone Rails apps, so a helper two of them need is COPIED, not shared. bin/check-demo-copies — its own CI job too — declares every hand-written Ruby file that exists in more than one demo, plus .gitignore, as kept identical, kept identical apart from its header prose, or a deliberate per-demo variant, each with the reason; it fails the build when copies that must agree stop agreeing, and when a new duplicate turns up undeclared. Header prose may vary, but a magic comment may not: # frozen_string_literal: true changes how the file runs, so it is compared. The Rails skeleton each demo edits for its own port and host is out of scope on purpose, and that exclusion is recorded file by file, with its reason, in the same script.

Everything in this repo consumes the gems by path:, which serves the working tree — so nothing here can tell a file that EXISTS from a file that is PACKAGED. A file outside lib/ that a gem reads at runtime must be in that gem's spec.files, or the published gem does not carry it.

Run the fleet in containers

docker compose up at the repository root runs the seven operator demos, the KYC broker, stripe-mock with its relay and one Postgres, on the ports each demo README names. All containers share one network, so the apps reach each other on 127.0.0.1 as they do on the deployed box. Payment and the identity check work. A demo's battery runs against it from the host, for example in kiosk-demo-skooti:

SERVER_URL=http://localhost:3004 bundle exec ruby ../kiosk-redteam/suites/skooti.rb

What still differs from the deployed box, and why:

  • The apps run in Rails development mode. Production mode needs the secrets the box keeps in its env files (see "The secrets of production mode" below) and sends its session cookie only over TLS.
  • No TLS: each origin is http://localhost:<port>. The box serves HTTPS through Caddy.
  • Each start resets every database and makes a new signing key, so assistant registrations and tokens do not survive a restart. The box migrates and keeps both.
  • Payment goes to stripe-mock. The box charges Stripe in test mode, which needs a Stripe key.
  • The KYC broker signs with the development key this repository tracks. The box's broker has its own key.

The secrets of production mode

deploy/env/<app>.env.example names each variable of an app. On the box, deploy/rollout.sh makes each missing value that nothing off the box holds. For a value that someone else holds it prints MISSING, and the operator supplies it: deploy/README.md lists these values. To make a value by hand:

Variable What it is How to make it
SECRET_KEY_BASE The Rails key that signs and encrypts cookies bin/rails secret
KIOSK_<APP>_DB_PASSWORD The password of the app's Postgres role, the same value that deploy/postgres-init.sql sets openssl rand -hex 32
KIOSK_SIGNING_KEY_B64 The Ed25519 key that signs access tokens, checkouts and receipts openssl genpkey -algorithm ed25519 | openssl base64 -A
KIOSK_POW_SECRET The key that signs each proof-of-work challenge, 32 bytes or more openssl rand -hex 32
KIOSK_PROVELI_INTAKE_SECRET The secret an operator and the KYC broker share. The broker keeps the same value as KIOSK_PROVELI_<OPERATOR>_SECRET, for example KIOSK_PROVELI_SKOOTI_SECRET openssl rand -hex 32, then set the value on both sides
PROVELI_KEY_PEM The broker's Ed25519 key that signs each attestation openssl genpkey -algorithm ed25519
KIOSK_PROVELI_PUBLIC_KEY_PEM The public half of the broker's key, which an operator uses to verify an attestation openssl pkey -in proveli_key.pem -pubout
STRIPE_SECRET_KEY The Stripe key of an operator that takes payment, in test mode Stripe Dashboard, Developers, API keys, with "Test mode" on: the secret key that starts with sk_test_
KIOSK_UNLOCK_SIGNING_KEY_PEM skooti only: the Ed25519 key that signs each unlock token for a scooter lock openssl genpkey -algorithm ed25519

Contributing

CONTRIBUTING.md is the whole of it: how to run one gem, one demo, the end-to-end harness, bin/lint (RuboCop, bundler-audit, Brakeman) and the bin/check-* checks; what the merge gate is; and what this repository expects of a changelog entry and a comment. A security flaw goes through a GitHub issue — read SECURITY.md first, for which repository takes it and for the fact that filing one is public disclosure.

Before 1.0 every gem carries the protocol's full version — the version parity the spec promises (protocol §14.1): the protocol, every gem here and the published skill read the same MAJOR.MINOR.PATCH, and a new cut of any one of them moves all of them. From 1.0 they share MAJOR.MINOR. The number is Kiosk::Protocol::API_VERSION, and the ~> MAJOR.MINOR.0 inter-gem constraints, the lockfiles, kiosk-server's skill_url and every demo's skill pin test follow it. A release is a commit on main that moves every gemspec to the new version and gives each changelog the version heading. The maintainer then builds each gem from its *.gemspec and pushes it to RubyGems. No git tag marks a release.

The two development keypairs this repo tracks on purpose

kiosk-demo-skooti/config/dev_unlock_key.pem (Ed25519) and kiosk-demo-proveli/config/dev_proveli_key.pem (Ed25519) are private keys, they are committed, and that is deliberate. Read the header of either file: it says what the key is for, that it is world-readable, and that it must never sign anything real. Both applications enforce that rather than asking — production refuses to boot without an explicit key in the environment, and refuses again if the key it is given is the shipped one, compared on the public half in DER so a re-serialised copy cannot slip past.

The skooti key, dev_unlock_key.pem, cannot be generated per machine: it is a KNOWN-ANSWER VECTOR. Its public half and one signature are reproduced in the lock firmware, in the C host test that firmware ships with, and in the demo's own known-answer test, and the crosscheck target signs a live message with the private half for the C verifier to check. A per-machine key turns all of that red on a fresh clone.

Do not read this as licence to commit a key. A scanner pointed at this repo will flag both files, and that is the correct behaviour — which is why .github/secret_scanning.yml names exactly these two paths and nothing else. bin/check-publication-paths fails on any OTHER tracked private key, and on either of these two if it ever loses its do-not-use banner; it re-proves that detector against a freshly minted key on every run, so it cannot rot into a gate that quietly matches nothing. And note the part that no later commit repairs: this project does not rewrite published history, so a key that reaches a pushed commit stays reachable in that history whatever a subsequent commit deletes. Generate at setup or read from the environment; if a fixed vector is genuinely unavoidable, declare it in that script with the reason.

License

Apache-2.0 for every gem in this repo. See each gem's LICENSE.txt.

Every Kiosk gem lives in this repo and under that licence: ls */*.gemspec is the whole list, and each one declares spec.license = "Apache-2.0".

Links