refactor: make semantic generation authoritative

This commit is contained in:
Kinneyzhang 2026-09-01 00:33:54 +08:00
parent d5ccb30125
commit 0cde865d3a
7 changed files with 344 additions and 13 deletions

View File

@ -1,8 +1,8 @@
EMACS ?= emacs
LOAD_PATH = -L . -L examples -L scripts -L ../ebox -L ../tp -L ../ecss
SOURCES = etaf-view.el etaf-compiler.el etaf-component.el etaf-reactive.el etaf-observer.el etaf-context.el etaf-theme-tp.el etaf-resource.el etaf-data.el etaf-render-port.el etaf-renderer.el etaf-runtime.el etaf-behavior.el etaf-actions.el etaf-events.el etaf-performance.el etaf.el scripts/emacs-gui-verifier.el
SOURCES = etaf-view.el etaf-compiler.el etaf-component.el etaf-reactive.el etaf-observer.el etaf-context.el etaf-theme-tp.el etaf-resource.el etaf-data.el etaf-generation.el etaf-render-port.el etaf-renderer.el etaf-runtime.el etaf-behavior.el etaf-actions.el etaf-events.el etaf-performance.el etaf.el scripts/emacs-gui-verifier.el
EXAMPLES = examples/etaf-counter-example.el examples/etaf-data-example.el examples/etaf-resource-example.el
TESTS = tests/etaf-tests.el tests/etaf-compiler-tests.el tests/etaf-component-frontends-tests.el tests/etaf-resource-tests.el tests/etaf-data-tests.el tests/etaf-theme-tp-tests.el tests/etaf-examples-tests.el tests/etaf-observer-tests.el tests/etaf-performance-tests.el tests/etaf-gui-verifier-tests.el tests/etaf-m0a-current-characterization-tests.el tests/etaf-interaction-contract-tests.el tests/etaf-m0b-component-manifest-tests.el tests/etaf-render-port-tests.el
TESTS = tests/etaf-tests.el tests/etaf-compiler-tests.el tests/etaf-component-frontends-tests.el tests/etaf-resource-tests.el tests/etaf-data-tests.el tests/etaf-theme-tp-tests.el tests/etaf-examples-tests.el tests/etaf-observer-tests.el tests/etaf-performance-tests.el tests/etaf-gui-verifier-tests.el tests/etaf-m0a-current-characterization-tests.el tests/etaf-interaction-contract-tests.el tests/etaf-m0b-component-manifest-tests.el tests/etaf-render-port-tests.el tests/etaf-generation-tests.el
.PHONY: test compile load checkdoc docs-check check clean

View File

@ -450,6 +450,14 @@ through one outer reactive dispatch. A local owner evaluates into a candidate
generation and Ebox logical replacement; disjoint owners are coalesced into
one TP/Ebox publication. The Root owner is the only complete-root adapter.
The sole mutable pointer to the committed semantic generation belongs to
`etaf-generation-authority`. Public handler and Host-property queries read that
generation directly. Same-named Runtime hash tables are one-way compatibility
mirrors rebuilt from the generation; they neither authorize queries nor write
back into it. The migration-only `legacy`, `project`, and `shadow` routes prove
projection equivalence and rollback safety without introducing a second
committed truth.
Each Runtime flush records a candidate-aware effect tuple containing the
generation id, effect-to-source edges and source versions, plus an immutable
semantic-node stamp for candidate input/context/output facts. A repeated tuple

View File

@ -440,6 +440,13 @@ mounted Component、expr、slot、fragment、raw、inline 和 Root owner 都经
replacement不相交 owner 会合并为一次 TP/Ebox publication。只有 Root owner
可以进入 complete-root adapter。
已提交 semantic generation 的唯一 mutable pointer 由
`etaf-generation-authority` 持有。handler 与 Host-prop 等公共查询直接读取该
generationRuntime 中同名 hash table 只是由 generation 单向重建的兼容 mirror
既不能独立授权查询,也不能反向改写 generation。迁移期的
`legacy`/`project`/`shadow` 路由只用于证明投影等价与安全回退,不增加第二份
committed truth。
每次 Runtime flush 都记录 candidate-aware effect tuple其中包含 generation id、
effect→source 边和 source version以及 candidate input/context/output facts 的
immutable semantic-node stamp。重复 tuple 会报告有序的 effect/edge pathstep

