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 ## 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: 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` 开发时先把同级 Ebox 检出目录加入 `load-path`

View File

@ -61,6 +61,7 @@
(tp-protocol nil :read-only t) (tp-protocol nil :read-only t)
(initial-function nil :read-only t) (initial-function nil :read-only t)
(update-function nil :read-only t) (update-function nil :read-only t)
(revision-function nil :read-only t)
(bootstrap-outcome nil :read-only t) (bootstrap-outcome nil :read-only t)
(provider nil :read-only t)) (provider nil :read-only t))
@ -92,6 +93,10 @@
"Return PORT's update function symbol." "Return PORT's update function symbol."
(etaf-render-port--update-function port)) (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) (defun etaf-render-port-bootstrap-outcome (port)
"Return PORT's immutable bootstrap outcome tag." "Return PORT's immutable bootstrap outcome tag."
(etaf-render-port--bootstrap-outcome port)) (etaf-render-port--bootstrap-outcome port))
@ -262,6 +267,9 @@
(defvar-local etaf-render-port--v1-cleanup-diagnostics nil (defvar-local etaf-render-port--v1-cleanup-diagnostics nil
"Contained cleanup failures from the latest failed v1 initial operation.") "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) (defun etaf-render-port-v1-cleanup-diagnostics (buffer)
"Return a defensive copy of BUFFER's latest v1 cleanup diagnostics." "Return a defensive copy of BUFFER's latest v1 cleanup diagnostics."
(and (buffer-live-p (get-buffer buffer)) (and (buffer-live-p (get-buffer buffer))
@ -312,6 +320,26 @@
((error quit) ((error quit)
(list :phase phase :condition (copy-tree condition)))))) (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 (defun etaf-render-port--v1-initial
(buffer input framework-stage framework-rollback &optional observer) (buffer input framework-stage framework-rollback &optional observer)
"Publish INPUT initially to BUFFER through legacy Ebox. "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) (when (ebox-surface-buffer-mounted-p buffer)
(error "Legacy Ebox initial operation requires an unmounted buffer")) (error "Legacy Ebox initial operation requires an unmounted buffer"))
(with-current-buffer 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 (ebox-render-to-buffer
buffer input (and observer (list :observer observer))))) buffer input (and observer (list :observer observer)))))
stage-entered) stage-entered)
@ -335,6 +364,10 @@ through Ebox's legacy initial option."
(progn (progn
(setq stage-entered t) (setq stage-entered t)
(funcall framework-stage nil) (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) result)
((error quit) ((error quit)
(when stage-entered (when stage-entered
@ -353,6 +386,12 @@ through Ebox's legacy initial option."
(when (and (buffer-live-p buffer) (when (and (buffer-live-p buffer)
(ebox-surface-buffer-mounted-p buffer)) (ebox-surface-buffer-mounted-p buffer))
(ebox-unmount-buffer 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 (buffer-restore
,(lambda () ,(lambda ()
(etaf-render-port--v1-restore-buffer buffer snapshot))))) (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." FRAMEWORK-STAGE and FRAMEWORK-ROLLBACK retain their existing Ebox meanings."
(etaf-render-port--validate-framework-pair (etaf-render-port--validate-framework-pair
framework-stage framework-rollback) 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 () (defun etaf-render-port--v1-fallback ()
"Return the complete immutable fallback port for an absent Ebox v2 SPI." "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 :tp-protocol 'tp-transaction-protocol-v1
:initial-function 'etaf-render-port--v1-initial :initial-function 'etaf-render-port--v1-initial
:update-function 'etaf-render-port--v1-update :update-function 'etaf-render-port--v1-update
:revision-function 'etaf-render-port--v1-revision
:bootstrap-outcome 'v2-absent-v1-selected)) :bootstrap-outcome 'v2-absent-v1-selected))
(defun etaf-render-port--v2-port (snapshot) (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) (plist-get (plist-get snapshot :initial) :function)
:update-function :update-function
(plist-get (plist-get snapshot :update) :function) (plist-get (plist-get snapshot :update) :function)
:revision-function 'ebox-surface-buffer-revision
:bootstrap-outcome 'valid-v2-selected :bootstrap-outcome 'valid-v2-selected
:provider (plist-get snapshot :provider)))) :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) (defun etaf-render-port-unmount (buffer)
"Release the retained Ebox surface owned by mounted 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) (defun etaf-render-port-mounted-p (buffer)
"Return non-nil when BUFFER owns a live retained Ebox surface." "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) (if (not (and (buffer-live-p buffer)
(ebox-surface-buffer-mounted-p buffer))) (ebox-surface-buffer-mounted-p buffer)))
0 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)) (unless (and (integerp revision) (>= revision 0))
(error "Mounted Ebox surface has no committed revision: %S" (error "Mounted Ebox surface has no committed revision: %S"
revision)) revision))

View File

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

View File

@ -27,6 +27,8 @@
'ebox-framework-spi-initial)) 'ebox-framework-spi-initial))
(should (eq (etaf-render-port-update-function port) (should (eq (etaf-render-port-update-function port)
'ebox-framework-spi-update)) 'ebox-framework-spi-update))
(should (eq (etaf-render-port-revision-function port)
'ebox-surface-buffer-revision))
(should (eq (etaf-render-port-bootstrap-outcome port) (should (eq (etaf-render-port-bootstrap-outcome port)
'valid-v2-selected)) 'valid-v2-selected))
(setcar capabilities 'mutated) (setcar capabilities 'mutated)
@ -50,6 +52,8 @@
'etaf-render-port--v1-initial)) 'etaf-render-port--v1-initial))
(should (eq (etaf-render-port-update-function port) (should (eq (etaf-render-port-update-function port)
'etaf-render-port--v1-update)) '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) (should (eq (etaf-render-port-bootstrap-outcome port)
'v2-absent-v1-selected)))))) 'v2-absent-v1-selected))))))
@ -208,6 +212,28 @@
(ebox-unmount-buffer buffer)) (ebox-unmount-buffer buffer))
(when (buffer-live-p buffer) (kill-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 () (ert-deftest etaf-render-port-is-the-only-protocol-probe-owner ()
"No downstream ETAF module probes Ebox framework SPI protocol state." "No downstream ETAF module probes Ebox framework SPI protocol state."
(dolist (file (directory-files etaf-render-port-test--root t "\\.el\\'")) (dolist (file (directory-files etaf-render-port-test--root t "\\.el\\'"))