From 8f31a866523d42799058c8f252a0fa23959cfe42 Mon Sep 17 00:00:00 2001 From: turinglambdaai Date: Fri, 23 Oct 2026 11:53:35 +0800 Subject: [PATCH 1/3] feat: implement the 2026-10-auth-1 in-repo slice (free raw export, gated report, release-instant eligibility) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Raw capture export (pcap-export, Wireshark retained replay) leaves the legacy Pro gate — the free scope's raw-data access, asserted over the wire with no entitlement. The engineering report becomes the gated paid evidence surface (the old UI copy claimed Pro, but no gate existed); gate-check's export action becomes report. Update eligibility moves to the authorization decision's rule: the candidate release's authenticated published_at against the entitlement's updates_through, never the current date. A release inside the window stays stageable forever, an expired window never invalidates the installed tier, a date-only cutoff extends to that day's end, and a candidate without a readable instant fails closed. Fixed UTC boundary tests cover cutoff-1s / at-cutoff / cutoff+1s and the past-window install keeps its tier and rights. --- app/backend.rkt | 45 +++++++++++------ app/gate.rkt | 31 +++++------- support/updates.rkt | 48 +++++++++++++----- tests/backend-wire-test.rkt | 19 ++++++- tests/gate-test.rkt | 99 +++++++++++++++++++++++++++---------- 5 files changed, 167 insertions(+), 75 deletions(-) diff --git a/app/backend.rkt b/app/backend.rkt index 1f221be..a5cf383 100644 --- a/app/backend.rkt +++ b/app/backend.rkt @@ -655,17 +655,16 @@ (count (lambda (d) (hash-ref d 'is_ptp #f)) decoded))) (ok-action (length decoded)))) +;; Raw capture export is free scope (the authorization decision moves it +;; out of the legacy Pro gate); only the engineering report is gated. (define-rpc (pcap-export [path : String] : ActionResult) - (cond - [(not (eq? (gate-check 'export) #t)) (deny-pro (gate-check 'export))] - [else - (with-handlers ([exn:fail? (lambda (e) (fail-action (exn-message e)))]) - (define frames - (reverse - (for/list ([f (in-list (packet-store-snapshot app-packets 999999))]) - (packet->capture-file-frame f)))) - (write-capture-file path frames) - (ok-action (length frames)))])) + (with-handlers ([exn:fail? (lambda (e) (fail-action (exn-message e)))]) + (define frames + (reverse + (for/list ([f (in-list (packet-store-snapshot app-packets 999999))]) + (packet->capture-file-frame f)))) + (write-capture-file path frames) + (ok-action (length frames)))) (define-rpc (wireshark-launch [action : String] [iface : String] : ActionResult) (cond @@ -680,8 +679,7 @@ (log-add! app-logs 'app 'info (format "Wireshark live: iface=~a filter=~a" iface filter)) (ok-action result))] - [(not (eq? (gate-check 'export) #t)) (deny-pro (gate-check 'export))] - [else (ok-action (open-retained-in-wireshark))])) + [(string=? action "retained") (ok-action (open-retained-in-wireshark))])) (define-rpc (series-window [series-id : String] : SeriesWindow) (cond @@ -795,8 +793,13 @@ (write-diagnostic-snapshot! path snapshot) (ok-action (hasheq 'path (path->string path) 'redacted #t)))) +;; The engineering report is the paid evidence surface (PRICING.md's paid +;; scope); raw data access around it stays free. (define-rpc (report-export [path : String] [iface : String] [role : String] [reference : String] : ActionResult) - (with-handlers ([exn:fail? (lambda (e) (fail-action (exn-message e)))]) + (cond + [(not (eq? (gate-check 'report) #t)) (deny-pro (gate-check 'report))] + [else + (with-handlers ([exn:fail? (lambda (e) (fail-action (exn-message e)))]) (define nics (detect-interfaces)) (define chosen-iface (if (string=? iface "") @@ -835,7 +838,7 @@ (write-engineering-report-json! path report) (write-engineering-report-markdown! markdown-path report) (ok-action (hasheq 'json (path->string path) - 'markdown (path->string markdown-path))))) + 'markdown (path->string markdown-path))))])) (define-rpc (params-merge [domain : (Optional Int64)] @@ -930,8 +933,18 @@ (with-handlers ([exn:fail? (lambda (e) (flow-set! 'failed #:error (exn-message e)))]) - (update-prepare! 'download (unbox update-manifest-box) - #:window-open? (gate-update-window-open?))) + (define candidate (unbox update-manifest-box)) + ;; Eligibility is the candidate's authenticated release instant + ;; against the entitlement cutoff — never the current date + ;; (docs/update-contract.md). + (define published-at + (and candidate + (update-manifest-published-at + (update-candidate-manifest candidate)))) + (update-prepare! 'download candidate + #:window-open? + (gate-update-window-open? + #:release-published-at published-at))) (update-state-now)) ;; Applies what is staged, per install shape (deb: pkexec with the diff --git a/app/gate.rkt b/app/gate.rkt index d07629d..080f428 100644 --- a/app/gate.rkt +++ b/app/gate.rkt @@ -3,9 +3,10 @@ ;; Commercial tier gate built on Permit (github.com/turinglambdaai/permit) ;; ;; free — passive listening (capture+decode), offline pcap analysis, -;; simulator, config editor/preview, presets (watch free) +;; simulator, config editor/preview, presets, raw capture +;; export and Wireshark replay ;; pro — engine control (GM/slave), reference-source management, -;; unlimited packet store, pcap/report exports (drive paid) +;; unlimited packet store, engineering reports (drive paid) ;; trial — 14 days of Pro from the moment the user starts it, tracked ;; as permit local-trial state (last_seen high-water mark) ;; @@ -16,7 +17,6 @@ ;; (ins_ + 32 hex) so the vendor can issue credentials bound to it. (require json - racket/date racket/file racket/string permit @@ -178,24 +178,15 @@ (define info (unbox gate-entitlement-box)) (and info (hash-ref info 'updates-through #f))) -;; Whether staging an official update is part of what this install holds. -;; Free and trial always update; a perpetual Pro install updates through -;; its entitlement's updates_through day. -(define (iso-today) - (define d (current-date)) - (format "~a-~a-~a" - (date-year d) - (~r2 (date-month d)) - (~r2 (date-day d)))) - -(define (~r2 n) - (define s (number->string n)) - (if (< (string-length s) 2) (string-append "0" s) s)) - -(define (gate-update-window-open? [today (iso-today)]) +;; Whether staging an official update with the given authenticated +;; release instant is part of what this install holds. Free and trial +;; always update; a perpetual Pro install stages releases published at or +;; before its entitlement's updates_through — the current date plays no +;; role (see updates-window-open?). +(define (gate-update-window-open? #:release-published-at published-at) (updates-window-open? #:pro? (eq? (unbox gate-cache) 'pro) #:updates-through (gate-updates-through) - #:today today)) + #:release-published-at published-at)) ;; Action-level check; returns #t or an error message string. (define (gate-check action) @@ -203,7 +194,7 @@ #t (case action [(engine) "引擎控制(GM / 从钟)是 Pro 功能。可开始 14 天免费试用或输入许可证。"] - [(export) "报文导出与报告生成是 Pro 功能。"] + [(report) "工程报告生成是 Pro 功能。"] [(reference) "外接参考源管理是 Pro 功能。"] [else "此功能需要 Pro 许可证。"]))) diff --git a/support/updates.rkt b/support/updates.rkt index c624b33..d57c79f 100644 --- a/support/updates.rkt +++ b/support/updates.rkt @@ -59,6 +59,11 @@ update-info update-info-box update-manifest-box + ;; re-exports the start-download eligibility needs: the + ;; candidate's manifest carries the authenticated release + ;; instant the window decision consumes + update-candidate-manifest + update-manifest-published-at call-under-watchdog!) ;; ---- product facts ------------------------------------------------------------- @@ -132,21 +137,40 @@ ;; ---- the updates window (the commercial contract in code) ---------------------- ;; ;; A perpetual entitlement carries updates_through — the instant official -;; updates stop being part of the purchase. When it passes the application -;; never degrades (perpetual rights: the last obtained official version -;; keeps working); only the in-app updater stops staging newer official -;; builds, and the renewal message says why. Free and trial installs always -;; update. The end day itself counts as entitled (date-prefix comparison), -;; so a host east of UTC still updates on the morning the instant has -;; technically passed — a one-year window does not need sharper edges. +;; updates stop being part of the purchase. Eligibility answers one +;; question about the candidate release: was it published at or before +;; that instant? The inputs are the signed feed's authenticated +;; published_at and the entitlement's cutoff — never the current date +;; (PRICING.md's authorization decision: "Use release time and +;; updates_through for update eligibility; the current date must not +;; suddenly invalidate an acquired version"). A release inside the window +;; therefore stays stageable forever, an expired window never degrades +;; the running install (perpetual rights: the last obtained official +;; version keeps working), and the renewal message explains a refusal. +;; Free and trial installs always update. + +;; UTC RFC 3339 values in this shape ("YYYY-MM-DD" or +;; "YYYY-MM-DDTHH:MM:SSZ") compare chronologically as plain strings, so +;; no date parsing is involved. A date-only cutoff extends to that day's +;; end: the entitlement's day counts as entitled, and a host east of UTC +;; still updates on that morning — a one-year window does not need +;; sharper edges. +(define (normalize-cutoff through) + (if (= (string-length through) 10) + (string-append through "T23:59:59Z") + through)) (define (updates-window-open? #:pro? pro? #:updates-through through - #:today today) - (or (not pro?) - (not (string? through)) - (string<=? today - (substring through 0 (min 10 (string-length through)))))) + #:release-published-at published-at) + (cond + [(not pro?) #t] + [(not (string? through)) #t] + ;; An authenticated candidate without a readable instant cannot prove + ;; it is inside the window: refuse staging for gated installs + ;; (fail-closed). The rivet manifest schema always carries one. + [(not (string? published-at)) #f] + [else (string<=? published-at (normalize-cutoff through))])) ;; ---- feed location ------------------------------------------------------------- diff --git a/tests/backend-wire-test.rkt b/tests/backend-wire-test.rkt index f8dd03c..fe3d412 100644 --- a/tests/backend-wire-test.rkt +++ b/tests/backend-wire-test.rkt @@ -101,7 +101,24 @@ (define-values (stopped stop-error) (call-rpc "engine-stop")) (check-false stop-error) - (check-equal? (list-ref stopped 0) "#f")) + (check-equal? (list-ref stopped 0) "#f") + + ;; Raw-data access is free scope (the 2026-10-auth-1 decision): pcap + ;; export answers on the wire with no entitlement at all, while the + ;; engineering report refuses with need-pro before doing any work. + (define export-path (build-path scratch "free-export.pcap")) + (define-values (exported export-error) + (call-rpc "pcap-export" (path->string export-path))) + (check-false export-error) + (check-equal? (list-ref exported 0) #t) + (check-true (file-exists? export-path) "the free export wrote a capture file") + + (define report-path (build-path scratch "free-report.json")) + (define-values (report report-error) + (call-rpc "report-export" (path->string report-path) "" "" "")) + (check-false report-error) + (check-equal? (take report 2) (list #f #t)) + (check-false (file-exists? report-path) "the refused report wrote nothing")) (lambda () (kill-thread server-thread) (delete-directory/files scratch))) diff --git a/tests/gate-test.rkt b/tests/gate-test.rkt index 6d1eeec..5b38292 100644 --- a/tests/gate-test.rkt +++ b/tests/gate-test.rkt @@ -119,9 +119,11 @@ (check-equal? (hash-ref ok-info 'issuer) "test.issuer" "issuer comes from the credential") ;; ---- the updates window (the commercial contract in gate form) ---- -;; A perpetual Pro purchase carries updates_through; the window decision -;; never re-parses the file (the gate caches what the tier came from) and -;; the end day itself still counts as entitled. +;; A perpetual Pro purchase carries updates_through. Eligibility is a +;; pure function of the candidate release's authenticated instant +;; against that cutoff — the current date is not an input — so a release +;; inside the window stays stageable forever and no check date can +;; invalidate an acquired version. (define window-path (issue-to-file @@ -133,27 +135,40 @@ "updates_through surfaces from the credential") (check-true (updates-window-open? #:pro? #f #:updates-through #f - #:today "2026-10-22") + #:release-published-at "2099-01-01T00:00:00Z") "a free install always updates") (check-true (updates-window-open? #:pro? #t #:updates-through #f - #:today "2099-01-01") + #:release-published-at "2099-01-01T00:00:00Z") "a Pro entitlement without the constraint updates forever") -(check-true (updates-window-open? #:pro? #t - #:updates-through "2027-10-16T00:00:00Z" - #:today "2027-10-15") - "the day before the instant is entitled") -(check-true (updates-window-open? #:pro? #t - #:updates-through "2027-10-16T00:00:00Z" - #:today "2027-10-16") - "the end day itself is entitled") -(check-false (updates-window-open? #:pro? #t - #:updates-through "2027-10-16T00:00:00Z" - #:today "2027-10-17") - "the day after the instant is not") -(check-false (updates-window-open? #:pro? #t - #:updates-through "2026-10-22" - #:today "2026-10-23") - "a date-only constraint behaves the same") + +;; fixed UTC boundaries around the cutoff instant +(define window-cutoff "2027-10-16T00:00:00Z") +(check-true (updates-window-open? #:pro? #t #:updates-through window-cutoff + #:release-published-at "2027-10-15T23:59:59Z") + "one second before the cutoff is inside the window") +(check-true (updates-window-open? #:pro? #t #:updates-through window-cutoff + #:release-published-at window-cutoff) + "a release published exactly at the cutoff is entitled") +(check-false (updates-window-open? #:pro? #t #:updates-through window-cutoff + #:release-published-at "2027-10-16T00:00:01Z") + "one second after the cutoff is outside") +(check-false (updates-window-open? #:pro? #t #:updates-through window-cutoff + #:release-published-at #f) + "a candidate without an authenticated instant fails closed") +;; a date-only cutoff covers through the end of that day +(check-true (updates-window-open? #:pro? #t #:updates-through "2027-10-16" + #:release-published-at "2027-10-16T23:59:59Z") + "a date-only cutoff extends to that day's end") +(check-false (updates-window-open? #:pro? #t #:updates-through "2027-10-16" + #:release-published-at "2027-10-17T00:00:00Z") + "the day after a date-only cutoff is outside") +;; whatever the wall clock says, the same inputs answer the same way — +;; the decision reads no clock at all +(check-equal? (updates-window-open? #:pro? #t #:updates-through window-cutoff + #:release-published-at "2020-01-01T00:00:00Z") + (updates-window-open? #:pro? #t #:updates-through window-cutoff + #:release-published-at "2020-01-01T00:00:00Z") + "eligibility is deterministic in its two inputs") ;; The gate-level decision, driven through a real activation. The seam ;; points the gate at this test's throwaway store before the replacement @@ -166,15 +181,47 @@ (check-equal? (gate-init!) 'pro) (check-equal? (gate-updates-through) "2027-10-16T00:00:00Z" "the gate carries the window from the cached verification") - (check-false (gate-update-window-open? "2027-10-17") - "a closed window refuses official updates") - (check-true (gate-update-window-open? "2027-10-16") - "the end day still updates") + (check-false (gate-update-window-open? + #:release-published-at "2027-10-16T00:00:01Z") + "a release after the cutoff is not staged") + (check-true (gate-update-window-open? + #:release-published-at "2027-10-16T00:00:00Z") + "the cutoff-instant release still stages") + ;; An old release published inside a window that has since passed stays + ;; eligible, and the install keeps every right it had — the window + ;; gates staging, never use. + (check-true (gate-update-window-open? + #:release-published-at "2026-10-01T00:00:00Z") + "an in-window release stays eligible after the window passes") + (check-equal? (gate-tier) 'pro "the tier outlives the window") + (check-true (gate-pro?) "an expired window never invalidates the install") + (check-true (gate-check 'engine) "Pro actions keep working past the window") (gate-deactivate!) (check-equal? (gate-init!) 'free) - (check-true (gate-update-window-open? "2099-01-01") + (check-true (gate-update-window-open? + #:release-published-at "2099-01-01T00:00:00Z") "a free install updates again after deactivation")) +;; A credential whose window is already entirely past is still a valid +;; perpetual Pro entitlement: the install upgrades and keeps working. +(let ([scratch (make-temporary-file "gate-past-~a" 'directory)]) + (putenv "GPTP_STUDIO_HOME" (path->string scratch)) + (putenv "GPTP_STUDIO_TRUST_STORE" (path->string trust-store-path)) + (define past-path + (issue-to-file + (gate-payload #:updates-through "2020-01-01T00:00:00Z") + (make-temporary-file "gate-pastwin-~a.permit.json"))) + (define-values (p-act-ok? _p-subject) (gate-activate! past-path)) + (check-true p-act-ok? "a past-window credential is still a valid purchase") + (check-equal? (gate-init!) 'pro "an expired window still grants the pro tier") + (check-true (gate-check 'engine) "an expired window install keeps Pro actions") + (check-false (gate-update-window-open? + #:release-published-at "2021-01-01T00:00:00Z") + "nothing published after the cutoff stages") + (check-true (gate-update-window-open? + #:release-published-at "2019-12-31T23:59:59Z") + "anything published inside the cutoff stays eligible")) + ;; ---- expired use window ---- (define expired-path From c04e173a64484911e7a0110be2d9c4ba1afbe5bb Mon Sep 17 00:00:00 2001 From: turinglambdaai Date: Fri, 23 Oct 2026 11:53:35 +0800 Subject: [PATCH 2/3] docs: reconcile the update contract, landing page and pricing status with the decision The update contract documents the release-instant eligibility and corrects the AppImage note (a distribution format, not an update shape); the landing page gains the AppImage install card its release assets already ship; PRICING's free-scope paragraph now states the 0.4.0 implementation status instead of the pending-move caveat. --- PRICING.md | 2 +- PRICING.zh-CN.md | 2 +- docs/index.html | 4 ++++ docs/update-contract.md | 24 +++++++++++++++--------- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/PRICING.md b/PRICING.md index b10ac0a..99dc06d 100644 --- a/PRICING.md +++ b/PRICING.md @@ -18,7 +18,7 @@ Engineering value and support effort are higher than for an everyday utility. A ## Free and paid scope -Free / evaluation: Passive capture, protocol decoding, offline import, simulator, parameter previews, existing diagnostics and raw-data export are intended to be free. Moving raw capture export out of its legacy Pro gate still requires implementation; current installers must not be described as already unrestricted. Apache-2.0 source rights remain unchanged. +Free / evaluation: Passive capture, protocol decoding, offline import, simulator, parameter previews, existing diagnostics and raw-data export are intended to be free. Raw capture export (pcap export and Wireshark replay) moved out of its legacy Pro gate in 0.4.0; engineering report generation remains the gated paid evidence surface. Apache-2.0 source rights remain unchanged. Paid: Official Pro GM/slave runtime control, reference-source management, repeatable test orchestration, evidence-backed comparison reports and validated reference combinations. Seats mean named users, not network adapters, devices or concurrent sessions. Only delivered and verified capabilities may appear in an active offer. diff --git a/PRICING.zh-CN.md b/PRICING.zh-CN.md index 673f886..b5ea5bf 100644 --- a/PRICING.zh-CN.md +++ b/PRICING.zh-CN.md @@ -18,7 +18,7 @@ ## 免费与付费边界 -免费 / 评估:被动抓包、协议解码、离线导入、模拟器、参数预览、已有诊断与原始数据导出应免费。原始捕获导出从旧 Pro 门禁移到 Free 的实现尚待完成;不能据此称当前安装包已放开。Apache-2.0 源码权利不变。 +免费 / 评估:被动抓包、协议解码、离线导入、模拟器、参数预览、已有诊断与原始数据导出应免费。原始捕获导出(pcap 导出与 Wireshark 回放)自 0.4.0 起移出旧 Pro 门禁;工程报告生成保持付费证据面。Apache-2.0 源码权利不变。 付费:官方 Pro 的 GM/从钟运行控制、参考源管理、重复测试编排、带证据的比较报告与已验证参考组合;席位是命名用户,不是网卡数/设备数/并发数。仅交付并验证的能力可列在正式 offer。 diff --git a/docs/index.html b/docs/index.html index be79f86..41bfb2f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -116,6 +116,10 @@

Self-contained; installs nothing outside its directory.

+

AppImage

curl -LO https://github.com/turinglambdaai/gptp-studio/releases/latest/download/gptp-studio-linux-x64.AppImage
+chmod +x gptp-studio-linux-x64.AppImage
+./gptp-studio-linux-x64.AppImage --doctor
+./gptp-studio-linux-x64.AppImage

Bundles its GTK4 dependency closure; no install step.

Open latest release diff --git a/docs/update-contract.md b/docs/update-contract.md index 7599913..be5fb71 100644 --- a/docs/update-contract.md +++ b/docs/update-contract.md @@ -23,13 +23,17 @@ (state: idle | downloading | staged | applying | applied | failed) and shows progress with the signed percent. - **The updates window gates staging, not use**: Free and trial installs - update always; a perpetual Pro install updates through its entitlement's - `updates_through` day (PRICING.md's "含 1 年官方更新"). When the window - has passed, `start-download` refuses before any network leg and the flow - lands on `failed` with the renewal message — the customer's version - keeps working forever (perpetual rights, `docs/licensing-model.md`). - A newer version is still reported honestly: the pill stays, the refusal - names the renewal. + update always; a perpetual Pro install stages exactly the releases its + entitlement covers. Eligibility is a pure function of the candidate + release's authenticated `published_at` (the signed feed) against the + entitlement's `updates_through` — the current date is not an input, so + a release published inside the window stays stageable forever no + matter when the customer checks (PRICING.md's authorization decision). + A release published after the cutoff is refused at `start-download` + before any network leg and the flow lands on `failed` with the renewal + message — the customer's installed version keeps working forever + (perpetual rights, `docs/licensing-model.md`). A newer version is + still reported honestly: the pill stays, the refusal names the renewal. - **Bounded transfer**: a stalled download surfaces a retryable error after a finite timeout (no byte progress for 120 s, wall clock capped at 30 min); a slow or stalled manifest fetch is bounded the same way (60 s). @@ -52,8 +56,10 @@ | relocatable tarball (everything else) | `linux/` `targz` entry | same stage dir | in-place swap: extract to a temp dir beside the stage, rename the running payload root to `.old`, move the extracted payload in, chmod the launch executables with the integer mode `#o755`; a failed swap restores `.old` so the previous install always stays runnable; the host then offers `重启应用 / Quit to Restart` | The arm64 feed entries are the same shapes (`gptp-studio--linux-arm64.*` -assets); `pick-artifact` keys on the running architecture. There is no -AppImage (deliberately not ported) and no Windows/macOS channel. +assets); `pick-artifact` keys on the running architecture. The AppImage +is a distribution format, not an update shape: its users update by +re-downloading from Releases (the in-app updater serves deb and tarball +shapes only). There is no Windows/macOS channel. ## The feed: `update-stable.json` From 02882498224bdaf14715e023a621dbbb549c29d7 Mon Sep 17 00:00:00 2001 From: turinglambdaai Date: Fri, 23 Oct 2026 11:53:35 +0800 Subject: [PATCH 3/3] chore: release 0.4.0 --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ app/state.rkt | 2 +- info.rkt | 2 +- rivet.rktd | 2 +- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abb0a88..110e8a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # 更新日志 / Changelog +## 0.4.0 - 2026-10-23 + +**授权决策 `2026-10-auth-1` 落地:原始数据导出回归免费,更新资格改由 +发布时刻判定。**(issue #35 的仓内切片) + +### Added + +- **原始数据导出移出旧 Pro 门禁**:`pcap-export` 与 Wireshark 回放 + (retained)不再要求授权——免费层即可导出自己的捕获数据(PRICING + 免费范围的「raw-data export」落地)。工程报告生成(`report-export`) + 成为付费证据面:按付费范围的「带证据的报告」补上门禁(旧 UI 文案 + 一直宣称 Pro,但实现从未门禁)。 +- **更新资格 = 发布时刻语义**:候选版本的认证 `published_at`(签名 + feed 携带)与 entitlement 的 `updates_through` 判定,**当前日期不再 + 是输入**——窗口内的版本永远可获取,窗口过期不使已装版本失效、 + 不降级、不影响启动。此前按「今天 vs 窗口」判定,截止日次日连窗口 + 内发布的最后一个版本也无法下载。日期粒度的截止扩展到当日结束; + 无可读发布时刻的候选对受门禁安装一律拒绝(fail-closed)。 + `docs/update-contract.md` 记录契约;固定 UTC 边界测试覆盖 + 截止前 1 秒/恰在截止/截止后 1 秒与日期粒度截止。 + +### Changed + +- 产品站(docs/index.html)补 AppImage 安装卡,对照实际发布资产; + `gate-check` 的 `export` 动作由 `report` 取代(原始导出免费后报告 + 是唯一付费导出面)。 + ## 0.3.1 - 2026-10-23 **Linux 分发面对齐 taskly 标杆 + 发布 rivet 切 pin。** 修复 v0.3.0 流水线 diff --git a/app/state.rkt b/app/state.rkt index 2232754..78a678c 100644 --- a/app/state.rkt +++ b/app/state.rkt @@ -33,7 +33,7 @@ settings-set! app-version) -(define app-version "0.3.1") +(define app-version "0.4.0") ;; ---- stores (created once) ----------------------------------------------------- diff --git a/info.rkt b/info.rkt index 3abad87..a7554c2 100644 --- a/info.rkt +++ b/info.rkt @@ -2,7 +2,7 @@ (define collection "gptp-studio") (define pkg-desc "gPTP Studio - gPTP / IEEE 802.1AS debugging workstation (Racket + Glaze)") -(define version "0.3.1") +(define version "0.4.0") (define pkg-authors '("turinglambdaai")) (define license 'Apache-2.0) diff --git a/rivet.rktd b/rivet.rktd index d570b97..960649d 100644 --- a/rivet.rktd +++ b/rivet.rktd @@ -1 +1 @@ -#hasheq((name . "gptp-studio") (display-name . "gPTP Studio") (version . "0.3.1") (build . 4) (identifier . "site.jrtx.gptpstudio") (release-channel . stable) (url-schemes . ()) (file-associations . ()) (resources . ("assets")) (linux-icon . "assets/icon.png") (linux-formats . ("deb" "rpm" "appimage")) (device-rpcs . ()) (macos-min-version . "14.0") (windows-min-version . "10.0.19041.0") (macos-icon . "assets/icon.icns") (windows-icon . "assets/icon.ico") (linux-binary-name . "gptp-studio") (backend . "app/backend.rkt") (module . "backend") (entry . "start") (protocol . 1)) +#hasheq((name . "gptp-studio") (display-name . "gPTP Studio") (version . "0.4.0") (build . 5) (identifier . "site.jrtx.gptpstudio") (release-channel . stable) (url-schemes . ()) (file-associations . ()) (resources . ("assets")) (linux-icon . "assets/icon.png") (linux-formats . ("deb" "rpm" "appimage")) (device-rpcs . ()) (macos-min-version . "14.0") (windows-min-version . "10.0.19041.0") (macos-icon . "assets/icon.icns") (windows-icon . "assets/icon.ico") (linux-binary-name . "gptp-studio") (backend . "app/backend.rkt") (module . "backend") (entry . "start") (protocol . 1))