fix(compat): close package and v1 revision gaps

This commit is contained in:
Kinneyzhang 2026-09-01 05:57:29 +08:00
parent 35b309a1a9
commit 49bfe162ef
5 changed files with 89 additions and 8 deletions

View File

@ -112,7 +112,7 @@ There is no separate `etaf-data` install: Data is a core ETAF capability. There
## Load and verify
ECSS 0.1.0 and TP 1.0.0 are independent packages and may be installed in either order. Install both before Ebox 2.0.0, then install ETAF. ETAF consumes only the Ebox 2.0 public rendering contract.
ECSS 0.1.0 and TP 1.0.1 are independent packages and may be installed in either order. Install both before Ebox 2.0.1, then install ETAF 0.1.1. ETAF declares TP directly because Host final-accept authority uses the TP transaction contract; rendering still consumes only the Ebox 2.0 public contract.
During development, load the sibling Ebox checkout before ETAF:

View File

@ -106,7 +106,7 @@ operation 的 flat 阶段。`etaf-performance-records` 返回 operation/stage
## 加载与验证
ECSS 0.1.0 与 TP 1.0.0 是互相独立的包,安装顺序任意;两者都安装后再安装 Ebox 2.0.0,最后安装 ETAF。ETAF 只使用 Ebox 2.0 的公共渲染契约。
ECSS 0.1.0 与 TP 1.0.1 是互相独立的包,安装顺序任意;两者都安装后再安装 Ebox 2.0.1,最后安装 ETAF 0.1.1。ETAF 会直接声明 TP 依赖,因为 Host final-accept authority 使用 TP transaction contract渲染仍只使用 Ebox 2.0 的公共契约。
开发时先把同级 Ebox 检出目录加入 `load-path`

View File

