From 74d37ee522bd1f5f560c3a960ece36c03b69bbc5 Mon Sep 17 00:00:00 2001 From: "ren.ji" Date: Mon, 12 Oct 2026 18:44:42 +0800 Subject: [PATCH] refactor: stabilize the public Racket API --- .github/workflows/ci.yml | 17 +++++ CHANGELOG.md | 6 ++ README.md | 10 ++- README.zh-CN.md | 6 +- docs/api-stability.md | 23 +++++++ docs/api-stability.zh-CN.md | 21 ++++++ rivet/main.rkt | 6 +- rivet/scribblings/rivet.scrbl | 15 ++--- rivet/types.rkt | 21 ------ tests/public-api.rkt | 123 ++++++++++++++++++++++++++++++++++ tests/schema-grammar.rkt | 15 +++++ 11 files changed, 227 insertions(+), 36 deletions(-) create mode 100644 docs/api-stability.md create mode 100644 docs/api-stability.zh-CN.md delete mode 100644 rivet/types.rkt create mode 100644 tests/public-api.rkt create mode 100644 tests/schema-grammar.rkt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f69eeca1..5c4e239c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,23 @@ concurrency: cancel-in-progress: true jobs: + racket-minimum: + name: Racket 9.0 compatibility + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Set up minimum supported Racket CS + uses: Bogdanp/setup-racket@2466913449df77df2bad149d1f2fc4e1ea4795dd # v1.15 + with: + architecture: x64 + distribution: full + variant: CS + version: '9.0' + - name: Install Rivet + run: raco pkg install --auto --no-docs --name rivet --link "$PWD" + - name: Test + run: raco test tests/ + racket: name: Racket core runs-on: ubuntu-24.04 diff --git a/CHANGELOG.md b/CHANGELOG.md index 60eb9baa..46cd5441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +- Removed the misleading `rivet-type` runtime descriptors from the pre-1.0 + public surface. Schema type identifiers are declaration grammar, while Keel + is the canonical runtime-inspection model. +- Added an exact public-export contract and a Racket 9.0 CI lane so accidental + API drift and minimum-version regressions fail before merge. + - 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 diff --git a/README.md b/README.md index df5234a2..52afcdee 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ Racket CS runs on a dedicated runtime thread. Native UI code never manipulates R The embedding model is inspired by [Noise](https://github.com/Bogdanp/Noise), but Rivet makes the runtime contract, protocol, code generation, and lifecycle cross-platform instead of Swift-first. -See [architecture](docs/architecture.md), [agent-native development](docs/agent-native.md), [protocol](docs/protocol.md), [embedding](docs/embedding.md), [typed device communication](docs/device-communication.md), the unimplemented [network device channel design](docs/network-device-channel.md), [Android architecture](docs/android.md), [project configuration](docs/configuration.md), [system services](docs/system-services.md), [Linux display policy](docs/linux-display.md), [production roadmap](docs/production-roadmap.md), [large payloads, pagination, and progress](docs/large-payloads.md), [Windows native window interop](docs/windows-native-window.md), [release and updates](docs/release-and-updates.md), [diagnostics](docs/diagnostics.md), [package verification](docs/package-verification.md), and [production signing](docs/production-signing.md) for the details. +See [architecture](docs/architecture.md), [API stability](docs/api-stability.md), [agent-native development](docs/agent-native.md), [protocol](docs/protocol.md), [embedding](docs/embedding.md), [typed device communication](docs/device-communication.md), the unimplemented [network device channel design](docs/network-device-channel.md), [Android architecture](docs/android.md), [project configuration](docs/configuration.md), [system services](docs/system-services.md), [Linux display policy](docs/linux-display.md), [production roadmap](docs/production-roadmap.md), [large payloads, pagination, and progress](docs/large-payloads.md), [Windows native window interop](docs/windows-native-window.md), [release and updates](docs/release-and-updates.md), [diagnostics](docs/diagnostics.md), [package verification](docs/package-verification.md), and [production signing](docs/production-signing.md) for the details. ## Platform status @@ -203,6 +203,10 @@ For publisher-signed output, use `raco rivet package --production` with the plat Arguments and results are validated at the Racket boundary. Supported schema values currently include `String`, `Int64`, `Bool`, `Bytes`, `Void`, `Any`, `(List T)`, `(Optional T)`, named values declared with `define-record`, and closed cases declared with `define-enum`. Records generate Swift structs under `RivetTypes`, C++ structs, and Kotlin data classes; Enums generate Swift raw-value enums under `RivetTypes`, C++ `enum class` values, and Kotlin `enum class` values. The Swift namespace prevents domain names such as `Task` and `Result` from shadowing standard-library types across the application module. +These type names form Rivet's declaration grammar; they are not runtime Racket +values. Runtime tooling should inspect the immutable Keel schema returned by +`current-backend-app-schema` rather than depending on a second descriptor API. + ### Events ```racket @@ -332,6 +336,10 @@ The maintained Taskboard also has platform-native [UI end-to-end gates](docs/nat - **Publisher credentials remain application-specific** — Rivet automates Authenticode and Developer ID/notarization flows, but certificates, PFX passwords, and Apple notary profiles are intentionally supplied by the application/CI environment rather than stored by Rivet. - **The public API is still pre-1.0** — protocol compatibility is versioned, but higher-level APIs may still evolve. +Maintainers should follow the [API stability policy](docs/api-stability.md); +the repository snapshots every public export and tests the full Racket suite +against the minimum supported Racket release. + ## Roadmap - [x] **Phase 1** — RVT1 protocol in Racket, C++, and Swift diff --git a/README.zh-CN.md b/README.zh-CN.md index e06ada48..4ad0f522 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -106,7 +106,7 @@ Racket CS 运行在独立 runtime 线程。原生 UI 代码不会直接操作 Ra 嵌入模型受到 [Noise](https://github.com/Bogdanp/Noise) 的启发,但 Rivet 把 runtime contract、协议、代码生成和生命周期都做成平台无关的统一核心,而不是以 Swift 为中心。 -深入设计见 [架构](docs/architecture.md)、[Agent-native 开发](docs/agent-native.md)、[协议](docs/protocol.md)、[嵌入](docs/embedding.md)、[类型安全设备通信](docs/device-communication.md)、尚未实现的[网络设备通道设计](docs/network-device-channel.md)、[Android 架构](docs/android.md)、[项目配置](docs/configuration.md)、[系统服务](docs/system-services.md)、[Linux 显示协议策略](docs/linux-display.md)、[生产晋级路线图](docs/production-roadmap.md)、[大载荷与分页](docs/large-payloads.md)、[Windows 原生窗口 interop](docs/windows-native-window.md)、[发布与更新](docs/release-and-updates.md)、[诊断](docs/diagnostics.md)、[发布物验证](docs/package-verification.md) 与 [生产签名](docs/production-signing.md)。 +深入设计见 [架构](docs/architecture.md)、[API 稳定性策略](docs/api-stability.zh-CN.md)、[Agent-native 开发](docs/agent-native.md)、[协议](docs/protocol.md)、[嵌入](docs/embedding.md)、[类型安全设备通信](docs/device-communication.md)、尚未实现的[网络设备通道设计](docs/network-device-channel.md)、[Android 架构](docs/android.md)、[项目配置](docs/configuration.md)、[系统服务](docs/system-services.md)、[Linux 显示协议策略](docs/linux-display.md)、[生产晋级路线图](docs/production-roadmap.md)、[大载荷与分页](docs/large-payloads.md)、[Windows 原生窗口 interop](docs/windows-native-window.md)、[发布与更新](docs/release-and-updates.md)、[诊断](docs/diagnostics.md)、[发布物验证](docs/package-verification.md) 与 [生产签名](docs/production-signing.md)。 ## 平台支持状态 @@ -201,6 +201,8 @@ raco rivet release 参数与返回值都会在 Racket 边界做类型校验。目前支持 `String`、`Int64`、`Bool`、`Bytes`、`Void`、`Any`、`(List T)`、`(Optional T)`、通过 `define-record` 声明的具名 Record,以及通过 `define-enum` 声明的封闭取值。Record 会生成位于 `RivetTypes` 下的 Swift struct、C++ struct 与 Kotlin data class;Enum 会生成位于 `RivetTypes` 下的 Swift raw-value enum、C++ `enum class` 与 Kotlin `enum class`。Swift 命名空间可避免 `Task`、`Result` 等领域名称在整个应用模块中遮蔽标准库类型。 +这些类型名属于 Rivet 的声明语法,并不是 Racket 运行时值。运行时工具应检查 `current-backend-app-schema` 返回的不可变 Keel schema,而不是依赖第二套 descriptor API。 + ### Event ```racket @@ -328,6 +330,8 @@ CI 会验证 Racket、C++、Swift 与 Kotlin 协议实现,在三个桌面平 - **正式发布凭据仍属于应用自身** —— Rivet 已自动化 Authenticode、Developer ID、notarization 流程,但证书、PFX 密码、Apple notary profile 会由应用/CI 环境注入,不由 Rivet 保存。 - **Public API 仍处于 pre-1.0** —— 协议有版本控制,但高层 API 仍可能继续调整。 +维护者应遵循 [API 稳定性策略](docs/api-stability.zh-CN.md):仓库会快照全部公开导出,并使用最低支持的 Racket 版本运行完整 Racket 测试套件。 + ## 路线图 - [x] **Phase 1** —— Racket / C++ / Swift 三端 RVT1 协议 diff --git a/docs/api-stability.md b/docs/api-stability.md new file mode 100644 index 00000000..dbe6676e --- /dev/null +++ b/docs/api-stability.md @@ -0,0 +1,23 @@ +# Rivet API stability + +Rivet is pre-1.0, but its public surface is deliberate. Every phase-0 export +from `rivet`, `rivet/app-info`, `rivet/backend`, `rivet/keel`, +`rivet/protocol`, `rivet/resources`, `rivet/system`, `rivet/distribution`, and +`rivet/testing` is covered by `tests/public-api.rkt`. + +An API change must therefore be explicit: + +1. change the implementation and documentation together; +2. update the public export snapshot intentionally; +3. record the migration in `CHANGELOG.md`; and +4. pass the full Racket suite on both the minimum supported Racket release and + the current development baseline. + +Before 1.0, incompatible changes are allowed when they remove a design trap or +create a materially clearer long-term contract. After 1.0, removing or changing +a public binding requires a major release. Additions remain minor releases, and +compatible fixes remain patch releases. + +Schema type identifiers such as `String`, `Int64`, `Bool`, and `(List T)` are +syntax accepted by Rivet declaration forms. They are not exported runtime +values. Tools inspect the canonical Keel `app-schema` instead. diff --git a/docs/api-stability.zh-CN.md b/docs/api-stability.zh-CN.md new file mode 100644 index 00000000..e40521ca --- /dev/null +++ b/docs/api-stability.zh-CN.md @@ -0,0 +1,21 @@ +# Rivet API 稳定性 + +Rivet 尚未到 1.0,但它的公开接口必须是有意设计的。`rivet`、 +`rivet/app-info`、`rivet/backend`、`rivet/keel`、`rivet/protocol`、 +`rivet/resources`、`rivet/system`、`rivet/distribution` 与 `rivet/testing` +的全部 phase-0 导出都由 `tests/public-api.rkt` 固定。 + +因此,API 变更必须是显式行为: + +1. 同时修改实现与文档; +2. 有意更新公开导出快照; +3. 在 `CHANGELOG.md` 记录迁移方式; +4. 使用最低支持的 Racket 版本和当前开发基线运行完整 Racket 测试套件。 + +在 1.0 之前,如果不兼容变更能够消除设计陷阱或建立明显更清晰的长期契约, +则可以接受。1.0 之后,删除或改变公开绑定需要发布 major 版本;新增兼容 API +属于 minor 版本,兼容修复属于 patch 版本。 + +`String`、`Int64`、`Bool`、`(List T)` 等 Schema 类型标识符,是 Rivet +声明形式接受的语法,并不是导出的运行时值。工具应检查规范的 Keel +`app-schema`。 diff --git a/rivet/main.rkt b/rivet/main.rkt index 4aed9e5f..be1167eb 100644 --- a/rivet/main.rkt +++ b/rivet/main.rkt @@ -4,12 +4,10 @@ "backend.rkt" "keel.rkt" "protocol.rkt" - "resources.rkt" - "types.rkt") + "resources.rkt") (provide (all-from-out "app-info.rkt") (all-from-out "backend.rkt") (all-from-out "keel.rkt") (all-from-out "protocol.rkt") - (all-from-out "resources.rkt") - (all-from-out "types.rkt")) + (all-from-out "resources.rkt")) diff --git a/rivet/scribblings/rivet.scrbl b/rivet/scribblings/rivet.scrbl index 61c4904f..9a4cc909 100644 --- a/rivet/scribblings/rivet.scrbl +++ b/rivet/scribblings/rivet.scrbl @@ -91,20 +91,17 @@ packaged dependency-closure, and clean-machine checks. @subsection{RPCs, Events, State, Records, and Enums} -@defstruct*[rivet-type ([name symbol?])]{Represents a public schema type descriptor.} - -@defthing[Void rivet-type?] -@defthing[Bool rivet-type?] -@defthing[Int64 rivet-type?] -@defthing[String rivet-type?] -@defthing[Bytes rivet-type?] -@defthing[List rivet-type?] - State descriptors and the registry records behind RPCs, Events, Records, and Enums are intentionally opaque. Applications declare them with the forms in this section; code generators consume the immutable @racket[backend-schema] snapshot instead of depending on mutable runtime representation. +The type names in declarations are schema grammar, not Racket variables or +runtime descriptor values. They are interpreted only inside the declaration +forms below. This keeps application code honest: use ordinary Racket +predicates and contracts for runtime values, and use the generated Keel schema +when a tool needs to inspect the application contract. + @defform[(define-rpc (name [arg : type] ... : result-type) body ...)]{ Defines a Racket procedure named @racket[name] and registers it as an RPC for generated Swift, C++, and Kotlin clients. Supported schema types are @racket[String], diff --git a/rivet/types.rkt b/rivet/types.rkt deleted file mode 100644 index 4d17bd89..00000000 --- a/rivet/types.rkt +++ /dev/null @@ -1,21 +0,0 @@ -#lang racket/base - -(provide (struct-out rivet-type) - Void - Bool - Int64 - String - Bytes - List) - -;; Public schema descriptors. `define-rpc` records the symbolic type names in -;; v0; these values give tools and future code generators one canonical place -;; to attach richer metadata without changing application source syntax. -(struct rivet-type (name) #:transparent) - -(define Void (rivet-type 'Void)) -(define Bool (rivet-type 'Bool)) -(define Int64 (rivet-type 'Int64)) -(define String (rivet-type 'String)) -(define Bytes (rivet-type 'Bytes)) -(define List (rivet-type 'List)) diff --git a/tests/public-api.rkt b/tests/public-api.rkt new file mode 100644 index 00000000..c573b94d --- /dev/null +++ b/tests/public-api.rkt @@ -0,0 +1,123 @@ +#lang racket/base + +(require racket/list + rackunit) + +;; This is an intentional compatibility boundary, not a documentation index. +;; Every phase-0 export is listed so that adding an accidental helper is caught +;; just as reliably as removing or renaming an existing binding. +(define expected-public-api + '((rivet + (app-build app-display-name app-identifier app-info app-info-build + app-info-display-name app-info-identifier app-info-name + app-info-release-channel app-info-version app-info? app-name + app-release-channel app-version backend-schema + backend-schema->app-schema current-app-info current-backend-app-schema + current-resource-root current-rivet-diagnostic-sink decode-value + define-enum define-event define-record define-rpc define-state + emit-event! encode-value enum-case enum-schema event-schema frame + frame-id frame-payload frame-type frame? max-frame-payload-size + max-value-depth max-value-nodes message:cancel message:error + message:event message:hello message:request message:response + message:shutdown protocol-version read-frame record-ref record-schema + resource-path resource-root rpc-schema serve serve-fds state-ref + state-schema state-set! state? struct:app-info struct:frame write-frame)) + (rivet/app-info + (app-build app-display-name app-identifier app-info app-info-build + app-info-display-name app-info-identifier app-info-name + app-info-release-channel app-info-version app-info? app-name + app-release-channel app-version current-app-info struct:app-info)) + (rivet/backend + (backend-schema current-rivet-diagnostic-sink define-enum define-event + define-record define-rpc define-state emit-event! enum-case enum-schema + event-schema record-ref record-schema rpc-schema serve serve-fds + state-ref state-schema state-set! state?)) + (rivet/keel + (backend-schema->app-schema current-backend-app-schema)) + (rivet/protocol + (decode-value encode-value frame frame-id frame-payload frame-type frame? + max-frame-payload-size max-value-depth max-value-nodes message:cancel + message:error message:event message:hello message:request + message:response message:shutdown protocol-version read-frame + struct:frame write-frame)) + (rivet/resources + (current-resource-root resource-path resource-root)) + (rivet/system + (acquire-single-instance! autostart-enabled? call-with-crash-reporting + current-rivet-crash-reporter current-rivet-log-sink + current-system-adapter install-crash-hook! make-settings-store + register-activation-handler! rivet-log secure-store-ref + secure-store-remove! secure-store-set! set-autostart! + set-settings-store-data! set-settings-store-lock! + set-settings-store-path! set-tray-menu! settings-ref settings-remove! + settings-set! settings-snapshot settings-store settings-store-data + settings-store-lock settings-store-path settings-store? + show-system-notification! struct:settings-store struct:system-adapter + system-adapter system-adapter-acquire-single-instance + system-adapter-autostart-enabled system-adapter-capabilities + system-adapter-install-crash-hook system-adapter-name + system-adapter-register-activation-handler + system-adapter-secure-store-ref system-adapter-secure-store-remove + system-adapter-secure-store-set system-adapter-set-autostart + system-adapter-set-tray-menu system-adapter-show-notification + system-adapter? system-capabilities)) + (rivet/distribution + (base64-string->bytes bytes->base64-string bytes->ed25519-public-key + channel-accepts-version? current-install-kind current-update-platform + download-update ed25519-sign ed25519-verify execute-install-plan! + execute-platform-installation! fetch-update-manifest install-plan + install-plan-backup-path install-plan-candidate + install-plan-downloaded-path install-plan-install install-plan-restart + install-plan-rollback install-plan? make-install-plan + manifest->payload-bytes manifest-schema-version payload-bytes->manifest + platform-installation platform-installation-commit + platform-installation-health-check platform-installation-plan + platform-installation? platform-installer-policy + prepare-platform-installation read-ed25519-private-key + read-ed25519-public-key read-signed-manifest recover-install-plan! + recover-platform-installation! select-update sha256-bytes sha256-file + sha256-file/hex struct:install-plan struct:platform-installation + struct:update-artifact struct:update-candidate struct:update-manifest + struct:updater-config update-artifact update-artifact-architecture + update-artifact-arguments update-artifact-installer + update-artifact-platform update-artifact-sha256 update-artifact-size + update-artifact-url update-artifact? update-candidate + update-candidate-artifact update-candidate-manifest update-candidate? + update-manifest update-manifest-application-id update-manifest-artifacts + update-manifest-build update-manifest-channel + update-manifest-minimum-version update-manifest-previous-version + update-manifest-published-at update-manifest-rollback-allowed? + update-manifest-rollout update-manifest-version update-manifest? + updater-config updater-config-application-id + updater-config-architecture updater-config-channel + updater-config-current-version updater-config-expected-key-id + updater-config-maximum-download-bytes updater-config-platform + updater-config-public-key updater-config-rollout-bucket updater-config? + valid-channel? verify-platform-payload! verify-signed-manifest + verify-update-artifact! version-compare version<=? version=? version>? version? write-signed-manifest)) + (rivet/testing + (ephemeral-http-listener fake-request fake-request-body + fake-request-headers fake-request-method fake-request-path fake-request? + serve-http! struct:fake-request)))) + +(define (phase-zero-export-names exports) + (for*/list ([phase-entry (in-list exports)] + #:when (equal? (car phase-entry) 0) + [export-entry (in-list (cdr phase-entry))]) + (car export-entry))) + +(define (public-export-names module-path) + (dynamic-require module-path #f) + (define-values (variable-exports syntax-exports) + (module->exports module-path)) + (sort (remove-duplicates + (append (phase-zero-export-names variable-exports) + (phase-zero-export-names syntax-exports))) + symbol