Changelog
What each release changed, newest first. The specification, the skill and the reference implementation share one version number. Sources: the site's changelog and the reference implementation's changelog.
0.6.28
Specification and skill
- Skill 0.6.28: only the human sets the ask-first amount. An assistant's payment script stops above it, and a spending cap that accepts a payment is not a yes.
Reference implementation
- Every gem and
API_VERSIONare 0.6.28, matching skill 0.6.28, which says only the human sets the ask-first amount. Every demo pins its digest. - kiosk-demo-stylish: the owner signs in as owner@stylish.example with stylish-demo-password.
0.6.27
Specification and skill
- Skill 0.6.27: the text is that of 0.6.26. The version matches release 0.6.27 of the reference implementation.
- The KYC broker demo, its adapter gem and its class are named Proveli:
kiosk-demo-proveli,kiosk-kyc-proveli,Kiosk::KycProviders::Proveli.
Reference implementation
- Every gem and
API_VERSIONare 0.6.27, matching skill 0.6.27; the engine default isskill-v0.6.27.mdand every demo pins its digest.MIN_CLIENTstays 0.6.0. - The KYC broker is Proveli: gem
kiosk-kyc-proveli, classKiosk::KycProviders::Proveli, demokiosk-demo-proveli, env namesKIOSK_PROVELI_*andPROVELI_KEY_PEM. - deploy: the broker unit, env file, role and database are named
proveli;deploy/CHECKLIST.md§7c moves a box once. The broker's migrations are one fresh install.
0.6.26
Specification and skill
- Skill 0.6.26: the text is that of 0.6.25. The version matches release 0.6.26 of the reference implementation.
- The spending cap is the most that one
payof an assistant may charge. The operator no longer counts the spend of an assistant over time.
Reference implementation
- Every gem and
API_VERSIONare 0.6.26, matching skill 0.6.26; the engine default isskill-v0.6.26.mdand every demo pins its digest.MIN_CLIENTstays 0.6.0. - kiosk-server: the spending cap is the most one
paymay charge.spending_cap_window_daysand the running total of settled and unfinished charges are removed. - ROADMAP: 0.7 adds a client script, DNS-AID, Poppy business side and UCP to consider; 0.8 adds Shared Payment Tokens and MPP; rejected ideas are gone.
- kiosk-server: the PoW gate and the auth toll raise their ConfigurationError when only a gem's version file is loaded, as a path bundle loads it, and no NoMethodError or NameError.
- kiosk-reputation:
RecentSpendasks the Equihash params only when the gem is loaded, so a path bundle that loads only its version file gets n=168, k=7 and no NoMethodError.
0.6.25
Specification and skill
- Skill 0.6.25: the text is that of 0.6.24. The version matches release 0.6.25 of the reference implementation.
Reference implementation
- Every gem and
API_VERSIONare 0.6.25, matching skill 0.6.25; the engine default isskill-v0.6.25.mdand every demo pins its digest.MIN_CLIENTstays 0.6.0. - The root README, CONTRIBUTING and SECURITY state the release process, the e2e prerequisites, what CI checks, the seeded demo passwords and the whole scope.
- Demo READMEs match the code and tests: stylish shows no spending cap, a revoked tudu key gets 401, skooti lists A1, A, B, C, the getgrocery KYC submit is optional.
- The KYC broker README says the broker proves only that the human holds a government account, and names the gap that leaves.
- skooti's
unlock_urldescription and before-after page say a phone app of the shop gives the token to the lock; the demo publishes no such app. - Demo READMEs and compose files carry no history or internal labels, and name operations and their paths.
- Demo error pages and footers call the operator «this site» or «a demo»; tudu has one footer; the GetGrocery logo and its Start shopping link are fixed.
- The KYC broker page says it proves that the human holds a government account, and that the human answers each check afresh.
- atablefor names diners as the people who sign in, in its header link, its note and its signed-in label.
- The sign-in page of atablefor, getgrocery, hoteling, philslist, skooti and stylish is a whole document under the demo's header.
- skooti
/unlockwith a missing parameter answers a 400 page that tells the person what to do; its pages say that no phone app is published. - The manage-assistants page shows money amounts and a cap of 0 as «no payments allowed»; its
spending_capfield takes an amount in the cap currency. - The manage-assistants and link-approval pages are whole documents with a title, in the engine layout
layouts/kiosk/server/application.
0.6.24
Specification and skill
- Skill 0.6.24: the binding poll and the claim in the code solve the toll and send the request again; on exit
5of the listener, a wait reads the state one time. - The specification lists the mandate checks that answer
403as the formal spec does. An absentiatorexp, and anexpmore than 24 hours ahead, answer400. - Both spec documents name
Kiosk-Payment-Setupas the challenge of the card-setup gate. - protocol.md says five auth URLs and the 0.6 series, and lets only the operator's own verbs collide with the reserved names.
- Both spec documents name the card-setup return page and the KYC callback among the mount paths that take no access token.
- The proof-of-work diagram gives 9 to 20 s and 1.0 to 1.4 GiB per proof, as the text does.
- The schemas publish the KYC callback, its answer, and the
subscribedandunsubscribedmessages. Section 17 names the event stream's Action Cable frames. - The two spec documents list the same profile clauses for
payand for the reuse of an access token.
Reference implementation
- Every gem and
API_VERSIONare 0.6.24, matching skill 0.6.24; the engine default isskill-v0.6.24.mdand every demo pins its digest.MIN_CLIENTstays 0.6.0. validate_requestsholds the KYC callback body to the publishedcallbackobject: a wrong-typed member is a 400 naming it.- The
101answer at<endpoint>/eventscarries the three version headers, as every answer under the mount does. - A subscription whose reach is withdrawn leaves the connection, so a later
subscribewith its identifier getsreject_subscription.
0.6.23
Specification and skill
- Skill 0.6.23: wait for the listener's
subscribedline before the call; the total in the one message is an estimate; subscribe topaymentbeforepay.
Reference implementation
- Every gem and
API_VERSIONare 0.6.23, matching skill 0.6.23; the engine default isskill-v0.6.23.mdand every demo pins its digest.MIN_CLIENTstays 0.6.0.
0.6.22
Specification and skill
- Skill 0.6.22: before the first order, the assistant starts the identity check and the card setup, and asks for all human steps and the ask-first amount in one message.
Reference implementation
- Every gem and
API_VERSIONare 0.6.22, matching skill 0.6.22; the engine default isskill-v0.6.22.mdand every demo pins its digest.MIN_CLIENTstays 0.6.0.
0.6.21
Specification and skill
- Skill 0.6.21: the first listener starts with no
--since, the work ends when the request is done, open questions go in one message, and both samples readkey.pemas bytes. - The onboarding guide shows getgrocery's code as the reference repository has it: each file copied whole, or marked as an excerpt.
- The onboarding guide names Ruby 4.0, the floor of every gemspec, and the
actioncabledependency of kiosk-server. - The onboarding guide says that Hermes and Claude Code have each driven the loop against the hosted demos on protocol 0.6.
- The landing says the operator picks how to measure the reputation that makes proofs rarer, with recent spend as the default.
Reference implementation
- Every gem and
API_VERSIONare 0.6.21, matching skill 0.6.21; the engine default isskill-v0.6.21.mdand every demo pins its digest.MIN_CLIENTstays 0.6.0. - The tampered-price attacks on hoteling and skooti lower the price in the lines the operator signed and keep every other member, so the forgery has the shape of a real checkout.
- ROADMAP.md says what the Poppy draft says: an assistant is named by an HTTPS URL that serves its metadata, with its keys on the same domain.
0.6.20
Specification and skill
- Skill 0.6.20: a wait that ends with no event reads the state one time: KYC calls the gated action again, card setup calls
payment_setup. One rule ends every wait.
Reference implementation
- Every gem and
API_VERSIONare 0.6.20, matching skill 0.6.20; the engine default isskill-v0.6.20.mdand every demo pins its digest.MIN_CLIENTstays 0.6.0. - skooti's
reservecheckout line names the scooter by itsscooter_code.
0.6.19
Specification and skill
- Skill 0.6.19: an event can come late or not at all, and the operation that owns a state gives its current value. The specification says the same.
- The specification states that two event writes that overlap can commit out of
idorder, so a subscriber can get an event late or never.
Reference implementation
- Every gem and
API_VERSIONare 0.6.19, matching skill 0.6.19; the engine default isskill-v0.6.19.mdand every demo pins its digest.MIN_CLIENTstays 0.6.0. - The vendored event schema and the channel comment say that an event can come more than once.
- The event comments say that an event committed after a higher id is outside id order, and that
Events.emitgoes last in its transaction. - hoteling's confirmation code is six digits, unique at its property. The
reserve_roomcheckout line names the room type. - ROADMAP.md says why Kiosk exists, what 0.6 ships, what 0.7 and later bring, and what is out of scope.
0.6.18
Specification and skill
- Skill 0.6.18: the toll is solved, and the request sent again after a
402is the tolled retry. The specification and onboarding use the same words. - The landing: one headline, Why Kiosk before How it works, Events, "For visitors/customers", no Ruby jargon, a plain list of compatible assistants, and what each demo shows.
Reference implementation
- Every gem and
API_VERSIONare 0.6.18, matching skill 0.6.18; the engine default isskill-v0.6.18.mdand every demo pins its digest.MIN_CLIENTstays 0.6.0. - Comments, READMEs, test names and the atablefor home page say that an assistant solves the toll, and name the request sent again after a
402the tolled retry.
0.6.17
Specification and skill
- Skill 0.6.17: the registration example pays the proof-of-work toll when the operator answers
402 pow_required. - The landing has one headline, a "Why Kiosk" section, the gatekeepers' deals in the problem, a plain list of compatible assistants, and what each demo shows.
Reference implementation
- Every gem and
API_VERSIONare 0.6.17, matching skill 0.6.17; the engine default isskill-v0.6.17.mdand every demo pins its digest.MIN_CLIENTstays 0.6.0. - The event stream sends the replay of every subscription the upgrade URL declares in one ascending id order, and live events in the order the pubsub gives them.
deploy/kyc-pairing-audit.shfinds a KYC operator whose Gemfile indents thekiosk-kyc-proveline.
0.6.16
Specification and skill
- Skill 0.6.16: it gives the body of
POST <endpoint>/agents/kyc, introducesowner,min_clientand a topic'sexamples, and drops the token'ssubandagent_idclaims. - The landing says Claude Code has run the demos too, and names the section "Compatible assistants".
- The reference notes and the onboarding page name Kiosk's tables
kiosk_*(kiosk_stripe_customersamong them), and thekiosk_current_*()functions as kiosk-rls's.
Reference implementation
- Every gem and
API_VERSIONare 0.6.16, matching skill 0.6.16; the engine default isskill-v0.6.16.mdand every demo pins its digest.MIN_CLIENTstays 0.6.0. - Kiosk's tables are
kiosk_*tables in the schemac.schemanames,publicby default, made by plain Rails migrations. A 0.6 database is rebuilt to upgrade. bin/rails g kiosk:rls:installcreates thekiosk_current_*()functions RLS policies call;kiosk:installdoes not.- Each demo's migrations are one fresh install again, with kiosk:install's migrations; every deployed demo database is rebuilt.
Kiosk::Identitytakes the actoragentorhuman;serviceis removed.
0.6.15
Specification and skill
- Skill 0.6.15, the protocol and the text of 0.6.14: the skill cut of reference release 0.6.15.
Kiosk-Min-Clientstays 0.6.0. - The onboarding page sets
skill_sha256in the initializer, adds the skill pin test, and says that kiosk-server givesskill_url. - The landing speaks to operators and to people who use an assistant. It adds a privacy section for people and one prompt to copy per demo, and lists the standards in their own section.
Reference implementation
- Every gem and
API_VERSIONare 0.6.15, matching skill 0.6.15; the engine default isskill-v0.6.15.mdand every demo pins its digest.MIN_CLIENTstays 0.6.0. - The demos take
skill_urlfrom kiosk-server and set onlyskill_sha256. Each has a skill pin test that fails, with the steps to fix, when kiosk-server gives a different skill.
0.6.14
Specification and skill
- Skill 0.6.14:
WWW-Authenticatenames the gate in the three 402s only, and the token-file rule is stated once. The protocol is that of 0.6.13. - The landing's problem paragraph names what an assistant on a laptop meets: a CAPTCHA, a CDN block or a ban, and gates that open by vendor list or partner deal.
Reference implementation
- Every gem and
API_VERSIONare 0.6.14, matching skill 0.6.14, and every demo and the engine default pinskill-v0.6.14.md.MIN_CLIENTstays 0.6.0.
0.6.13
Specification and skill
- Skill 0.6.13, the text of 0.6.12: an
invalid_client401 namesKiosk-Client-Assertion, and the verify page's 401 namesKiosk-Session.Kiosk-Min-Clientstays 0.6.0. - The site serves a changelog page, one section per release, built from both changelogs and linked from the landing footer.
- The spec names the challenge of the
invalid_client401,Kiosk-Client-Assertion, and of the verify page's 401,Kiosk-Session.
Reference implementation
- Every gem and
API_VERSIONare 0.6.13, matching skill 0.6.13, and every demo and the engine default pinskill-v0.6.13.md.MIN_CLIENTstays 0.6.0. docker compose upat the root runs the whole demo fleet with the KYC broker and stripe-mock, so payment and the identity check work in containers.- The KYC broker waits longer before each new attempt at the operator's callback (
retry_on wait: :polynomially_longer). - The
invalid_client401 of the token and device-authorization endpoints namesKiosk-Client-Assertion; the 401 of the engine pages namesKiosk-Session.
0.6.12
Specification and skill
- Skill 0.6.12 says that
network_confirmation_idof a payment receipt is the network's id, ornot reported by the payment provider. - The onboarding page and the KYC schema name the operator's KYC provider as what sets the issuer, the key and the audience of an attestation.
- The spec drops the JSON answer an operator MAY give at a human sign-in or sign-out page.
- A payment receipt always carries
network_confirmation_id: the network's id, ornot reported by the payment providerwhen the provider reports none.
Reference implementation
- Every gem and
API_VERSIONare 0.6.12, matching skill 0.6.12, and every demo and the engine default pinskill-v0.6.12.md.MIN_CLIENTstays 0.6.0. - Every demo battery runs
CallToll: the call toll holds over HTTP against seven known ways to skip a proof. - Every demo serves /privacy, linked from its footer: what it and the Kiosk engine store, and for how long.
- Every demo's robots.txt states its AI-use preferences as an IETF AIPREF
Content-Usageline too: search and AI use yes, AI training no. - An operator sets its KYC provider in one setting,
c.kyc_provider, which names the issuer, the public key and the audience;kyc_issuer,kyc_public_keyandkyc_audienceare removed. - The KYC broker demo sends a refused callback again from an in-process job, with Rails' default retry_on.
- A JSON call to a human sign-in, sign-out or browser page gets the plain Rails refusal; the JSON pointer to the protocol is removed.
- When the payment provider reports no card network id, the payment receipt's
network_confirmation_idisnot reported by the payment provider.
0.6.11
Specification and skill
- The spec says that a
402and the retry that pays it count as one tolled call, and thatheadis 0 while an origin has no event. - Skill 0.6.11 names the listener's
--token-command,verification_uri_completeand the receipt'sorder_id, shows the binding poll and the claim, and states both rules.
Reference implementation
- Every gem and
API_VERSIONare 0.6.11, matching skill 0.6.11, and every demo and the engine default pinskill-v0.6.11.md.MIN_CLIENTstays 0.6.0. - The manage-assistants page shows when each assistant was linked, the spend only on an origin with payments, and the cap only with a
spending_cap. Demos migrate 20261024100026. - Every demo lockfile takes crass 1.0.8, which fixes a CSS sanitizer bypass.
0.6.10
Specification and skill
- Skill 0.6.10: the assistant verifies the checkout and the two receipts against the operator's JWKS, and gets a step for a link code that is unknown, expired or used.
Reference implementation
- Every gem and
API_VERSIONare 0.6.10, matching skill 0.6.10, and every demo and the engine default pinskill-v0.6.10.md.MIN_CLIENTstays 0.6.0.
0.6.9
Specification and skill
- Skill 0.6.9 has the text of 0.6.8.
Kiosk-Min-Clientstays 0.6.0.
Reference implementation
- Every gem and
API_VERSIONare 0.6.9, matching skill 0.6.9, and every demo and the engine default pinskill-v0.6.9.md.MIN_CLIENTstays 0.6.0. - Demo migration 20261022100025 creates
assistant_accountsempty: only registration records an assistant account.
0.6.8
Specification and skill
- Skill 0.6.8 has the text of 0.6.7.
Kiosk-Min-Clientstays 0.6.0. - The specification: the first binding merges the assistant's own account into the customer's account, so its payments and visits count as the customer's spend, also after an unlink.
Reference implementation
- Every gem and
API_VERSIONare 0.6.8, matching skill 0.6.8, and every demo and the engine default pinskill-v0.6.8.md.MIN_CLIENTstays 0.6.0. - The first binding merges the assistant account into the human's account, so its settlements and visits count in the recent spend, also after an unlink. Demos migrate 20261022100025.
0.6.7
Specification and skill
- Skill 0.6.7: a 401, or a 400 on the arguments, does not count toward the toll; a token is kept in the origin's
tokenfile; shared scripts go in~/.kiosk/bin/. - The site says "toll" for the proof of work an operator asks, and gives the proof time measured on the pinned solver: 9 to 20 s and 1.0 to 1.4 GiB.
Reference implementation
- Every gem and
API_VERSIONare 0.6.7, matching skill 0.6.7.MIN_CLIENTstays 0.6.0: the protocol is the protocol of 0.6.6. - Every demo and the engine default pin
skill-v0.6.7.md, which says which answers count toward the toll and gives the proof time as a range. - atablefor takes bookings for today and the next 13 days. An availability search with no date shows today and tomorrow.
- The getgrocery and atablefor pages describe the toll every demo asks: 9 to 20 s per proof on one Apple M3 laptop core, to register and on every 10th call.
0.6.6
Specification and skill
- Skill 0.6.6: the operator counts the tolled calls of each account, so the assistants of one account share the count and the spend.
Kiosk-Min-Clientstays 0.6.0. - The onboarding guide installs the Kiosk gems from RubyGems.
Reference implementation
- Every gem and
API_VERSIONare 0.6.6, matching skill 0.6.6.MIN_CLIENTstays 0.6.0: a 0.6.5 assistant answers a 402 on any call. - Every demo and the engine default pin
skill-v0.6.6.md, which says that the assistants of one account share the count. - The toll counts the tolled calls of each account, and the assistants of one account share the count and the spend. A binding adds the previous account's count.
- The recent spend of an account also counts what its assistants paid on their own accounts before a binding moved them there.
- The seven operator demos move the count from
kiosk.agents.calls_since_prooftokiosk.tolled_calls, keyed by the account. - Every gem README installs its gem from RubyGems.
0.6.5
Specification and skill
- Skill 0.6.5, the text of 0.6.4: a 401 from a Kiosk auth endpoint names its challenge,
Kiosk-Client-AssertionorKiosk-Session.Kiosk-Min-Clientstays 0.6.0.
Reference implementation
- Every gem and
API_VERSIONare 0.6.5, matching skill 0.6.5.MIN_CLIENTstays 0.6.0: a 0.6.4 assistant reads no new header. - Every demo and the engine default pin
skill-v0.6.5.md, which has the text of 0.6.4. - A 401 from
/auth/claim,/auth/rotate,/auth/linkor/auth/unlinknames its challenge:Kiosk-Client-AssertionorKiosk-Session. - Every demo sends a Content-Security-Policy that takes scripts and styles from its own origin only. The pages with an inline script or style load it from a file now.
- A demo landing page answers Markdown when Accept ranks
text/markdownfirst, also beside*/*, and wraps its content in<main>.
0.6.4
Specification and skill
- Skill 0.6.4, the text of 0.6.3: the OAuth metadata lists the scope
default, and an operator ignores a requestedscope.Kiosk-Min-Clientstays 0.6.0. - A 401 for an access token names the protected-resource metadata (RFC 9728), and every discovery document answers
Access-Control-Allow-Origin: *.
Reference implementation
- Every gem and
API_VERSIONare 0.6.4, matching skill 0.6.4.MIN_CLIENTstays 0.6.0: a 0.6.3 assistant sends noscopeand reads no new header. - Every demo and the engine default pin
skill-v0.6.4.md, which has the text of 0.6.3. - The OAuth metadata lists the one scope
default. An operator ignores a requestedscopeand grants the role of the account; it still refuses arole. - A 401 for an access token names the protected-resource metadata (RFC 9728), and every discovery document answers
Access-Control-Allow-Origin: *. - Refusal hints name
auth.registration_urlandauth.token_urlof kiosk.json, the endpoints that exist.
0.6.3
Specification and skill
- Skill 0.6.3: the operator counts each assistant's tolled calls and can ask a proof on any call; a new access token keeps the count.
Kiosk-Min-Clientstays 0.6.0. - The reference policy asks a proof on every Nth call, N = ceil(10 / share), share = (cap − spend) / cap; it replaces one proof per access token by chance.
Reference implementation
- Every gem and
API_VERSIONare 0.6.3, matching skill 0.6.3.MIN_CLIENTstays 0.6.0: a 0.6.2 assistant handles a 402 on any call. - Every demo and the engine default pin
skill-v0.6.3.md, which says that the operator counts the tolled calls and that a new token keeps the count. - The toll counts calls:
RecentSpendasks a proof on every Nth tolled call of an assistant, N = ceil(10 / share). It replaces one proof per access token by chance. - The seven operator demos add
kiosk.agents.calls_since_proof, each assistant's tolled calls since its last paid proof.
0.6.2
Specification and skill
- Skill 0.6.2 and every page say "protocol" or "HTTP API" for the requests and answers the specification defines. The protocol does not change;
Kiosk-Min-Clientstays 0.6.0.
Reference implementation
- Every gem and
API_VERSIONare 0.6.2, matching skill 0.6.2.MIN_CLIENTstays 0.6.0: the protocol does not change. - Every demo and the engine default pin
skill-v0.6.2.md, which says "protocol" and "HTTP API" for the requests and answers. - READMEs, comments and messages say "protocol", "HTTP API", "endpoint" or "request". The OpenAPI tag of
schemaandpayisreserved.
0.6.1
Specification and skill
- Skill 0.6.1 names every error code of the specification and the two DPoP headers, and says each answer one time. The wire does not change;
Kiosk-Min-Clientstays 0.6.0. - Both specifications publish
scopes_supported: []in the OAuth metadata, because an AI assistant requests no scope. auth.md writes each Discover URL bare. - The landing page is retold in the founder's voice: personal assistants, any service, the toll on the door, one card entry per operator, and the event stream.
Reference implementation
- Every gem and
API_VERSIONare 0.6.1, matching skill 0.6.1.MIN_CLIENTstays 0.6.0: the wire does not change. - Every demo and the engine default pin
skill-v0.6.1.md, which names every error code of the specification and the two DPoP headers. - The OAuth metadata documents carry
scopes_supported: [], because an AI assistant requests no scope./auth.mdwrites each Discover URL bare, so a scanner reads it. - kiosk-test-support drops its
kiosk-pow-equihashandpumadependencies; the host's bundle brings them. deploy/rollout.shmints the KYC broker's Ed25519PROVE_KEY_PEMwhen the box has none or an RSA one, and pins its public half in the KYC operators.- A DPoP proof or client assertion whose
algis not the algorithm of its key is refused with a message that names that algorithm. deploy/deploy.shrunsrollout.shbefore the push, so an incomplete env file stops the deploy before the new code boots.
0.6.0
Specification and skill
- Skill 0.6.0 opens protocol 0.6: it makes an Ed25519 key and signs every JWT EdDSA.
Kiosk-Min-Clientis 0.6.0: a 0.5 cut registers an RSA key, which is refused. - Both specifications move every signature from RSA to Ed25519 (EdDSA, RFC 8037). An operator may also accept ES256 from a P-256 assistant key.
- A checkout carries a random 128-bit
jti, because an EdDSA signature is the same for the same claims (AP2 v0.2). - The auth, mandate and KYC schemas and their examples describe Ed25519 and P-256 keys and EdDSA signatures; an RSA JWK is a rejected example.
- The landing page and the onboarding guide make an Ed25519 key; the onboarding scripts sign with Ruby's OpenSSL alone.
Reference implementation
- Every gem and
API_VERSIONare 0.6.0, matching skill 0.6.0, and every inter-gem constraint is~> 0.6.0.MIN_CLIENTis 0.6.0: a 0.5 skill registers an RSA key, which is refused. - Every demo and the engine default pin
skill-v0.6.0.md, which makes an Ed25519 key and signs every JWT EdDSA. - Kiosk signs and verifies with Ed25519 (EdDSA, RFC 8037) and removes RSA. An operator can also accept ES256 from P-256 assistant keys.
- A checkout carries a random 128-bit
jti, because an EdDSA signature is the same for the same claims. - The KYC broker demo signs its attestations EdDSA with an Ed25519 key, and the operators that trust it read an Ed25519 public key.
deploy/rollout.shmints an Ed25519 signing key where the box holds another type, and names a broker key that is not Ed25519 as missing.- The e2e harness accepts ES256 too: a P-256 key registers and calls, and an RSA key is refused at registration.
0.5.27
Specification and skill
- Skill 0.5.27 registers its key as an OAuth client and gets each token with a client assertion and a DPoP proof.
Kiosk-Min-Clientis 0.5.27: an older cut cannot register. - Both specifications move registration and tokens onto OAuth: RFC 7591 registration, RFC 7523 client assertions and the
client_credentialsgrant, with DPoP on each token request. - The challenge, register, login and revoke endpoints under
/auth/are removed. - The specification lists every standard that Kiosk follows and what it uses each one for, and names AAuth and Web Bot Auth as related work.
- The auth, binding and discovery schemas describe the OAuth wire, and their examples follow.
- The landing page, the onboarding guide and the problem pages describe the OAuth registration and token request.
Reference implementation
- Every gem and
API_VERSIONare 0.5.27, matching skill 0.5.27.MIN_CLIENTis 0.5.27: an older skill registers and logs in at paths that are gone. - Every demo and the engine default pin
skill-v0.5.27.md, which registers on RFC 7591 and gets each token with a client assertion and DPoP. kiosk-test-supportandkiosk-redteamregister atoauth/registerand get tokens by client assertion and DPoP; their login, revoke and challenge helpers are gone.- The e2e harness registers at
oauth/register, gets tokens by client assertion and DPoP, and validates all eighteen auth and binding schema$defsagainst live bytes. deploy/live-fleet-drive.rbadds a read-only token probe: an unknown client must getinvalid_client. The README walkthrough registers at/kiosk/oauth/register.- The edge rate-limit notes name the three tolled requests before a token:
oauth/register,auth/claimand the first device-code poll atoauth/token. - Every operator demo drops
kiosk.auth_challengesby a migration, and its stories register, link and sign in again over the OAuth wire. - The engine registers assistants by RFC 7591 and gives tokens at
oauth/tokenfor an RFC 7523 client assertion and a DPoP proof; binding and rotation use the assertion too. - Removed from the engine:
GET /auth/challenge,POST /auth/register,/auth/login,/auth/revoke, and thekiosk.auth_challengestable ofkiosk:install.
0.5.26
Specification and skill
- Skill 0.5.26 keeps every file for an operator in that operator's directory, and saves the receipts of each payment in a file of their own.
Kiosk-Min-Clientstays 0.5.23. - Both specifications and the onboarding guide add RFC 9728 protected resource metadata, and the ARD manifest also at
/.well-known/ai-catalog.json. - The landing page is short: the open way in for any assistant, how it works in five steps, operators and assistants, the demos, and what ships.
Reference implementation
- Every gem and
API_VERSIONare 0.5.26, matching skill 0.5.26.MIN_CLIENTstays 0.5.23: the two new discovery documents are additive. - Every demo and the engine default pin
skill-v0.5.26.md, which keeps every file for an operator in its directory and saves each payment's receipts. - Every operator demo answers its landing page in markdown to
Accept: text/markdown, and serves/sitemap.xml, which its robots.txt names. - The engine serves RFC 9728 protected resource metadata, and the ARD manifest also at
/.well-known/ai-catalog.json, the path isitagentready.com reads. - Every deploy script that reaches the box takes the host as its first argument, or
KIOSK_DEPLOY_HOST.deploy/deploy.shpushes and rolls out.
0.5.25
Specification and skill
- Skill 0.5.25 gives the same rules in less than half the words, in tables and short lists. The wire does not change, and
Kiosk-Min-Clientstays 0.5.23.
Reference implementation
- Every gem and
API_VERSIONare 0.5.25, matching skill 0.5.25.MIN_CLIENTstays 0.5.23: the wire does not change. - Every demo and the engine default pin
skill-v0.5.25.md, which gives the same rules in less than half the words.
0.5.24
Specification and skill
- Skill 0.5.24 keeps the two receipts that
payanswers with its mandates.Kiosk-Min-Clientstays 0.5.23: a 0.5.23 assistant pays as before. - Both specifications make a successful
payanswer the AP2 v0.2 checkout and payment receipts: JWTs that the operator signs PS256, each naming its mandate. mandates.schema.jsondescribes the two receipts and their claims; its examples follow. The landing page names the receipts.
Reference implementation
- Every gem and
API_VERSIONare 0.5.24, matching skill 0.5.24.MIN_CLIENTstays 0.5.23: a 0.5.23 skill pays and ignores the receipts. - Every demo and the engine default pin
skill-v0.5.24.md, which keeps the two receipts thatpayanswers. payanswers the AP2 v0.2 checkout and payment receipts, signed PS256 by the operator. Every demo addssettlements.network_confirmation_id.- The Stripe adapter answers the card network's id of a charge,
network_confirmation_id, when the network gave one. - kiosk-test-support depends on kiosk-server and kiosk-user-idp-devise, so its in-process helpers and
Personload in a bundle that names only it.
0.5.23
Specification and skill
- Skill 0.5.23 sends a DPoP proof with each call that carries a token, and rotates a key without a new account.
Kiosk-Min-Clientis 0.5.23: an older cut sends no proof. - Both specifications bind every access token to the assistant's key (DPoP, RFC 9449): a call sends
Authorization: DPoPand aDPoPproof. - Both specifications add assistant key rotation,
POST /auth/rotate, and operator key rotation with a previous key in the JWKS. The key-rotation TODO is gone. - The card-setup
402namesKiosk-Payment-SetupinWWW-Authenticate, in place of the IETFPaymentscheme. - The listener
events/listen-v0.5.23.pysends a DPoP proof that the assistant's--proof-commandprints. The onboarding guide signs a DPoP proof for each curl call.
Reference implementation
- Every gem and
API_VERSIONare 0.5.23, matching skill 0.5.23.MIN_CLIENTis 0.5.23: an older skill sends no DPoP proof. - Every demo and the engine default pin
skill-v0.5.23.md, which sends a DPoP proof with each call and rotates a key without a new account. - Every access token is bound to the assistant's key (DPoP, RFC 9449). A call sends
Authorization: DPoPwith aDPoPproof; a token alone gets401. POST /kiosk/auth/rotateputs a new key on an assistant account. Both keys sign, and the account keeps its binding and reputation.c.previous_signing_keykeeps an operator's old key in the JWKS, so a token or checkout that it signed still verifies while the key changes.- The card-setup
402carriesWWW-Authenticate: Kiosk-Payment-Setup realm="<issuer>"in place of the IETFPaymentscheme name. - The listener in
kiosk-server/listen.pysends a DPoP proof that the assistant's--proof-commandprints; it never reads the key.
0.5.22
Specification and skill
- Skill 0.5.22 pays an operator's
checkout_jwtwith two closed AP2 v0.2 mandates, each an SD-JWT.Kiosk-Min-Clientis 0.5.22: an older cut cannot pay. - Both specifications move payment to AP2 v0.2: the operator signs a PS256 checkout, and
paytakes a closed checkout mandate and a closed payment mandate. - One checkout has at most one live pair of mandates; the mandate reference, a SHA-256 of the SD-JWT, is the idempotency key.
- The JWKS key names no
alg, because one key signs RS256 access tokens and PS256 checkouts. mandates.schema.jsondescribes the checkout, the two AP2 v0.2 mandates and the pay request; its examples follow.- The landing page and the onboarding guide describe the signed checkout and the two AP2 v0.2 mandates.
Reference implementation
- Every gem and
API_VERSIONare 0.5.22, matching skill 0.5.22.MIN_CLIENTis 0.5.22: an older skill cannot pay. - Every demo and the engine default pin
skill-v0.5.22.md, which pays a signed checkout with two AP2 v0.2 mandates. paytakes two closed AP2 v0.2 mandates, each an SD-JWT, over a checkout the operator signs PS256. The intent, cart and payment trio is gone.- One checkout has at most one live pair of mandates. Demo migration
20261021100020reshapes the mandate tables and keeps every settlement. c.checkout_price_checkerreplacesc.cart_price_checker, andKiosk::Payable#checkoutreplaces#cart. The JWKS key names noalg.- kiosk-redteam, e2e and the paying demos attack and pay the signed checkout;
wrong_currency_cartiswrong_currency_payment.
0.5.21
Specification and skill
- Skill 0.5.21 puts
version,tagsandtriggerundermetadata, as agentskills.io asks, and says near its top that the skill, solver and listener come only from kiosk.tech. - Both specifications say every skill URL an operator publishes names a cut on kiosk.tech, and an assistant refuses one on another host. The discovery schema holds the pin to it.
- agents.txt and agents.json name
x-kiosk-popand claim no AP2, no required payment and no required identity./.well-known/agent-configurationis not served. - Both specifications add RFC 8414 OAuth metadata, the agent skills index and
ard.json, and put the api-catalog's descriptions on the endpoint as RFC 9727 shows. - The onboarding guide says an operator does not put a bot challenge in front of
/kiosk/*and the discovery documents. - The onboarding guide names
Kiosk.clock_gap_noteas the stock way to tell the caller how far the place's clock is from theirs.
Reference implementation
- Every gem and
API_VERSIONare 0.5.21, matching skill 0.5.21.MIN_CLIENTstays 0.5.20: the 0.5.20 skill reads the same wire. - Every demo and the engine default pin
skill-v0.5.21.md, whose frontmatter follows agentskills.io and which names kiosk.tech as the only source of the skill. - agents.txt and agents.json name
x-kiosk-popand claim no AP2, no required payment or identity./.well-known/agent-configurationis gone; each document passes its standard's schema. - kiosk-server serves RFC 8414 OAuth metadata, the agent skills index and
ard.json, and its api-catalog follows RFC 9727. A landing page links the api-catalog. - Every operator demo's robots.txt states a Content-Signal and allows
/agents.txt. - ROADMAP.md is removed.
- RuboCop also runs the Rake plugin and the RSpec cops of rubocop-rspec, and the tree has no offense.
- ROADMAP.md drops durable token revocation and operator-push events, which ship.
Kiosk.clock_gap_notesays how far a place's clock is from the caller's declared one. hoteling, atablefor and stylish buildtimezone_noteon it.- atablefor's
availabilityrows and stylish'ssalonsrows carrytimezone_notewhen the caller's declared zone is on another clock than the place.
0.5.20
Specification and skill
- Skill 0.5.20 resolves every service time on the clock of the place where the service happens, and pays the toll on a link-code claim and on the first binding poll.
- The onboarding guide reads a getgrocery
dateon the address's clock, recommends telling the caller when the clocks differ, and names the binding toll. - Both specifications read every service time on the clock of the place where the service happens. An operator SHOULD say so when the caller's declared zone differs.
- Both specifications let an operator toll the link-code claim and the first token poll of each device code. It decides on the toll before it reads the code.
- The onboarding guide says each verb route reaches the controller action that declares the verb.
Reference implementation
- Every gem and
API_VERSIONare 0.5.20, matching skill 0.5.20.MIN_CLIENTis 0.5.20: a 0.5.19 skill reads a service time on its human's clock. - Every demo and the engine default pin
skill-v0.5.20.md, which reads a service time on the place's clock and pays the binding toll. - The link-code claim and the first token poll of each device code pay the registration toll before the code is looked up.
RegistrationPowis renamedAuthToll. - getgrocery reads a
delivery_slotsdate on the delivery address's clock.Kiosk-Timezonedoes not move it. - hoteling's
hotel_detailrow carriestimezone_notewhen the caller's declared zone is on another clock than the hotel. InProcessOriginandInProcessExecutorcall a verb through its route with a linked assistant's token;InProcessWiredoes the call.- A verb route reaches the controller action that declares the verb, and
Kiosk::Handlerruns the wire's checks around it.HandlerDispatchis removed. - kiosk-server validates an action's body against its
input_schemawith no exemption. Only a query accepts an undeclaredlimitorcursor.
0.5.19 listener 0.5.19
Specification and skill
- Skill 0.5.19 sends a declared
limitinside its range, says that a spending cap has one currency, and uses the pinned cut when it is older thanKiosk-Min-Client. - Listener 0.5.19 names an origin folder for its token file. The new pinned solver says that it has no path without numpy.
- Both specifications say a spending cap has one currency, and a
payin another currency is refused with403 spending_cap_exceeded. - Both specifications say
Kiosk-Min-Clientis the oldest skill version that works with the operator: the same MAJOR.MINOR asKiosk-API-Version, no newer, before 1.0 too. - Both specifications say a verb never declares
cursor, and a query may declarelimitas an integer to set its page size. The descriptor house style shows how. - Both specifications say the reference canonicalises a currency by lower-casing it. A currency with surrounding spaces is refused.
- The landing says the scooter demo asks for the licence category a motorcycle needs, A1 or A.
- Both specifications say skooti gates
rent_motorcycleonage_over_18and the licence category the motorcycle needs. - Both specifications say a spending cap counts each settled charge less what the operator refunded of it: a refund gives its amount back to the cap.
- Onboarding states the demos' session toll as the chance (cap − spend) / cap: certain with no recorded spend, none at the cap.
- The specification's reputation section says a keypair is unique per origin, as the formal specification and the skill do.
- The formal specification's reference note says the Stripe adapter keys a capture by the principal's Stripe customer and the cart mandate id.
Reference implementation
- Every gem and
API_VERSIONare 0.5.19, matching skill 0.5.19.MIN_CLIENTstays 0.5.18: a 0.5.18 skill still works with this release. - Every demo and the engine default pin
skill-v0.5.19.md, which sends a declaredlimitinside its range and says that a spending cap has one currency. - The listener's usage line keeps the token in the origin folder
~/.kiosk/https_shop.example_443/, as the skill says. - The solver's numpy error says that the solver has no path without numpy.
- getgrocery ships an order when the processor refuses a sold-out line's refund. The amount is credit (
credited_cents) andRails.errorgets the error. - A spending cap has a currency. kiosk-server refuses a
payin another currency with403 spending_cap_exceeded;config.spending_capreturns{ amount_cents:, currency: }. - The seven operator demos add
kiosk.agents.spending_cap_currency; an existing cap becomes a cap in euros. - The
min_clientsetting is removed from kiosk-server.Kiosk-Min-Clientandkiosk.min_clientcarry kiosk-core'sMIN_CLIENT. bin/check-version-parityholdsMIN_CLIENTto the MAJOR.MINOR ofAPI_VERSIONand no newer, before 1.0 too.- hoteling's
search_hotelsdeclareslimit, 1 to 50 with 20 as default. Alimitoutside that range is refused; it is not clamped. - kiosk-server refuses at class load a verb that declares
cursor, or alimitthat is not an integer. A declaredlimitsets the query's page size. - The
on_bad_proofsetting is removed from kiosk-server. - The mandate verifier refuses a currency with surrounding spaces, as §11.1 says. Upper case is still folded to lower case.
- skooti's
scooters_availableanswerslicence_category(A1, A or null) in place ofneeds_licence. - The kiosk-server README says a refund gives its amount back to the spending cap.
- The spend the manage-assistants page shows is the settled part of what the spending cap counts. The cap also counts the carts in flight.
- The derived OpenAPI document closes the verb and topic descriptors, as the spec does: a member the spec does not name fails validation.
- The manage-assistants page shows what each assistant spent per currency, less refunds and never below zero: the figure the spending cap counts.
- The prove broker's development callback host for skooti and getgrocery is
localhost, the host of their development issuer, sorequest_kycworks underbin/dev. - The getgrocery, skooti and hoteling READMEs start stripe-mock behind the
Kiosk::TestHelpers::StripeMockrelay, because stripe-mock alone answers each charge as unpaid. - Each skooti motorcycle names the licence category it needs, A1 or A, and
rent_motorcyclechecks its attribute. The fleet adds the A1 motorcycle MC-002. - The prove broker asks the human for a licence category (A1, A, B1, B or none) and signs the requested
licence_*attributes that category covers. Kyc.require!takes the attribute names an action needs and an optional refusaldetail.- The Prove adapter asks the broker for the
kyc_claimsnames as they are. - The prove README says the broker issues no attestation from an mDL.
- The
capabilitiesconfiguration setting is removed. Discovery always derives the list from what is registered. - The spending cap counts each settled charge less what the operator refunded of it, so a refund gives its amount back to the cap.
- The getgrocery and skooti READMEs say
request_kycanswers 500 underdocker compose up, because the app cannot reach the KYC broker from its container. - The hoteling
check_outargument ofhotel_detail,availabilityandreserve_roomsays it is a day on the hotel's own clock. - The derived OpenAPI document writes the verb and topic name
patternas^[a-z][a-z0-9_]*$, a valid ECMA-262 pattern, as JSON Schema requires. - An error a handler renders keeps the
type,title,statusanddetailof its code; the engine drops itsinstance, and itschallengesunless the code ispow_required. - The derived OpenAPI document describes the
eventsarray of theschemacatalog, so it accepts a catalog that serves topics. - A
Pagerefuses an emptynext_cursor: the last page passes nil, and the wire sends nonextlink that leads back to page one. - The engine refuses a query answer that is not a JSON array with
action_failed, paginated or not. - The Prove KYC client, the Devise session client and the test-support wire and event clients connect to an IPv6 literal URL such as
http://[::1]:3004. - The kiosk-server README says the engine keeps its own state in the
kioskschema and writes no audit record there; the audit sink is off by default.
0.5.18
Specification and skill
- Skill 0.5.18 resolves a relative
nextlink against the request URL, and does not guess the order of the rows from a cursor. - The specification page says that an AI assistant ignores unknown members in every response, as the formal specification does.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.18, matching skill 0.5.18. - Every demo and the engine default pin
skill-v0.5.18.md, which resolves a relativenextlink and does not guess the row order from a cursor.
0.5.17
Specification and skill
- Skill 0.5.17 polls
payment_setupon the schedule its description states, ignores unknown members in every answer, and says that only an operator with no roles omitsscope. - The landing says one assistant key per origin (scheme, host and port), as the skill does, and names Python for the event listener.
- An operator keys its processor capture by the principal together with the cart mandate id.
- The specification's reputation section names recent spend as the reference input, and its refunds TODO names only a refund or cancellation that an assistant asks for.
- The onboarding guide keeps the money in
payment_status, shows how to refund, and predicts theeventscapability. - The landing names the event listener beside the solver, about 20 ms to verify a proof, and the visits that count as spend.
POST /kiosk/agents/kycrefuses with403 forbiddenan attestation the operator's KYC provider does not accept, as the provider callback does.- Money that the operator refunds is not recent spend, so it does not lower the chance to pay the proof-of-work toll.
- The reference reputation policy draws once for each customer in each access-token lifetime, so a new token in the same lifetime meets the same outcome.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.17, matching skill 0.5.17. - Every demo and the engine default pin
skill-v0.5.17.md, which pollspayment_setupon its description's schedule and ignores unknown members in every answer. - The PoW READMEs give measured memory: an Equihash verify holds under 3 KB live, and the bundled Cuckatoo solver needs ~59 GiB at edgebits=29.
- The verify cost reads as measured: ~22 ms for a valid Equihash proof at n=168 k=7 in pure Ruby, ~15 ms with YJIT; the deploy runbook's figures follow it.
- The kiosk-server README renders again, and its Configure example names every core key; the gem READMEs say what the code does.
- The demo READMEs give the toll per access token, start stripe-mock before a payment by hand, and name the controller that holds each verb.
- kiosk-pay-stripe keys a capture by the Stripe customer and the cart mandate id, so the carts of two customers cannot share an idempotency key.
- The
kyc_requiredhint names the attributes it asks for, as the specification says. - The skooti red-team battery scores a local beat whose setup step failed as UNTESTED and prints its summary, with or without a stripe-mock.
- kiosk-redteam scores an attack that a failed setup step or an unreachable payment processor kept from its gate as UNTESTED, not BREACH. The run still fails.
refundtakeskey:, which names the refund among the refunds of its charge; kiosk-pay-stripe keys the Stripe request by the charge and that key.- A
kyc_providerwith emptykyc_claimsraisesConfigurationErrorat boot: no verification could grant a gated action. PaymentClaimtakes nostatus_column:orunpaid:(the column ispayment_status), andColumnSpendingCap.newtakes no arguments.- The kiosk-server README tells the operator to set the status column to
refundedafter a full refund, before the announcement. - A broker answer that is not HTTP raises
Unavailable, sorequest_kycanswersaction_failed, not 500. PaymentFailedtakes noreason:; the engine reads only its message andretryable?.AgentIdentityProviders::Basedeclares onlyverify; the engine never calledissueoragent_payment_keyon a configuredc.agent_idp.- A
reputation_policytoll with string keys is aConfigurationError; the toll readsalg,paramsandcountas symbols, as the policy contract states. RecentSpendtolls every call of a caller whose token has noiatorexp, so a retry cannot fish for a free draw.Kiosk::Reputation::Policies::RateAndReputationis removed;Factorskeeps only the two purchase fields the engine fills.- The kiosk-reputation README configures
RecentSpendand states that the engine givesRateAndReputationno factors and keeps no count of wrong proofs. Kiosk::Reputation::Policies::Backoff,BackoffStoreand the gate'son_proof_verifiedpolicy hook are removed.POST /kiosk/agents/kycrefuses with 403 an attestation the configuredkyc_providerdoes not accept, as the provider callback does.- getgrocery's back office shows a cancelled, refunded order as REFUNDED: the badge reads the order's
payment_state. - Recent spend counts a settlement less what the operator refunded of it.
kiosk:installemitscreate_kiosk_refunds; every demo ships it. - The recent-spend policy draws once for each customer in each token lifetime: a new access token does not draw again.
- The recent-spend policy draws once per access token: a session pays the toll with the chance its recent spend sets.
0.5.16
Specification and skill
- Skill 0.5.16 lists a topic's
description,payload_schemaandexamplesamong the operator-written strings that an assistant reads as data. - The published JSON Schemas' long
descriptions are one to three short sentences; the rules stay in the specification. - The onboarding guide sends the
rel="kiosk"Link header withinclude Kiosk::SkillLink. - An assistant's RSA key is 2048 to 4096 bits; an operator refuses any other key with
400 bad_request.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.16, matching skill 0.5.16. - Every demo and the engine default pin
skill-v0.5.16.md, which lists a topic's text among the operator-written strings an assistant reads as data. Kiosk::UserIdentityProviders::DeviseSession#sign_out!is removed.Kiosk::Pow::Equihash.solver_pathis removed; a client solves withKiosk::Pow::Equihash.solve.Kiosk::Redteam::Scenario.newtakes onlyname:;categoryanddescriptionare removed.Kiosk::Redteam::Runner#all_blocked?,#breachesand#blockedare removed;Battery#report!is the exit status.- kiosk-redteam reads a query answer only as a bare array; ForgedUserId scores BREACH when the owner's query answers no rows array.
Kiosk::Server::ActionEvent#ok?and#error?are removed; readstatus.Kiosk::UuidCheck::JSON_SCHEMA_PATTERNis removed.Kiosk::Identity#agent?,#human?and#service?are removed; readactor.bin/check-solver-pinrefuses every argument with exit 2; it has no self-test.- Each demo's red-team battery moves from
script/redteam_suite.rbtokiosk-redteam/suites/<demo>.rb; CI runs it in the demo with the demo's bundle. - The KYC broker demo's tests run on Minitest (
bin/rails test), as six of the other demos' do. - The vendored JSON Schemas carry kiosk.tech's shortened
descriptions. bin/check-vendored-schemasholds each JSON Schema copied from kiosk.tech equal to the published file; CI runs it beside kiosk.tech.- The Stripe adapter keeps its key and URL in its own client; the demos pass stripe-mock as
api_base:. Apaymakes three Stripe calls, not five. - The KYC broker demo reads the intake from the request parameters; a body that is not JSON is a 400.
- getgrocery keeps the money in
orders.payment_statusand the basket instatus; a full refund readsrefundedand is announced on thepaymenttopic. - skooti derives
needs_licencefromkind: a motorcycle needs a licence. Thescooters.needs_licencecolumn goes; the wire field stays. Kiosk::SkillLinksends therel="kiosk"Link header; the seven demos include it in place of three hand-written spellings.- The KYC broker demo posts a callback over https, or over http only to a loopback host; its intake answer carries the stored
expires_at. - kiosk-server refuses an RSA key over 4096 bits, and
payanswers 401 before it reads the body. - kiosk-server drops its
solid_cabledependency; each demo's Gemfile declaressolid_cablefor itsconfig/cable.yml. - The payment provider port declares
refund, which the Stripe adapter andPaymentClaimalready answer. - kiosk-server's boot, declaration and currency refusal messages are one or two sentences: the fault and the fix.
- hoteling drops the unread
bookings.decision_due_atandrefunded_atcolumns; the property answers 180 seconds after the capture. - philslist and atablefor call their models straight from the controller where an operation was one line; the four wrapper classes are gone.
Kiosk.reader_zoneanswers the caller'sKiosk-Timezonezone; philslist, tudu and getgrocery read it there.- Every demo's wire handler controllers inherit
ActionController::API, as the kiosk-server README examples now show. - kiosk-pow is the base of the PoW backends: one shared BLAKE2b-256 and a guide to add a backend. The Argon2id backend moves to kiosk-pow-argon2id.
c.after_paymentruns after the engine records the settlement, so getgrocery refunds a sold-out line against the charge; the test Stripe front refuses a refund with no charge.
0.5.15
Specification and skill
- Skill 0.5.15 signs the
cartthat an action answers, readspayment_state, and listens on thepaymenttopic. - The specification defines the
cartan action answers, thepayment_statea per-user query gives, and thepaymenttopic; onboarding usesKiosk::Payable. - Onboarding creates the
stripe_customerstable withrails generate kiosk:pay_stripe:install, given the same--user-id-typeaskiosk:install. - The specification and the landing say the reference policy tolls a session with the chance
max(0, (cap - spend) / cap), linear in recent spend. - The landing says registration and each session pay the toll, recent spend lowers it, and two items are open; onboarding says the generator sets the KYC tables' user-id type.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.15, matching skill 0.5.15. - Every demo and the engine default pin
skill-v0.5.15.md, which signs thecartan action answers and readspayment_state. Kiosk::Payablegives a payable modelpayment_state,cartandsettlement, and the engine pushes thepaymenttopic; getgrocery, hoteling and skooti use them.- An action that creates a payable row answers
cart, the cart to sign, in place of the prosepay_hint. Kiosk::StoryTest#assert_refused(answer, code)asserts a wire refusal and prints the answer when it fails.- A device authorization store answers
lookup_by_device_code_hashandlookup_by_user_code_hash; a custom store renames its twofind_by_*methods. - RuboCop also runs the Rails, Minitest and RSpec Rails plugins, new cops included, and the tree has no offense.
- kiosk-test-support declares
puma;e2e/run.shmakesKIOSK_PYTHONabsolute; kiosk-demo-prove applies its layout. bin/rails g kiosk:pay_stripe:installwrites thestripe_customersmigration with its DDL in the file;CustomerRecord.create_tableis removed.- hoteling, atablefor and stylish run their jobs on Solid Queue inside Puma, so a scheduled job survives a restart.
RecentSpend.new(within:, cap:)tolls a session by chance: 1 with no recent spend, falling linearly to 0 atcapwhole currency units. Every demo setscap: 100.- The kiosk-user-idp-devise README names the human-side gate and the link endpoints as the adapter's callers; the kiosk-reputation README says kiosk-pow-equihash registers itself.
- atablefor and stylish record a kept booking as a
Kiosk::Visitonce its time has passed, so a returning customer's toll falls without any payment.
0.5.14 listener 0.5.14
Specification and skill
- Skill 0.5.14 is written in Simplified Technical English, sends the same chain again after a lost or unknown payment, and pays the toll once per access token.
- Listener 0.5.14 shows
--topic order_delivery:<order-id>in its usage line, a topic the demos publish. - The landing and onboarding say the demos toll once per session at full strength, and a customer who spent there recently pays none.
- The proof-of-work toll is paid once per access token, one hour by default, and an operator MAY lower it by reputation to no proof at all.
- The spending cap is exact: an operator serialises one assistant's payments, counts every unfinished cart, and stops counting a declined charge at once.
- A repeat of the same unfinished
paychain is charged again under the same key and settled, not refused;409 conflictnow means a declined chain or a reused mandate id. - Onboarding's sample initializer drops
user_id_column, which repeats the default, and no longer lists a reservations table. - Onboarding configures
PaymentClaimwith the payable model (model: "Order") instead of a table name, as the reference now takes it. - The landing says an owner's linked assistant sees every salon booking with its price; stylish no longer answers a separate revenue forecast row.
- The wire is bearer-only: the MAY that let an operator's own sign-in session call a wire verb is gone.
auth/linkandauth/unlinkMUST refuse a request that is notapplication/json, so a page on another origin cannot use the human's session.- The reference keeps revocation watermarks in a database table and holds the spending cap under concurrent pays; onboarding no longer says a session cookie reaches the wire.
- Onboarding renders each answer with a Jbuilder view, as getgrocery now does, and quotes its current controllers, views and operations.
- Onboarding teaches getgrocery as it now stands: controllers by resource, orders take a window's
slot_at, validation lives in the models, topics carry examples. - A topic descriptor may carry
examples, example payloads that satisfy itspayload_schema.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.14, matching skill 0.5.14. - Every demo and the engine default pin
skill-v0.5.14.md, written in Simplified Technical English, which repeats a lost payment with the same chain. - Every operator demo tolls once per access token with
RecentSpend: a customer who spent 100 euros there within a week pays no proof. - The proof-of-work toll is paid once per access token (
access_token_ttl, 3600 s). Settlements and recorded visits count as recent spend;reputation_factorsis gone. - A repeat of an unfinished
paychain charges again under the same key and records the missing settlement. A declined charge stops counting against the spending cap at once. - Each operator demo's
db/migrateis one fresh install with the DDL written in; deployed demo databases are dropped and rebuilt fromdb/schema.rb. - hoteling tells the guest's assistant when an unpaid hold lapses:
booking_confirmationcarriescancelledwith reasonhold_lapsed, once. - getgrocery restocks every shelf each morning, lists sold-out items as «Sold out. Check back tomorrow.», and refunds a line a payment race left empty.
- skooti holds a vehicle 3 minutes; a paid hold whose ride has not started by then is released and refunded, and
booking_paymentsaysrefunded. - getgrocery and skooti run their jobs on Solid Queue inside Puma, so a scheduled job survives a restart.
- stylish drops the
availabilityquery;service_menualready lists every bookable service. kiosk:installwrites each migration's DDL into the file, so a generated migration no longer depends on the installed kiosk-server version.- Every demo tolls registration only, at n=168 k=7: the per-demo query, browse and hold tolls are removed, and tests require
small_equihash. - Registration proof-of-work runs at the Equihash default everywhere;
registration_pow_paramsis gone, and only test setup may pick small params. - Settings live in the gem that reads them;
system_roleandregistration_difficulty=are gone and initializers drop default lines. skooti reads its unlock key fromconfig.x. - Auth challenges and events are stored in the database by default, and every demo ships the
kiosk.auth_challengesmigration; production needs a shared cable adapter to serve events. PaymentClaimtakes the payable model by name (model: "Order") instead of a table, so ids, owner and timestamps follow the model.Kiosk::Ownedaddsowned_by.- kiosk-server loads through Zeitwerk and keeps its controllers, events channel and views under app/. The in-process test executor and conformance origin moved to kiosk-test-support.
- Suites and red-team runs reach a saved card through
payment_setupagainst stripe-mock; no demo initializer carries a test switch orStripe.api_base. - stylish's
my_appointmentslists the oldest booking first, as it says, instead of in random id order. - stylish's
salon_calendaranswers booking rows only, without the owner's forecast row; stylish spells its currencyeur, as every other demo does. - hoteling drops
confirm_booking, which wrote nothing:my_bookingscarries the hotel's answer and its confirmation code. - tudu's
remove_memberrefuses only an owner removing themselves; the last-owner count is gone. - The engine no longer creates
kiosk.reservations, which nothing used; every demo drops it with a new migration. - skooti holds a vehicle for one rider at a time, for 15 minutes or until the rental token expires; a second
reserveanswers 409conflict. - hoteling cancels a room hold left unpaid for 15 minutes and frees its nights; the property's answer to a paid booking refunds it at most once.
- getgrocery keeps the price an order was placed at, refuses an order for more than the stock, lowers stock once per paid order, and sends one courier per order.
- The spending cap counts carts whose capture may still run and serialises one assistant's pays, so two concurrent pays cannot pass the cap together.
PaymentClaimrunsafter_paymentonly for the call that moves the row to paid, so a hook never runs twice for one payment.- The proof-of-work toll checks a proof's signature, expiry and parameters before it records the challenge as spent, and reads no more proofs than it asked for.
- Token revocation is stored in the new
kiosk.revocationstable by default, so a revoke or an unlink holds in every process and after a restart; runkiosk:install's new migration. - The wire and the event stream accept only a bearer token, never the operator's session cookie;
auth/linkandauth/unlinkrefuse a request that is notapplication/json. - Demo lockfiles take the patched crass, loofah, mail, nokogiri, rails-html-sanitizer and websocket-driver releases.
bin/lintand CI run RuboCop with the default cops over the whole tree, bundler-audit over every lockfile and Brakeman over every demo.- Public methods nothing called are removed from kiosk-core, kiosk-server, kiosk-redteam and kiosk-test-support; hoteling drops two environment overrides nothing set.
- Every demo renders its query and action answers with Jbuilder views; getgrocery no longer declares
order_payment, a topic nothing emitted. - A handler may answer through its JSON template, whatever the caller's
Accept, andrender_kiosk_pagepages what that template renders. - Demo controllers are grouped by resource with short descriptions; getgrocery and atablefor verbs take the slot's or seating's instant in place of an id and a date.
- Topics publish
examplesin the schema document, as verbs publishexample_paramsandexample_row. - Demo tooling that demonstrated nothing is gone: getgrocery's reconcile task,
Stripe::ChargeLookupand the curl tours; red-team suites bind through kiosk-test-support.
0.5.13
Specification and skill
- Skill 0.5.13 asks how much it may spend without asking: once a card is saved the assistant keeps the human's amount and asks before any payment over it.
- The landing says where things live: the card at Stripe, the assistant's keys with the assistant, kiosk.tech static with no server, the demos only examples.
- Onboarding: Step 3 names queries and actions in a
kiosk do … endblock inconfig/routes.rb; the separateconfig/routes/kiosk.rbis gone. - Specification: an operator MAY answer a JSON caller at its human sign-in and sign-out pages with a JSON pointer to its /.well-known/kiosk.json.
- The landing no longer claims a run against real Stripe; the site points at getgrocery's tests where it named its deleted walkthrough.
- Onboarding: the initializer sets
c.signing_keyfromKIOSK_SIGNING_KEY_B64itself; the engine no longer reads it. - Onboarding: handler controllers go in
app/controllers/kiosk/and register themselves; thec.handlersline is gone.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.13, matching skill 0.5.13. - Every demo and the engine default pin
skill-v0.5.13.md, whose assistant asks the human how much it may spend without asking. - Operators route with a
kiosk do … endblock inconfig/routes.rb: it mounts the engine, then drawsqueriesas GET andactionsas POST;config/routes/kiosk.rbis gone. - hoteling checks stays in its models: a reservation holds at most 30 nights and a rate is at most €10,000, so the oversized-total refusal is gone.
- getgrocery checks orders in its models: a cart is at most 50 lines of at most 99 each, so the oversized-total refusal is gone.
- atablefor drops the table deposit and checks bookings in its models:
availabilityrows losedeposit_eur, and a party is at most 20. - A failed model validation answers 400
bad_requestnaming what is wrong:RecordInvalidandActiveModel::ValidationErrorcarry their full messages to the wire. - Every gem requires Ruby 4.0, the version CI runs; the Ruby 3.2 CI leg is gone.
- The
bin/check-*scripts check code, not prose: the prose-count, red-team-header and other prose rules are gone; each script now says only what it checks. - kiosk-user-idp-devise answers a JSON caller at the human sign-in and sign-out pages with a pointer to the wire; the demos include it instead of carrying copies.
- Demo tests load no red-team code: they drive the wire with kiosk-test-support's
Assistantand stand in for the KYC provider; the attack batteries run on their own. - A demo resets with
bin/rails db:resetand is tested withbin/rails test; itscheck:anddemo:setuprake tasks are gone. - Every demo refuses with exceptions, keeps statuses in enums, reads its principal's rows through
own, and pays only for its own rows; its argument checks are Minitest or RSpec tests. - getgrocery refuses with exceptions, keeps order status in an enum, and reads a principal's orders through
Order.own; its argument checks are Minitest tests. - stylish validates
staff_roleon write, soUser#kiosk_roleisstaff_role || "customer". - The demos keep their schema in
db/schema.rb; they no longer use kiosk-rls, and getgrocery'scheck:rlsis gone. - No framework gem reads ENV; every demo configures Kiosk in its initializer from ENV, with development and test values in
config/local_env.rb. include Kiosk::Ownedgives a modelown, the current principal's rows; demos drop theirArel.sql("kiosk.current_user_id()")scopes.- A handler registers itself: the engine loads
app/controllers/kiosk/and registers every class includingKiosk::Handler;c.handlersis removed.
0.5.12
Specification and skill
- Skill 0.5.12 names a URL that carries a credential, like skooti's
unlock_url: hand it to the human once, as a card-setup link, and never paste the token elsewhere. - The landing's «Potentially works» tier adds OpenAI dots and Meta Muse, read off their documentation and never run.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.12, matching skill 0.5.12. - Every demo and the engine default pin
skill-v0.5.12.md. - skooti's rental verbs answer an
unlock_url: a page it serves with the token and its QR, the link an assistant hands its human.
0.5.11
Specification and skill
- Skill 0.5.11 and the spec say versions compare as integers, MAJOR then MINOR then PATCH, so an assistant holding 0.5.9 adopts a 0.5.10 pin.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.11, matching skill 0.5.11. - Every demo and the engine default pin
skill-v0.5.11.md.
0.5.10
Specification and skill
- Skill 0.5.10 tells the assistant to read
detailon a403 forbidden: it names the operator-side precondition, which the assistant meets or hands to its human. - The onboarding page's
create_orderandreschedule_deliveryrequiredelivery_date, so a booked slot is always on the day its row was shown. - The onboarding page wires Stripe with the adapter's own customer table, laid down by one migration, instead of hand-written resolver lambdas and a model.
- The hoteling demo card says it pays in Stripe test mode: hoteling and skooti now charge through kiosk-pay-stripe, no longer a stub.
known-issues.htmllists environment stumbles live assistant runs hit, outside the skill, linked from the landing footer.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.10, matching skill 0.5.10. - Every demo and the engine default pin
skill-v0.5.10.md. - skooti
start_rentalandrent_motorcyclesay the reservation must be paid first, and an unpaid one is refused with what to do: pay it, then call again. - getgrocery
create_orderandreschedule_deliveryrequiredelivery_date, so the window booked is the one thedelivery_slotsrow showed, never a defaulted tomorrow. - Demo columns every write path fills are NOT NULL: atablefor's booking table and seating, getgrocery's order window and address, skooti's scooter name, dock and position.
- Each demo's
db/migrateis one fresh install: users, thekiosk:installset, then its own tables in final form. Deployed databases are rebuilt (deploy/CHECKLIST.md§7b). - The deploy hook no longer restarts an app whose
db:migratefailed, and the push exits non-zero naming it. - kiosk-pay-stripe settles only a
succeededcard charge; demos and e2e reach that answer through kiosk-redteam's stripe-mock front. - getgrocery, hoteling and skooti configure a price checker over their own catalog; the engine checks the cart.
- kiosk-pay-stripe ships the charge lookup getgrocery kept as its own copy; the demo uses the gem's.
- hoteling, skooti and e2e pay through kiosk-pay-stripe in test mode (stripe-mock in CI); the stub PSPs are gone and a declined booking is refunded through Stripe.
0.5.9
Specification and skill
- Skill 0.5.9 says an event
idis one delivery to one identity, keys an identity per origin rather than per hostname, and calls the mandatescopean optional label. - The landing's verified tier now rests on the October 2026 Hermes run on the 0.5 wire: getgrocery's KYC, card setup and payment, and hoteling's paid booking confirmed by event.
- §11.6's reference note names the engine's
PaymentClaimas the operator half, which the paying demos now configure rather than copy. - An event
idnames one event in one identity's stream, so the same change told to two members carries two ids and is never correlated by id.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.9, matching skill 0.5.9. - Every demo and the engine default pin
skill-v0.5.9.md. - getgrocery, hoteling and skooti pay through the engine's
PaymentClaim, keeping only their cashier, instead of three copies of the claim. - getgrocery, hoteling and skooti read payment receipts through the engine's
Kiosk::Settlementinstead of their own copies of two models. - The demos render refusals with the engine's
render_kiosk_resultinstead of seven copies of their own concern. - Demos serve
public/withcache-control: no-cache, so browsers revalidate an undigested stylesheet instead of holding it for a year. - Each demo's environment files publish only the keys that demo reads; skooti's production requires its unlock key outright.
- The demos configure the PoW toll they run: getgrocery and hoteling toll with no env flag, atablefor reads only
KIOSK_POW_MODE. - tudu's
todotopic says the actor's own calls are delivered too, and hoteling'sreserve_roomno longer promises a pay-by deadline its answer never carries.
0.5.8
Specification and skill
- The skill's dual-check example writes the pinned cut as
skill-vX.Y.Z.md, so no cut ever shows an earlier cut's name as its own pin.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.8, matching skill 0.5.8. - Every demo and the engine default pin
skill-v0.5.8.md. - skooti's
pay_hintno longer asks for a line-itemskueither, and neither demo's scripts send one; the entry isqty,price_centsand the reservation or booking id.
0.5.7
Specification and skill
- The skill holds a second account on one origin in its own slot, takes a bound
user_idfrom the new token, and hands the binding page to the human unfetched. pow/solve-2ba89e902b14.pypublished and pinned: the solver without an uncalled helper, an unsourced claim and an untested mode; the old file stays served.- An unreadable
Kiosk-Timezoneis refused400with the arguments, before any proof-of-work toll is asked for. - The device verify page sends a signed-out browser to sign in and back, and answers any other caller
401naming the sign-in page.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.7, matching skill 0.5.7. - Every demo and the engine default pin
skill-v0.5.7.md. - The published solver drops an uncalled helper, an unsourced claim and an untested
--toymode; it solves and verifies exactly as before. - hoteling's
pay_hintno longer asks for a line-itemsku, which the operator never reads; the entry isqty,price_centsandbooking_id. - The heavy-toll
pow_noticepoints at the solver the Kiosk skill pins, not at this gem's bundled one. - An unreadable
Kiosk-Timezoneis refused400before the proof-of-work toll, with the arguments, rather than after a paid proof. - The binding pages'
401names the operator's sign-in URL and the page's own, so a non-browser caller can hand the human a working path. - hoteling's
my_bookingspublishespayment_state: refundedfor a booking the property declined and refunded, instead ofpaid.
0.5.6
Specification and skill
- The skill carries three assistant duties: compare a payment's currency as a code, expect a currency one operator refuses, and never read an unanswered query as isolation.
- Section 17 lists
event.schema.jsonfor the event-stream message, so the schema list covers every JSON object the wire carries. - The formal protocol makes submitting the KYC event's attestation optional, matching the narrative specification and the skill.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.6, matching skill 0.5.6. - Every demo and the engine default pin
skill-v0.5.6.md. - A malformed date or timestamp in an action body is refused with a hint naming
YYYY-MM-DDor ISO 8601, as the query channel already did. - The device-authorization call refuses an empty
roleorscopewith400 invalid_request, as it refuses any other value.
0.5.5 listener 0.5.5
Specification and skill
- The listener reads the access token from a file, never its command line, and the skill has the assistant show its key's fingerprint during binding.
- The skill describes what an assistant meets: optional KYC submit, discovery versus fixed paths, opt-in
limit, operator-chosen toll counts,action_failed. - The
kyc_requiredhint names only a KYC path the origin serves, and says verification is not available where it serves none. - Onboarding says the payment provider, not Stripe, returns the human to the payment-setup return page, matching the provider-neutral card setup.
- The specification's Flow 3 diagram names a payment provider, not Stripe, matching the provider-neutral card setup.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.5, matching skill 0.5.5. - Every demo and the engine default pin
skill-v0.5.5.md, andlisten.pyis listener 0.5.5, which reads the bearer token from a file instead of its command line. - kiosk-pay-stripe requires
customer_resolver:andcustomer_saver:; its unconfigured fallback charge mode is removed. - kiosk-pay-stripe treats a mapped Customer Stripe no longer has as no saved card, so
payment_setupandpaystop answering 500 for that principal. - An
action_faileddetail names the verb and no longer the exception class the handler raised; the operator's log still carries both. - getgrocery seeds its account holder's saved card only against stripe-mock, so a live origin no longer answers her
payment_setupwith a 500. - Every demo's seeds re-apply the seeded humans' sign-in credentials, so an account created before it had a password can sign in after the next seed.
- The seven operator demos send their production session cookie
Secure. - With neither a
kyc_providernor akyc_public_key, thekyc_requiredhint names no KYC path and says verification is not available at this origin. - A person's KYC grants and open verifications are deleted with their user row; the seven demos add the key with a new migration.
- The
payment_setupreturn page says the assistant can pay only when the provider confirms it; otherwise it says the assistant will check. - The
kyc_requiredhint namesrequest_kyconly where akyc_providerserves it, and a wrong-subject refusal names the principal each KYC path compares.
0.5.4
Specification and skill
- The skill caps open KYC checks per human, records attributes against the human for all their assistants, and names
payment_setupas the fixed path. - KYC attributes are recorded against the person, and the provider callback
POST /kiosk/kyc/callbackis specified besiderequest_kyc, with its answers and the open-verification cap. payment_setupis a fixed path of thepaymodule, provider-neutral, with a return page that pushes thepayment_setuptopic once the human finishes.- The site keeps only the check scripts that have caught defects or guard the skill cuts; five scripts and their workflows are removed.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.4, matching skill 0.5.4. - Every demo and the engine default pin
skill-v0.5.4.md: the KYC cap and attributes are the human's, andpayment_setupis a fixed path. The listener stayslisten-v0.5.3.py. - kiosk-server serves
request_kycand the KYC callback, recording attributes against the person; getgrocery and skooti drop their copies; the broker client is kiosk-kyc-prove. - kiosk-server serves
payment_setupand the provider's return page; three demos drop their copies, and the Stripe-specific return logic moves into kiosk-pay-stripe. - The repository keeps only the check scripts that have caught defects; the rest and their CI jobs are removed.
- The production boot refusal for an in-process event store now says why: a restart loses events inside the 24-hour retention window.
0.5.3 listener 0.5.3
Specification and skill
- Waking is mandatory: the listener runs as a background process the runtime tracks, and on an event the assistant acts and tells its human unasked.
- The event cursor lives in the conversation, so a rewound session resumes from the id it knew; the operator holds none, and the support for runtimes that cannot be woken is removed.
- A KYC attestation is valid for one year: the provider sets
expone year afteriat, so one recovered from the event tail is still good to submit. - Before 1.0 version parity is the full MAJOR.MINOR.PATCH of protocol, implementation and skill; from 1.0 any two patches of one MINOR interoperate.
- The specification's
Kiosk-Min-Clientexample and the discovery examples now show0.5.2, the floor every 0.5.2 origin sends.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.3, matching skill 0.5.3. - Every demo and the engine default pin
skill-v0.5.3.mdandlisten-v0.5.3.py: waking is mandatory and the event cursor lives in the conversation. - The KYC broker mints attestations valid for one year instead of one hour, so one recovered from the event tail can still be submitted.
0.5.2 listener 0.5.2
Specification and skill
- The identity check waits on the
kyc_verificationevent, which carries the signed attestation; the skill has no status verb and no poll for it. - The skill says how an event wakes an idle assistant: run the listener so it exits on the event, as a background process the runtime tracks, and restart it with
--since. - The listener leaves on
501 module_not_servedwith exit 4 instead of reporting a dead token or re-minting one for ever, and sends noOriginheader. - The skill lists the
token_expireddisconnect, and the event stream among the capabilitiesmodule_not_servedcan name. - KYC (§12.4):
request_kycopens a verification and thekyc_verificationevent delivers the signed attestation; KYC by verification implies the event stream. payment/return/is withdrawn: a card-setup flow returns the human to the operator's own origin, so nothing sent anyone to the site's page.- §4.1 states how an unserved hostname is answered: as the default origin, and a proof's
audmust be the origin a request is answered as. - One deployment MAY serve several origins, each its own operator (§4.1), replacing the one-origin constraint. Onboarding: redirect an alias, list a second business.
- §8.5.4 says what a cursor is: an event
idorhead, a non-negative integer within JSON's exact range. Any othersince, or asubjectthat is not a string, is refused. - The onboarding guide states the one-origin constraint where an operator sets the issuer, and says to redirect an alias hostname to the canonical origin rather than serve both.
- The response-shape rule names
schema's third member. It said{queries, actions}where §8.3 beside it says{queries, actions, events}, which is what an origin serves. - §8.5.4 states the rule instead of listing shapes, and no frame may get silence: an unresolvable
unsubscribeor an unreadablesinceis refused, a livesubscribere-confirmed. - The onboarding guide's list of what the mount draws now carries
/kiosk/events— the one path it serves that the page never mentioned. - §8.5.4 says what a frame the operator cannot act on gets:
reject_subscriptionwhere it names a subscription, aninvalid_requestclose where it names none. Silence was the gap. - An operator that serves no topics answers
501 module_not_servedat<endpoint>/events. Declining the events module is stated rather than left for a porter to invent. - The narrative page's
unsubscribedframe now printstopic, which the formal spec, the skill and the wire all carry. A port read it one member short. - §8.5 publishes the framing a port has to produce: the
welcomeframe, the refused-upgrade disconnect, theidentifier/messageenvelope andconfirm_subscription. - §8.5.6 tells an expired credential from a revoked one: a token that merely aged out closes with
reconnect: true, because minting another and resuming is what the assistant can do. - §16.3 now says what a caller meets when an operator validates a request body:
400 bad_requestnaming the member, never another status. - §16.1: the note that the reference cannot decline binding is gone. It can: a switch puts every binding path on
501 module_not_served, and the profile is exercised. - §8.1: an indexed
name%5B0%5D=valueis not an array either — it is a one-level object, and a400where the schema declares an array. The porter note says where decoding sits. - The onboarding page stopped demanding a
CREATE ROLEPostgres login to run a demo.demo:setupno longer creates theapp_rolegroup role;psqlon PATH is what it needs. - The narrative page's conformance checklist gains the events module, in both profiles. The formal spec carried it, and the page promises the two match item for item.
- §8.5.3: the
Authorizationheader alone authorises the stream upgrade — an operator must accept one carrying noOrigin. A browser forgery check there locks out every other stack. - §8.5.3 binds the operator too: the access token is accepted in the
Authorizationheader and nowhere else on the upgrade. The assistant-side MUST had no counterpart. - §8.5.4 now names the pinned reference listener as the client that can only subscribe in the URL, instead of arguing from one that might exist.
Reference implementation
- Every gem,
API_VERSIONandMIN_CLIENTare 0.5.2, matching skill 0.5.2: before 1.0 they all carry one version, andbin/check-version-parityholds the full number. Kiosk::Protocol::MIN_CLIENTis0.5.2, soKiosk-Min-Clientandkiosk.min_clienttell a 0.5.0 or 0.5.1 assistant it is behind a 0.5.2 origin./auth.mdnames the event stream among the modules501 module_not_servedcan mean, beside binding, payment and KYC.- Every demo and the engine default pin
skill-v0.5.2.md, the cut that waits on thekyc_verificationevent for the attestation and says how an event wakes an idle assistant. - The pinned listener leaves on
501 module_not_servedwith exit 4 rather than reporting a dead token or re-minting for ever, and sends noOriginheader. - getgrocery and skooti: the
kyc_verificationevent carries the signed attestation, andkyc_statusis removed. - getgrocery and skooti: narrowing
kyc_verificationto a request id works; the subject rule queried a column the table lacks and refused every one. - A subscription that names no
subjectis accepted on every topic and carries what was addressed to the subscriber; the subject rule answers for a named one. - Security: a revoked or expired token closes every events socket, subscribed or not, and can no longer open a subscription on one held from before.
- The Stripe return url follows the origin being served:
kiosk-pay-stripejoins areturn_url:path to it, and getgrocery passes a path. kiosk-serverships arequirements.txtforlisten.py, so every shipped python file with a third-party import has a manifest beside it.- The e2e harness serves
localhostbeside127.0.0.1as a second origin and proves one key makes two accounts and no token crosses between them. - One deployment may serve several origins, each its own operator (
c.additional_origins). Accounts, tokens, proofs and mandates made on one are refused on another. kiosk-pow-equihashships arequirements.txtfor its solver, so all three PoW backends declare their python dependency in a manifest rather than in prose.- The event stream refuses a
sincethat is not a cursor and asubjectthat is not a string. A word read as 0; an out-of-range integer opened it without its replay. - A boot warning for an
issuerthat cannot be right. Unset warns in every environment; a loopback origin only outside development and test, so local demo boots stay quiet. - The demo discovery drivers name the
schemacatalogue as it is served. Four copies printed{queries, actions}, or the retired{verbs, …}, where the origin answerseventstoo. - Three more event-stream frames are answered, not dropped. An
unsubscribenothing resolves and an unreadablesinceare refused; a duplicatesubscribeis re-confirmed. - The end-to-end harness declares its python dependencies in
e2e/requirements.txt. CI installs that file, the pre-flight's remedy names it, and a fresh clone needs no prose. - The demos' binding helper returns only what a driver reads.
BoundAssistantdropskey,pemandsession, and thesession:reuse parameter no caller passed is gone. - A frame the event stream cannot act on is now answered instead of dropped. A malformed
subscribegetsreject_subscription; a frame naming no subscription closes the socket. - An origin that declares no topic answers
501 module_not_servedat<endpoint>/events. It no longer welcomes an authenticated socket it can never stream on. - The two KYC demos stop publishing an opaque broker token as
format: "uuid", and each check task now validates the event it emitted against the schema its origin serves. - An event replay no longer hands a subscription another topic's events. The filter compared the subject alone, so a subscriber got
dataits own topic's schema rejects. - A revoked event stream is told
reconnect: falseonce. Action Cable's ownclosecarries the typed reason and the flag, so no defaultedtruefollows a millisecond later. - Security: philslist keeps the seller's contact line out of its request log. The board publishes that text; the operator need not keep a second copy.
- Security: a handler dispatch inherits the host's parameter filter. A filtered field was masked on the wire request's log line and written in full on the handler's.
- The spent
box-prep-2026-08-11.shone-shot is out ofdeploy/.rollout.shrenders every env file from its template on every run; the runbook no longer ships it. - Security: getgrocery filters the customer's postal address out of its request log. A demo-domain personal field is the operator's to name; the engine names only the wire's.
- Security: an origin no longer writes the caller's proofs and mandates into its request log. The engine filters its own field names; the three demos missing Rails' list now ship it.
- A topic may declare
description nil, which the published topic descriptor has always permitted and the engine refused. What is required is the declaration, not a truthy value. - The in-process event store stops answering
truncated: truefor a cursor one below the retained floor. The durable store never did; a complete range now reads as complete in both. - An event stream whose access token merely aged out now closes with
token_expiredandreconnect: true.revokedand its terminal flag are kept for a credential that is. - Security: the event stream now stops when a human revokes. Its re-authorisation raised every tick, so a withdrawn reach kept delivering and a revoked token kept its socket.
- The manage-assistants listing now only falls back to a spend-free query when the settlements table is absent. Every other statement error reaches the operator.
- Every demo task that boots a server, and
e2e/run.sh, now refuse to start when something already holds the port, naming the pid. They used to drive it and pass. - A database now records which MAJOR of the Kiosk schema it carries, and the engine refuses a boot two or more majors ahead of it. Crossing a major stops at it.
rails g kiosk:installnow draws the wire: it writesconfig/routes/kiosk.rbwith the engine mounted and thedraw(:kiosk)line that reaches it.- getgrocery's stuck-
payingsweep now asks Stripe: charged heals the order, not charged releases the claim, no answer keeps it.check:reconcilegates all three. - every reserved-plane JSON body is held to the object §17 publishes for it. A wrong-typed member is a 400 naming it, not a verifier raising deeper in.
- An origin can now decline account binding —
c.serve_account_binding = false— so the reference exhibits the optional-module profile the spec defines. Discovery is unchanged. - The event stream accepts an upgrade that sends no
Origin. Action Cable's browser forgery check was answering a bare 404 to every non-browser client on the advertised URL. - The README names
bin/check-prose-countsfor the four-documentbefore-after.mdset. It creditedbin/check-demo-copies, which says nothing about the count. CONTRIBUTING.md, the container file and the CI comment no longer sell aCREATE ROLEprivilege the demos stopped needing. Onlye2e/run.shstill creates a role.- The two engine event-store files say what the code does. The correction that set the retention floor and the retired row-count ceiling are in the commit instead.
- The eight demo lockfiles resolve activesupport 8.1.4 and json 3. The positional
JSON.parsecaller that made abundle update jsonfatal in a demo is gone. bin/check-refusal-assertionsandbin/check-redteam-headersread a Ruby comment as a comment. A stray delimiter in one could move where a beat's call ended.- stylish’s
salonsquery publishes each salon’s IANA zone. It was the only clock an assistant needed before booking and the only one no reachable verb carried. - Every demo and the engine default pin
skill-v0.5.1.md, the cut that asks the human’s time zone once and says how to hand over a bearer credential. - A signed-out human landing on the claim ceremony’s verify page is sent to the operator’s sign-in, as on the manage-assistants page. All seven demos now set
sign_in_path. bin/check-demo-derivationschecks the one block kind the demo narratives still carry, and refuses any other. The transcript and snippet machinery had no input left.demo:setupno longer creates anapp_rolegroup role nothing uses, so a demo needs noCREATE ROLElogin. The task is the onedb:drop … db:seedline in every demo.deploy/README.mdsays the shipped systemd unit is a reference the fleet does not run as is. The box runs asubuntuwith no hardening block; the runbook now says so.- stylish's two
slotrefusals are driven bycheck:clock_specthrough stand-ins, and the operation says the wire's own validation answers first. Nothing reached either branch. - stylish's salon-clock spec holds the per-salon zone read through a stand-in, and its header names the section its watched fail reddens. Both coverage claims were false.
- skooti's rental verbs publish the
expread back out of the token they signed. It was a second copy of the lifetime, and nothing held the two equal. - atablefor's README, seed, initializer and script headers say the seatings roll on each restaurant's own clock. Eight sentences still named the origin's zone.
- getgrocery's back office badges an order out for delivery and a delivered one by name. A delivered basket read PAID, because the badge only knew a settlement.
- getgrocery's
my_ordersdeclares all six order states as an enum, courier states included. The description named four; a delivered basket was published under none of them. bin/check-demo-copiesholds the Kiosk env block of every operator demo'sdevelopment.rbin lockstep. Only the host line and getgrocery's payment paragraph may differ.- The published onboarding guide is compared against this checkout on every push, in the CI job that already checks kiosk.tech out. Until now only the daily schedule asked.
- getgrocery's order model stops naming a
scheduledstate nothing writes. A delivery move leaves the rowrescheduled, so the one-member set beside it goes too. - getgrocery's out-of-zone refusal names the served districts instead of a range over them. The range claimed D18 and D19, which it does not deliver to.
bin/check-tls-seamnow runs in CI, and a check whose CI arm is not its bare run declares that arm in its own source. A bare local battery read a red tree as green.
0.5.1
Specification and skill
- Both specification pages now say which earlier 0.5 cut a 0.5.1 operator no longer serves, the sentence every series needs once it has more than one cut.
skill-v0.5.1.md: the human’s time zone is asked once and reused at every operator, and an operator that never sees it falls back to the service place’s clock.skill-v0.5.1.md: a bearer credential in an answer goes to the human once, never repeated or logged. The skill had a rule for relaying a URL and none for a capability.- §12 now names which id an attestation's
submust equal — the principal, not the AI assistant's own account — so the narrative page says what the formal spec already did.
0.5.0
Specification and skill
- The protocol is 0.5, and
skill-v0.5.0.mdis the renumber. The catalog's third array is REQUIRED, which a 0.4 reader refuses, so the wire now advertises the number it is. - The skill forbids promising a notification it cannot deliver. Say you will be told only while holding a foreground wait or a helper that can wake you.
- A release section names which artefact line it cut, because the skill and the pinned listener carry independent numbers.
CHANGELOG-RULE.mdhas the rule. - The landing drops operator-push events from its known TODOs — the event stream shipped — and dates the Hermes run to the 0.4 wire it ran against.
- The onboarding guide names the demo task that runs the worked flow,
rake check:shop, instead of the retiredrake demo. - Both specification pages now say the wire may still change before 1.0, which is what the landing already told a reader and what this week's event-stream change demonstrated.
Reference implementation
- The protocol is 0.5.0, and
min_clientwith it. The catalogue's REQUIREDeventsarray broke 0.4, so every gem, constraint and lock moves to the line the wire speaks. - The fleet re-pins to
skill-v0.5.0.md. All seven operator demos and the engine default moveskill_urlandskill_sha256together. - Shipped comments stop dating what the wire does by the version that introduced it.
bin/check-shipped-archaeologyR5 widened on its own when the wire moved. - 0.5.0 is the first release section every record carries. Nothing here has ever been tagged or published, so everything under
[Unreleased]is in it. - A demo task says what it is by its namespace:
check:asserts,demo:is one a person runs and reads. The barerake demowas a smoke test called a demo; it is gone. - the README taglines no longer put a database in Kiosk's headline. Kiosk is named as the protocol it is; Postgres stays where the implementation's requirements are stated.
listen.pygains a one-shot wait:--until-eventexits 0 on the first event and 5 at the deadline. A wait held in the foreground costs what the human took.- The fleet re-pins to
skill-v0.4.17.mdandevents/listen-v0.5.1.py. All seven demos and the engine default move URL and digest together. deploy/rollout.shis 65 lines and takes no arguments: it renders, keeps or mints, names what it cannot, and writes owner and mode as two literal lines.deploy/rollout.sh --applynow gives every env file to the account the deploy hook runs as, at0640, instead of preserving what it finds and warning about it.deploy/rollout.shpreserves an env file's owner and mode rather than setting them, and reports who can read it. Tightening them took the deploy hook's access away on the fleet.- The fleet re-pins to
skill-v0.4.16.md, the cut that puts the two out-of-band waits on the event stream. All seven demos and the engine default move URL and digest together. bin/check-listener-pinnow asks the ALIAS whether the current skill pins the listener, not "some published cut". A cut is immutable, so the wide question answers yes for ever.deploy/rollout.shmakesdeploy/env/*.env.exampledrive the boxes, which nothing in any repository did before. A missing secret stops the run rather than writing a blank.- An origin that declares an event topic and leaves the in-process store in place is refused at production boot. One declaring no topic is untouched.
- skooti told an assistant a verb unlocks the vehicle, on the same served document that says nothing on this wire opens a lock. Both sentences now name activation.
- There is always a default role: an origin declaring
c.rolesand noregistration_roleis refused at boot. One declaring no roles is untouched. - A rebind resolves its role the way a fresh key does — the operator's default, never the assistant's previous one. No role outlives the principal it was resolved for.
- The
json < 3.0pin comes off: activesupport 8.1.4 callsJSON.parsewith a keyword splat. The four declarations, the check that watched the condition and its CI job all go. - Entries are grouped by the release that carries them, version and date in the heading. A patch cut is as visible as a minor;
CHANGELOG-RULE.mdcarries the rules.
0.4.17
Specification and skill
skill-v0.4.17published: the skill now says HOW to wait, not only what to subscribe to. A wait blocks in the foreground; a subscription is delivered by a stored cursor.events/listen-v0.5.1.pypublished. Its--until-eventreturns on the first event instead of on the deadline, so a foreground wait costs what the human took.
0.4.16
Specification and skill
skill-v0.4.16published: an assistant is told a wait is over, not left asking. It subscribes to the event stream for the identity check and the card; polling is the fallback.- The skill pins
events/listen-v0.5.0.pyby SHA-256, the way it has always pinned the proof-of-work solver — fetch, verify, run, and never write a client of your own. - §6.3 and §5.4: role totality reaches self-registration, the one way in with no human. An operator that assigns roles at all assigns one to every AI assistant it admits.
- §6.3: the reference resolves a role-less rebind from the operator's default, not from the assistant's previous role. The forbidden mixture now costs staff their own access.
- An assistant MUST put the upgrade's token in the
Authorizationheader. With the ticket gone there is one place a credential goes, so the page says so. - Entries are grouped by the cut that carries them, version and date in the heading. A skill patch is as visible as a minor;
CHANGELOG-RULE.mdcarries the rules.