@ -61,6 +61,7 @@
(tp-protocol nil :read-only t)
(initial-function nil :read-only t)
(update-function nil :read-only t)
(revision-function nil :read-only t)
(bootstrap-outcome nil :read-only t)
(provider nil :read-only t))
@ -92,6 +93,10 @@
"Return PORT's update function symbol."
(etaf-render-port--update-function port))
(defun etaf-render-port-revision-function (port)
"Return PORT's committed-revision query function symbol."
(etaf-render-port--revision-function port))
(defun etaf-render-port-bootstrap-outcome (port)
"Return PORT's immutable bootstrap outcome tag."
(etaf-render-port--bootstrap-outcome port))
@ -262,6 +267,9 @@
(defvar-local etaf-render-port--v1-cleanup-diagnostics nil
"Contained cleanup failures from the latest failed v1 initial operation.")
(defvar-local etaf-render-port--v1-committed-revision nil
"ETAF-owned revision evidence for a mounted legacy Ebox v1 surface.")
(defun etaf-render-port-v1-cleanup-diagnostics (buffer)
"Return a defensive copy of BUFFER's latest v1 cleanup diagnostics."
(and (buffer-live-p (get-buffer buffer))
@ -312,6 +320,26 @@
((error quit)
(list :phase phase :condition (copy-tree condition))))))
(defun etaf-render-port--v1-record-revision (buffer revision)
"Record committed v1 REVISION for BUFFER without postaccept failure."
(when (buffer-live-p buffer)
(with-current-buffer buffer
(setq-local etaf-render-port--v1-committed-revision
(if (and (integerp revision) (> revision 0))
revision
'unavailable)))))
(defun etaf-render-port--v1-revision (buffer)
"Return ETAF's committed revision evidence for legacy v1 BUFFER."
(let ((revision
(and (buffer-live-p buffer)
(buffer-local-value
'etaf-render-port--v1-committed-revision buffer))))
(unless (and (integerp revision) (> revision 0))
(error "Mounted legacy Ebox surface has no committed revision: %S"
revision))
revision))
(defun etaf-render-port--v1-initial
(buffer input framework-stage framework-rollback &optional observer)
"Publish INPUT initially to BUFFER through legacy Ebox.
@ -327,7 +355,8 @@ through Ebox's legacy initial option."
(when (ebox-surface-buffer-mounted-p buffer)
(error "Legacy Ebox initial operation requires an unmounted buffer"))
(with-current-buffer buffer
(setq-local etaf-render-port--v1-cleanup-diagnostics nil))
(setq-local etaf-render-port--v1-cleanup-diagnostics nil
etaf-render-port--v1-committed-revision nil))
(ebox-render-to-buffer
buffer input (and observer (list :observer observer)))))
stage-entered)
@ -335,6 +364,10 @@ through Ebox's legacy initial option."
(progn
(setq stage-entered t)
(funcall framework-stage nil)
;; TP surfaces start at committed revision one. Legacy Ebox does
;; not expose its surface handle, so ETAF owns this compatibility
;; evidence and advances it from later update reports.
(etaf-render-port--v1-record-revision buffer 1)
result)
((error quit)
(when stage-entered
@ -353,6 +386,12 @@ through Ebox's legacy initial option."
(when (and (buffer-live-p buffer)
(ebox-surface-buffer-mounted-p buffer))
(ebox-unmount-buffer buffer))))
(revision-reset
,(lambda ()
(when (buffer-live-p buffer)
(with-current-buffer buffer
(setq-local
etaf-render-port--v1-committed-revision nil)))))
(buffer-restore
,(lambda ()
(etaf-render-port--v1-restore-buffer buffer snapshot)))))
@ -372,7 +411,13 @@ through Ebox's legacy initial option."
FRAMEWORK-STAGE and FRAMEWORK-ROLLBACK retain their existing Ebox meanings."
(etaf-render-port--validate-framework-pair
framework-stage framework-rollback)
(ebox-commit buffer input framework-stage framework-rollback))
(let ((report
(ebox-commit buffer input framework-stage framework-rollback)))
;; Publication is accepted here. Missing compatibility metadata must not
;; become a rollback-capable error after commit; a later read fails closed.
(etaf-render-port--v1-record-revision
(get-buffer buffer) (plist-get report :surface-revision))
report))
(defun etaf-render-port--v1-fallback ()
"Return the complete immutable fallback port for an absent Ebox v2 SPI."
@ -386,6 +431,7 @@ FRAMEWORK-STAGE and FRAMEWORK-ROLLBACK retain their existing Ebox meanings."
:tp-protocol 'tp-transaction-protocol-v1
:initial-function 'etaf-render-port--v1-initial
:update-function 'etaf-render-port--v1-update
:revision-function 'etaf-render-port--v1-revision
:bootstrap-outcome 'v2-absent-v1-selected))
(defun etaf-render-port--v2-port (snapshot)
@ -404,6 +450,7 @@ FRAMEWORK-STAGE and FRAMEWORK-ROLLBACK retain their existing Ebox meanings."
(plist-get (plist-get snapshot :initial) :function)
:update-function
(plist-get (plist-get snapshot :update) :function)
:revision-function 'ebox-surface-buffer-revision
:bootstrap-outcome 'valid-v2-selected
:provider (plist-get snapshot :provider))))
@ -467,7 +514,11 @@ FRAMEWORK-STAGE and FRAMEWORK-ROLLBACK are one required callback pair."
(defun etaf-render-port-unmount (buffer)
"Release the retained Ebox surface owned by mounted BUFFER."
(ebox-unmount-buffer buffer))
(let ((buffer (get-buffer buffer)))
(prog1 (ebox-unmount-buffer buffer)
(when (buffer-live-p buffer)
(with-current-buffer buffer
(setq-local etaf-render-port--v1-committed-revision nil))))))
(defun etaf-render-port-mounted-p (buffer)
"Return non-nil when BUFFER owns a live retained Ebox surface."
@ -479,7 +530,11 @@ FRAMEWORK-STAGE and FRAMEWORK-ROLLBACK are one required callback pair."
(if (not (and (buffer-live-p buffer)
(ebox-surface-buffer-mounted-p buffer)))
0
(let ((revision (ebox-surface-buffer-revision buffer)))
(let ((revision
(funcall
(etaf-render-port-revision-function
etaf-render-port--selected-port)
buffer)))
(unless (and (integerp revision) (>= revision 0))
(error "Mounted Ebox surface has no committed revision: %S"
revision))

View File

@ -3,8 +3,8 @@
;; SPDX-License-Identifier: GPL-3.0-or-later
;; Author: ETAF contributors
;; Version: 0.1.0
;; Package-Requires: ((emacs "29.1") (ebox "2.0.0"))
;; Version: 0.1.1
;; Package-Requires: ((emacs "29.1") (ebox "2.0.1") (tp "1.0.1"))
;; Keywords: ui, tools, convenience
;; URL: https://github.com/ginqi7/etaf

View File

@ -27,6 +27,8 @@
'ebox-framework-spi-initial))
(should (eq (etaf-render-port-update-function port)
'ebox-framework-spi-update))
(should (eq (etaf-render-port-revision-function port)
'ebox-surface-buffer-revision))
(should (eq (etaf-render-port-bootstrap-outcome port)
'valid-v2-selected))
(setcar capabilities 'mutated)
@ -50,6 +52,8 @@
'etaf-render-port--v1-initial))
(should (eq (etaf-render-port-update-function port)
'etaf-render-port--v1-update))
(should (eq (etaf-render-port-revision-function port)
'etaf-render-port--v1-revision))
(should (eq (etaf-render-port-bootstrap-outcome port)
'v2-absent-v1-selected))))))
@ -208,6 +212,28 @@
(ebox-unmount-buffer buffer))
(when (buffer-live-p buffer) (kill-buffer buffer)))))
(ert-deftest etaf-render-port-v1-revision-needs-no-v2-ebox-accessor ()
"A v1-only Ebox retains revisions without a v2-only query symbol."
(let ((buffer (generate-new-buffer " *etaf-v1-revision*"))
(etaf-render-port--selected-port (etaf-render-port--v1-fallback)))
(unwind-protect
(cl-letf (((symbol-function 'ebox-surface-buffer-revision) nil))
(etaf-render-port-initial
buffer (ebox-build '(box "one")) #'ignore #'ignore)
(should (= (etaf-render-port-revision buffer) 1))
(let ((report
(etaf-render-port-update
buffer (ebox-build '(box "two")) #'ignore #'ignore)))
(should (= (etaf-render-port-revision buffer)
(plist-get report :surface-revision))))
(with-current-buffer buffer
(setq-local etaf-render-port--v1-committed-revision nil))
(should-error (etaf-render-port-revision buffer) :type 'error))
(when (and (buffer-live-p buffer)
(ebox-surface-buffer-mounted-p buffer))
(ebox-unmount-buffer buffer))
(when (buffer-live-p buffer) (kill-buffer buffer)))))
(ert-deftest etaf-render-port-is-the-only-protocol-probe-owner ()
"No downstream ETAF module probes Ebox framework SPI protocol state."
(dolist (file (directory-files etaf-render-port-test--root t "\\.el\\'"))