89
etaf-generation.el Normal file
View File

@ -0,0 +1,89 @@
;;; etaf-generation.el --- ETAF committed generation authority -*- lexical-binding: t; -*-
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Commentary:
;; Owns the single mutable pointer to ETAF's committed semantic generation and
;; pure construction/comparison of compatibility mirrors. Runtime mirrors are
;; projections only; they never authorize semantic queries or publication.
;;; Code:
(require 'cl-lib)
(require 'subr-x)
(define-error 'etaf-generation-error "Invalid ETAF generation authority")
(cl-defstruct
(etaf-generation-authority
(:constructor etaf-generation-authority--create))
"Mutable authority for one Runtime's committed generation."
generation
(token 0))
(defun etaf-generation-authority-create (&optional generation)
"Return fresh authority initially pointing at GENERATION."
(etaf-generation-authority--create :generation generation :token 0))
(defun etaf-generation-authority-current (authority)
"Return AUTHORITY's current committed generation."
(unless (etaf-generation-authority-p authority)
(signal 'wrong-type-argument
(list 'etaf-generation-authority-p authority)))
(etaf-generation-authority-generation authority))
(defun etaf-generation-authority-set-current (authority generation)
"Set AUTHORITY's current committed GENERATION and return GENERATION.
This compatibility mutation does not advance the semantic commit token; the
versioned compare-and-swap boundary owns token changes."
(unless (etaf-generation-authority-p authority)
(signal 'wrong-type-argument
(list 'etaf-generation-authority-p authority)))
(setf (etaf-generation-authority-generation authority) generation)
generation)
(defun etaf-generation-project-mirror (entries &optional test)
"Project immutable contribution ENTRIES into a fresh hash table.
TEST defaults to `equal'. Duplicate keys are rejected so a compatibility
mirror cannot silently choose a second semantic truth."
(unless (proper-list-p entries)
(signal 'wrong-type-argument (list 'proper-list-p entries)))
(let ((table (make-hash-table :test (or test #'equal)))
(missing (make-symbol "etaf-generation-mirror-missing")))
(dolist (entry entries)
(unless (consp entry)
(signal 'etaf-generation-error
(list :malformed-mirror-entry entry)))
(let ((key (car entry)))
(unless (eq (gethash key table missing) missing)
(signal 'etaf-generation-error
(list :duplicate-mirror-key key)))
(puthash (copy-tree key) (copy-tree (cdr entry)) table)))
table))
(defun etaf-generation-project-mirrors (handler-entries host-prop-entries)
"Return fresh mirrors from HANDLER-ENTRIES and HOST-PROP-ENTRIES."
(list :handlers
(etaf-generation-project-mirror handler-entries #'equal)
:host-props
(etaf-generation-project-mirror host-prop-entries #'equal)))
(defun etaf-generation-mirror-equal-p (left right)
"Return non-nil when hash tables LEFT and RIGHT contain equal facts."
(and (hash-table-p left)
(hash-table-p right)
(= (hash-table-count left) (hash-table-count right))
(let ((missing (make-symbol "etaf-generation-mirror-missing"))
equal-p)
(setq equal-p t)
(maphash
(lambda (key value)
(unless (equal value (gethash key right missing))
(setq equal-p nil)))
left)
equal-p)))
(provide 'etaf-generation)
;;; etaf-generation.el ends here

View File

@ -15,6 +15,7 @@
(require 'etaf-view)
(require 'etaf-component)
(require 'etaf-reactive)
(require 'etaf-generation)
(require 'etaf-render-port)
(require 'etaf-renderer)
(require 'etaf-context)
@ -284,7 +285,7 @@ the sequential `etaf--pvec-put' contract."
next-resource-id
next-effect-id
next-semantic-id
current-generation
generation-authority
artifact-registry
candidate-artifacts
candidate-effects
@ -334,6 +335,33 @@ the sequential `etaf--pvec-put' contract."
(event-depth 0)
dirty-effect-queue-tail)
(defcustom etaf-generation-mirror-route 'project
"Compatibility mirror route used after a committed generation changes.
`legacy' preserves incremental Runtime table updates, `project' rebuilds exact
mirrors from the committed generation, and `shadow' runs legacy updates before
checking them against a fresh generation projection."
:type '(choice (const legacy) (const project) (const shadow))
:group 'etaf)
(defun etaf--runtime-generation-authority (runtime)
"Return RUNTIME's generation authority, creating its empty owner if needed."
(or (etaf-runtime-generation-authority runtime)
(setf (etaf-runtime-generation-authority runtime)
(etaf-generation-authority-create))))
(defun etaf-runtime-current-generation (runtime)
"Return RUNTIME's uniquely authoritative committed generation."
(etaf-generation-authority-current
(etaf--runtime-generation-authority runtime)))
(defun etaf-runtime--set-current-generation (runtime generation)
"Set RUNTIME's compatibility generation pointer to GENERATION."
(etaf-generation-authority-set-current
(etaf--runtime-generation-authority runtime) generation))
(gv-define-simple-setter etaf-runtime-current-generation
etaf-runtime--set-current-generation)
(defun etaf--theme-paint-face-spec (property value)
"Return PROPERTY's TP face spec for resolved paint VALUE, or nil."
(pcase property
@ -572,6 +600,81 @@ RESOLVED is the current projected paint value."
(copy-tree (etaf--generation-index-entries
(etaf-runtime-current-generation runtime) 'host-props)))
(defun etaf--runtime-generation-mirror-projection (generation)
"Return exact compatibility mirrors projected from GENERATION."
(etaf-generation-project-mirrors
(etaf--generation-index-entries generation 'handlers)
(etaf--generation-index-entries generation 'host-props)))
(defun etaf--runtime-install-legacy-generation-mirrors (runtime full-p)
"Install RUNTIME candidate mirrors through the legacy FULL-P route."
(if full-p
(setf (etaf-runtime-handlers runtime)
(etaf-runtime-candidate-handlers runtime)
(etaf-runtime-host-props runtime)
(etaf-runtime-candidate-host-props runtime))
(unless (hash-table-p (etaf-runtime-handlers runtime))
(setf (etaf-runtime-handlers runtime) (make-hash-table :test #'equal)))
(unless (hash-table-p (etaf-runtime-host-props runtime))
(setf (etaf-runtime-host-props runtime)
(make-hash-table :test #'equal)))
(maphash
(lambda (host-ref handlers)
(puthash host-ref handlers (etaf-runtime-handlers runtime)))
(etaf-runtime-candidate-handlers runtime))
(maphash
(lambda (host-ref props)
(puthash host-ref props (etaf-runtime-host-props runtime)))
(etaf-runtime-candidate-host-props runtime))))
(defun etaf--runtime-install-projected-generation-mirrors
(runtime projection)
"Install detached generation mirror PROJECTION into RUNTIME."
(setf (etaf-runtime-handlers runtime) (plist-get projection :handlers)
(etaf-runtime-host-props runtime) (plist-get projection :host-props)))
(defun etaf-runtime-generation-mirrors-consistent-p (runtime)
"Return non-nil when RUNTIME mirrors equal its committed generation."
(let ((projection
(etaf--runtime-generation-mirror-projection
(etaf-runtime-current-generation runtime))))
(and
(etaf-generation-mirror-equal-p
(or (etaf-runtime-handlers runtime) (make-hash-table :test #'equal))
(plist-get projection :handlers))
(etaf-generation-mirror-equal-p
(or (etaf-runtime-host-props runtime) (make-hash-table :test #'equal))
(plist-get projection :host-props)))))
(defun etaf--runtime-install-generation-mirrors
(runtime generation full-p)
"Install RUNTIME compatibility mirrors for committed GENERATION.
FULL-P preserves the old route's full-root replacement distinction."
(let ((projection (etaf--runtime-generation-mirror-projection generation)))
(pcase etaf-generation-mirror-route
('legacy
(etaf--runtime-install-legacy-generation-mirrors runtime full-p))
('project
(etaf--runtime-install-projected-generation-mirrors runtime projection))
('shadow
(etaf--runtime-install-legacy-generation-mirrors runtime full-p)
(unless (and
(etaf-generation-mirror-equal-p
(etaf-runtime-handlers runtime)
(plist-get projection :handlers))
(etaf-generation-mirror-equal-p
(etaf-runtime-host-props runtime)
(plist-get projection :host-props)))
(signal 'etaf-generation-error
(list :compatibility-mirror-drift
:generation
(and generation
(etaf-generation-generation-id generation))))))
(_
(signal 'etaf-generation-error
(list :unknown-mirror-route etaf-generation-mirror-route))))
runtime))
(defun etaf--runtime-build-contribution-indexes (runtime base full-p)
"Build RUNTIME immutable generation contributions over BASE.
FULL-P means candidate tables describe the complete mounted tree."
@ -5592,12 +5695,8 @@ RENDERED-IDENTITIES names the Component render participants."
(etaf--runtime-clear-candidate runtime)
(signal (car err) (cdr err))))
(etaf--runtime-complete-generation runtime candidate-generation)
(maphash (lambda (host-ref handlers)
(puthash host-ref handlers (etaf-runtime-handlers runtime)))
(etaf-runtime-candidate-handlers runtime))
(maphash (lambda (host-ref props)
(puthash host-ref props (etaf-runtime-host-props runtime)))
(etaf-runtime-candidate-host-props runtime))
(etaf--runtime-install-generation-mirrors
runtime candidate-generation nil)
(etaf--runtime-clear-dirty-effects runtime)
(unwind-protect
(progn
@ -5670,11 +5769,9 @@ RENDERED-IDENTITIES names the Component render participants."
(list :observer #'etaf--runtime-forward-ebox-report)))
(etaf--runtime-participant-publish participant))
(etaf--runtime-complete-generation runtime candidate-generation)
(etaf--runtime-install-generation-mirrors
runtime candidate-generation t)
(setf (etaf-runtime-root-node runtime) root-node
(etaf-runtime-handlers runtime)
(etaf-runtime-candidate-handlers runtime)
(etaf-runtime-host-props runtime)
(etaf-runtime-candidate-host-props runtime)
(etaf-runtime-root-dirty-p runtime) nil)
(etaf--runtime-clear-dirty-effects runtime))
((error quit)
@ -5770,6 +5867,8 @@ backend anchor proof failed; ordinary root turns keep their artifact reuse."
:next-resource-id 0
:next-effect-id 0
:next-semantic-id 1
:generation-authority
(etaf-generation-authority-create)
:root-effect-id 0
:root-range-id 1
:artifact-registry (make-hash-table :test #'equal)

View File

@ -35,6 +35,7 @@
(require 'etaf-context)
(require 'etaf-resource)
(require 'etaf-data)
(require 'etaf-generation)
(require 'etaf-render-port)
(require 'etaf-renderer)
(etaf--prefer-local-files)

View File

@ -0,0 +1,127 @@
;;; etaf-generation-tests.el --- M3a generation authority gates -*- lexical-binding: t; -*-
;; SPDX-License-Identifier: GPL-3.0-or-later
(require 'ert)
(require 'etaf)
(require 'etaf-generation)
(defun etaf-generation-test--view (visible)
"Return a root View selected by reactive VISIBLE."
(if (etaf-value visible)
(etaf-view
(text :ref 'generation-target :role 'button
:on-press #'ignore "visible"))
(etaf-view (text :ref 'generation-other "other"))))
(ert-deftest etaf-generation-authority-is-runtime-single-source ()
"Runtime compatibility access reads and writes one authority object."
(let* ((runtime (etaf--runtime-create))
(first (etaf--generation-create :generation-id 1))
(second (etaf--generation-create :generation-id 2)))
(should-not (etaf-runtime-current-generation runtime))
(setf (etaf-runtime-current-generation runtime) first)
(let ((authority (etaf-runtime-generation-authority runtime)))
(should (etaf-generation-authority-p authority))
(should (eq first (etaf-generation-authority-current authority)))
(should (eq first (etaf-runtime-current-generation runtime)))
(setf (etaf-runtime-current-generation runtime) second)
(should (eq authority (etaf-runtime-generation-authority runtime)))
(should (eq second (etaf-runtime-current-generation runtime)))
(should (zerop (etaf-generation-authority-token authority))))))
(ert-deftest etaf-generation-projection-is-detached-and-rejects-duplicates ()
"Compatibility projections copy facts and reject ambiguous keys."
(let* ((handlers '((host . ((press . callback)))))
(projection
(etaf-generation-project-mirrors handlers '((host :role button))))
(handler-table (plist-get projection :handlers))
(props-table (plist-get projection :host-props)))
(setcdr (car handlers) 'mutated)
(should (equal (gethash 'host handler-table) '((press . callback))))
(should (equal (gethash 'host props-table) '(:role button)))
(should-error
(etaf-generation-project-mirror '((host . one) (host . two)))
:type 'etaf-generation-error)))
(ert-deftest etaf-generation-mirrors-follow-committed-generation ()
"Every commit projects exact mirrors and removes stale Host facts."
(let ((buffer-name " *etaf-generation-mirror-test*")
(visible (etaf-ref t)))
(unwind-protect
(progn
(etaf-mount buffer-name
(lambda () (etaf-generation-test--view visible)))
(let ((runtime (etaf-runtime-for-buffer buffer-name)))
(should (etaf-runtime-generation-mirrors-consistent-p runtime))
(should (etaf-runtime-handler-for runtime 'generation-target))
(should (gethash 'generation-target
(etaf-runtime-handlers runtime)))
(setf (etaf-value visible) nil)
(should (etaf-runtime-generation-mirrors-consistent-p runtime))
(should-not (etaf-runtime-handler-for runtime 'generation-target))
(should-not (gethash 'generation-target
(etaf-runtime-handlers runtime)))
(should (gethash 'generation-other
(etaf-runtime-host-props runtime)))))
(when-let* ((runtime (etaf-runtime-for-buffer buffer-name)))
(etaf-unmount runtime))
(when-let* ((buffer (get-buffer buffer-name)))
(kill-buffer buffer)))))
(ert-deftest etaf-generation-query-ignores-compatibility-mirror-drift ()
"Corrupting a mirror never changes committed generation queries."
(let ((buffer-name " *etaf-generation-query-test*")
(visible (etaf-ref t)))
(unwind-protect
(progn
(etaf-mount buffer-name
(lambda () (etaf-generation-test--view visible)))
(let* ((runtime (etaf-runtime-for-buffer buffer-name))
(committed
(copy-tree
(etaf-runtime-handler-for runtime 'generation-target))))
(puthash 'generation-target 'corrupt
(etaf-runtime-handlers runtime))
(should-not (etaf-runtime-generation-mirrors-consistent-p runtime))
(should (equal committed
(etaf-runtime-handler-for
runtime 'generation-target)))
(setf (etaf-value visible) nil)
(setf (etaf-value visible) t)
(should (etaf-runtime-generation-mirrors-consistent-p runtime))))
(when-let* ((runtime (etaf-runtime-for-buffer buffer-name)))
(etaf-unmount runtime))
(when-let* ((buffer (get-buffer buffer-name)))
(kill-buffer buffer)))))
(ert-deftest etaf-generation-shadow-route-proves-legacy-equivalence ()
"The shadow route accepts a legacy projection only when facts are equal."
(let ((buffer-name " *etaf-generation-shadow-test*")
(visible (etaf-ref t))
(etaf-generation-mirror-route 'shadow))
(unwind-protect
(progn
(etaf-mount buffer-name
(lambda () (etaf-generation-test--view visible)))
(setf (etaf-value visible) nil)
(let ((runtime (etaf-runtime-for-buffer buffer-name)))
(should (etaf-runtime-generation-mirrors-consistent-p runtime))))
(when-let* ((runtime (etaf-runtime-for-buffer buffer-name)))
(etaf-unmount runtime))
(when-let* ((buffer (get-buffer buffer-name)))
(kill-buffer buffer)))))
(ert-deftest etaf-generation-rejects-unknown-mirror-route ()
"An unknown compatibility route cannot silently publish mirrors."
(let ((runtime (etaf--runtime-create
:generation-authority
(etaf-generation-authority-create)))
(etaf-generation-mirror-route 'unknown))
(should-error
(etaf--runtime-install-generation-mirrors runtime nil t)
:type 'etaf-generation-error)))
(provide 'etaf-generation-tests)
;;; etaf-generation-tests.el ends here