Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## Unreleased

- Make Keel's validated `app-schema` the canonical in-memory input to Swift,
C++, and Kotlin client generation. Rivet now keeps only a fail-closed
Keel-to-RVT1 type adapter instead of reconstructing a parallel codegen schema
model, while preserving generated source and schema snapshot compatibility.
- Synchronize the English and Chinese CI guide with the pinned Ubuntu 24.04
runner and remove a stale comment about the now-opaque State descriptor.

## 0.7.0

- Stage Racket's bundled foreign runtime libraries (`libeay32.dll`,
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ The maintained Taskboard also has platform-native [UI end-to-end gates](docs/nat
- [x] Kotlin typed-client generation
- [x] clean-runner build/package/verify evidence for every supported desktop architecture
- [x] **Phase 7 — desktop production graduation** — real Linux install/upgrade evidence, first-party update installation adapters, native UI end-to-end gates, and characterization-protected maintainability work; see the [graduation record](docs/production-roadmap.md)
- [ ] **Phase 8 — 1.0 contract stabilization** — incrementally source generated-client inputs from Keel, evaluate Float64/map/structured-error RVT1 extensions only with cross-runtime golden and fuzz evidence, and align portable distribution metadata without moving platform installer policy into Keel
- [ ] **Phase 8 — 1.0 contract stabilization** — use Keel as the canonical generated-client schema, evaluate Float64/map/structured-error RVT1 extensions only with cross-runtime golden and fuzz evidence, and align portable distribution metadata without moving platform installer policy into Keel
- [ ] **Phase 9 — first-class device application delivery** — after the Windows/macOS/Linux 1.0 line is stable, add generated iOS/iPadOS/tvOS/visionOS/watchOS and Android projects, explicit embedded-runtime or companion roles, signing, packaging, and real-device verification

## License
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ CI 会验证 Racket、C++、Swift 与 Kotlin 协议实现,在三个桌面平
- [x] Kotlin 类型客户端生成
- [x] 覆盖每个受支持桌面架构的干净 runner 构建/打包/验证证据
- [x] **Phase 7 —— 桌面生产晋级** —— 真实 Linux 安装/升级证据、一方更新安装适配器、原生 UI 端到端门禁,以及由特征测试保护的可维护性改造;详见[晋级记录](docs/production-roadmap.md)
- [ ] **Phase 8 —— 1.0 契约稳定化** —— 渐进地让生成客户端以 Keel 为规范输入;Float64、map、结构化 error 等 RVT1 扩展只有在跨 runtime golden 与 fuzz 证据齐备时才引入;对齐可移植分发元数据,但平台安装器策略不进入 Keel
- [ ] **Phase 8 —— 1.0 契约稳定化** —— 生成客户端统一以 Keel 为规范 Schema;Float64、map、结构化 error 等 RVT1 扩展只有在跨 runtime golden 与 fuzz 证据齐备时才引入;对齐可移植分发元数据,但平台安装器策略不进入 Keel
- [ ] **Phase 9 —— 一等设备应用交付** —— Windows/macOS/Linux 1.0 主线稳定之后,再提供生成式 iOS/iPadOS/tvOS/visionOS/watchOS 与 Android 项目,明确 embedded-runtime 或 companion 角色,并补齐签名、打包和真机验证

## 许可证
Expand Down
24 changes: 13 additions & 11 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,12 @@ forms, Event/State operations, `serve`/`serve-fds`, diagnostics, and schema
snapshots. The registry structs, RPC procedures, mutable State cells, locks,
and request custodians are runtime-owned implementation details.

Build tools load the application in a fresh namespace and call
`backend-schema`. That one immutable, data-only snapshot is the supported
reflection boundary; generators do not reach into runtime structs. This keeps
code generation deterministic and lets the server's concurrency machinery
evolve without turning internal representation into an accidental API.
Build tools load the application in a fresh namespace, project its immutable
`backend-schema` snapshot into a Keel `app-schema`, and use that canonical
contract as the input to code generation. Generators do not reach into runtime
structs or maintain a second schema object model. This keeps code generation
deterministic and lets the server's concurrency machinery evolve without
turning internal representation into an accidental API.

The terminal-ownership state machine for active requests lives in the private
`backend-pending` module. It owns admission capacity, exactly-once terminal
Expand Down Expand Up @@ -179,12 +180,13 @@ Typed generated clients are layered on top of this codec. The current schema sup

`rivet/keel` projects the immutable `backend-schema` snapshot into Keel's
transport-neutral `app-schema`. Keel owns the common type, schema, lifecycle,
and canonical introspection vocabulary shared with Glaze; Rivet continues to
own RVT1 framing, typed native code generation, embedded-runtime lifecycle,
and the WinUI, SwiftUI/AppKit, and GTK4/Wayland adapters. The projection does
not enable Keel's additional value tags on RVT1. RVT1 v1 Errors also remain
bounded diagnostic strings, so projected commands advertise no structured
error codes until that wire boundary changes deliberately.
canonical introspection vocabulary, and the in-memory objects consumed by
Rivet's native code generators. Rivet continues to own RVT1 framing, the
Keel-to-RVT1 type adapter, generated-language policy, embedded-runtime
lifecycle, and the WinUI, SwiftUI/AppKit, and GTK4/Wayland adapters. The
projection does not enable Keel's additional value tags on RVT1. RVT1 v1
Errors also remain bounded diagnostic strings, so projected commands advertise
no structured error codes until that wire boundary changes deliberately.

Migrating a Swift call site from the pre-namespace emission is mechanical
— qualify the type once:
Expand Down
4 changes: 2 additions & 2 deletions docs/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ uses:
- Windows: `windows-latest` for the WinUI 3 host;
- macOS: both `macos-latest` and `macos-15`, so current Swift and the pinned
Xcode 16 release environment both compile the SwiftUI/AppKit host;
- Linux: `ubuntu-latest` with GTK4 development files and an embeddable Racket
- Linux: `ubuntu-24.04` with GTK4 development files and an embeddable Racket
CS runtime.

`raco rivet build` is the required host-compilation gate. Add `package` and
Expand Down Expand Up @@ -73,7 +73,7 @@ jobs:
- run: raco rivet build

linux-host:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- name: Set up embeddable Racket CS
Expand Down
4 changes: 2 additions & 2 deletions docs/ci.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ CI 应使用与发布任务相同类别的 runner 和工具链:
- Windows:用 `windows-latest` 编译 WinUI 3 宿主;
- macOS:同时使用 `macos-latest` 与 `macos-15`,分别覆盖当前 Swift 和
固定的 Xcode 16 发布环境;
- Linux:用 `ubuntu-latest`,安装 GTK4 开发文件,并准备可嵌入的 Racket
- Linux:用 `ubuntu-24.04`,安装 GTK4 开发文件,并准备可嵌入的 Racket
CS runtime。

`raco rivet build` 是必需的宿主编译门禁。若还要验证分发布局,可继续执行
Expand Down Expand Up @@ -69,7 +69,7 @@ jobs:
- run: raco rivet build

linux-host:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- name: Set up embeddable Racket CS
Expand Down
3 changes: 2 additions & 1 deletion rivet-cli/codegen.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@

(define (generate-clients! project)
(define backend (project-path project (project-ref project 'backend)))
(define-values (rpcs events states records enums) (load-schema backend))
(define-values (rpcs events states records enums)
(load-schema backend #:id (project-identifier project)))
(define module-name (project-ref project 'module))
(define entry-name (project-ref project 'entry))
(define display-name (project-display-name project))
Expand Down
142 changes: 111 additions & 31 deletions rivet-cli/codegen/model.rkt
Original file line number Diff line number Diff line change
@@ -1,51 +1,131 @@
#lang racket/base

(require racket/list
(prefix-in keel: keel)
"../project.rkt")

(provide (struct-out schema-rpc)
(struct-out schema-event)
(struct-out schema-state)
(struct-out schema-record)
(struct-out schema-enum)
(provide schema-rpc?
schema-rpc-name
schema-rpc-arg-names
schema-rpc-arg-types
schema-rpc-result-type
schema-event?
schema-event-name
schema-event-type
schema-state?
schema-state-name
schema-state-type
schema-record?
schema-record-name
schema-record-field-names
schema-record-field-types
schema-enum?
schema-enum-name
schema-enum-cases
load-schema
schema-name
resolve-device-rpcs
upper-first
record-native-name)

(struct schema-rpc (name arg-names arg-types result-type) #:transparent)
(struct schema-event (name type) #:transparent)
(struct schema-state (name type) #:transparent)
(struct schema-record (name field-names field-types) #:transparent)
(struct schema-enum (name cases) #:transparent)
(define schema-rpc? keel:schema-command?)
(define schema-event? keel:schema-event?)
(define schema-state? keel:schema-state?)
(define schema-record? keel:schema-record?)
(define schema-enum? keel:schema-enum?)
(define-namespace-anchor model-namespace-anchor)

(define (load-schema backend)
;; Native generators still target RVT1's deliberately smaller type system.
;; Keep that transport mapping in one adapter while Keel remains the canonical
;; in-memory schema. Unsupported Keel types fail here instead of being silently
;; approximated by a generator.
(define (keel-type->rvt1-type type)
(cond
[(keel:scalar-type? type)
(case (keel:scalar-type-name type)
[(Bool Int64 String Bytes) (keel:scalar-type-name type)]
[(Unit) 'Void]
[(Value) 'Any]
[else
(raise-arguments-error 'load-schema
"Keel type is not supported by RVT1"
"type" type)])]
[(keel:list-type? type)
(list 'List (keel-type->rvt1-type (keel:list-type-element type)))]
[(keel:optional-type? type)
(list 'Optional
(keel-type->rvt1-type (keel:optional-type-element type)))]
[(keel:reference-type? type)
(string->symbol (keel:reference-type-name type))]
[else
(raise-arguments-error 'load-schema
"Keel type is not supported by RVT1"
"type" type)]))

(define (schema-rpc-name rpc)
(string->symbol (keel:schema-command-name rpc)))

(define (schema-rpc-arg-names rpc)
(for/list ([parameter (in-list (keel:schema-command-parameters rpc))])
(string->symbol (keel:schema-parameter-name parameter))))

(define (schema-rpc-arg-types rpc)
(for/list ([parameter (in-list (keel:schema-command-parameters rpc))])
(keel-type->rvt1-type (keel:schema-parameter-type parameter))))

(define (schema-rpc-result-type rpc)
(keel-type->rvt1-type (keel:schema-command-result rpc)))

(define (schema-event-name event)
(string->symbol (keel:schema-event-name event)))

(define (schema-event-type event)
(keel-type->rvt1-type (keel:schema-event-type event)))

(define (schema-state-name state)
(string->symbol (keel:schema-state-name state)))

(define (schema-state-type state)
(keel-type->rvt1-type (keel:schema-state-type state)))

(define (schema-record-name record)
(string->symbol (keel:schema-record-name record)))

(define (schema-record-field-names record)
(for/list ([field (in-list (keel:schema-record-fields record))])
(string->symbol (keel:schema-field-name field))))

(define (schema-record-field-types record)
(for/list ([field (in-list (keel:schema-record-fields record))])
(keel-type->rvt1-type (keel:schema-field-type field))))

(define (schema-enum-name enum)
(string->symbol (keel:schema-enum-name enum)))

(define (schema-enum-cases enum)
(map string->symbol (keel:schema-enum-cases enum)))

(define (load-schema backend #:id [id "rivet.codegen"])
;; Backends register declarations as a module side effect. A fresh
;; namespace keeps separate projects from sharing a registry accidentally.
;; Attach Keel from the caller namespace so the isolated Rivet instance
;; returns canonical structs recognized by these accessors; only Rivet's
;; mutable backend registry must be instantiated per project.
(define ns (make-base-namespace))
(namespace-attach-module
(namespace-anchor->namespace model-namespace-anchor)
'keel
ns)
(parameterize ([current-namespace ns])
(dynamic-require backend #f)
(define get-schema (dynamic-require 'rivet/backend 'backend-schema))
(define schema (get-schema))
(values
(for/list ([info (in-list (hash-ref schema 'rpcs))])
(define arguments (hash-ref info 'arguments))
(schema-rpc (hash-ref info 'name)
(map (lambda (argument) (hash-ref argument 'name)) arguments)
(map (lambda (argument) (hash-ref argument 'type)) arguments)
(hash-ref info 'result)))
(for/list ([info (in-list (hash-ref schema 'events))])
(schema-event (hash-ref info 'name) (hash-ref info 'type)))
(for/list ([info (in-list (hash-ref schema 'states))])
(schema-state (hash-ref info 'name) (hash-ref info 'type)))
(for/list ([info (in-list (hash-ref schema 'records))])
(define fields (hash-ref info 'fields))
(schema-record (hash-ref info 'name)
(map (lambda (field) (hash-ref field 'name)) fields)
(map (lambda (field) (hash-ref field 'type)) fields)))
(for/list ([info (in-list (hash-ref schema 'enums))])
(schema-enum (hash-ref info 'name) (hash-ref info 'cases))))))
(define current-schema
(dynamic-require 'rivet/keel 'current-backend-app-schema))
(define schema (current-schema #:id id))
(values (keel:app-schema-commands schema)
(keel:app-schema-events schema)
(keel:app-schema-states schema)
(keel:app-schema-records schema)
(keel:app-schema-enums schema))))

(define (schema-name value)
(if (symbol? value) (symbol->string value) value))
Expand Down
3 changes: 2 additions & 1 deletion rivet-cli/codegen/snapshot.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@

(define (schema-snapshot project)
(define backend (project-path project (project-ref project 'backend)))
(define-values (rpcs events states records enums) (load-schema backend))
(define-values (rpcs events states records enums)
(load-schema backend #:id (project-identifier project)))
(define device-rpcs (resolve-device-rpcs project rpcs 'schema-snapshot))
(parameterize ([current-records records]
[current-enums enums])
Expand Down
8 changes: 4 additions & 4 deletions rivet/backend.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@
;; Keeping it private lets state-set! preserve the same behavior outside serve.
(define current-state-commit-guard (make-parameter (lambda (thunk) (thunk))))

;; `state-info` is public through struct-out, so keep update-order bookkeeping
;; private instead of adding a field that would break constructor/pattern source
;; compatibility. Weak identity keys also avoid retaining externally constructed
;; State values after the application releases them.
;; Keep update-order bookkeeping outside the opaque State descriptor. Weak
;; identity keys avoid retaining State values after the application releases
;; them and let the descriptor representation evolve without widening the
;; public API.
(define state-update-locks (make-weak-hasheq))
(define state-update-locks-lock (make-semaphore 1))

Expand Down
2 changes: 1 addition & 1 deletion tests/documentation.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
(for ([expected (in-list '("windows-latest"
"macos-latest"
"macos-15"
"ubuntu-latest"
"ubuntu-24.04"
"setup-embed-racket"
"raco rivet build"))])
(check-file-contains ci-guide expected))
Expand Down
19 changes: 18 additions & 1 deletion tests/keel.rkt
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
#lang racket/base

(require rackunit
racket/runtime-path
rivet
(prefix-in keel: keel))
(prefix-in keel: keel)
(prefix-in codegen: "../rivet-cli/codegen/model.rkt"))

(define-runtime-path schema-matrix-backend
"fixtures/schema-matrix-backend.rkt")

(define-enum AccessLevel (viewer editor owner))

Expand Down Expand Up @@ -40,6 +45,18 @@
(check-equal? encoded (keel:encode-app-schema schema) "Keel introspection encoding is deterministic")
(check-equal? (keel:decode-app-value encoded) (keel:app-schema->app-value schema))

;; Code generation isolates each application's mutable Rivet registry in a
;; fresh namespace, but it must still return canonical Keel schema values from
;; the one shared Keel module instance.
(define-values (codegen-commands codegen-events codegen-states
codegen-records codegen-enums)
(codegen:load-schema schema-matrix-backend #:id "rivet.codegen-test"))
(check-true (andmap keel:schema-command? codegen-commands))
(check-true (andmap keel:schema-event? codegen-events))
(check-true (andmap keel:schema-state? codegen-states))
(check-true (andmap keel:schema-record? codegen-records))
(check-true (andmap keel:schema-enum? codegen-enums))

(check-exn #rx"Unit is valid only as a command result"
(lambda ()
(backend-schema->app-schema (hash 'rpcs
Expand Down
Loading