refactor: classify Ebox retained state for M2a E1
Some checks are pending
CI / test (push) Waiting to run
CI / native-build (macos-latest) (push) Waiting to run
CI / native-build (ubuntu-latest) (push) Waiting to run
CI / native-build (windows-latest) (push) Waiting to run
CI / native-msrv (macos-latest) (push) Waiting to run
CI / native-msrv (ubuntu-latest) (push) Waiting to run
CI / native-msrv (windows-latest) (push) Waiting to run
Some checks are pending
CI / test (push) Waiting to run
CI / native-build (macos-latest) (push) Waiting to run
CI / native-build (ubuntu-latest) (push) Waiting to run
CI / native-build (windows-latest) (push) Waiting to run
CI / native-msrv (macos-latest) (push) Waiting to run
CI / native-msrv (ubuntu-latest) (push) Waiting to run
CI / native-msrv (windows-latest) (push) Waiting to run
This commit is contained in:
parent
54b3514668
commit
e55798e0e9
7
Makefile
7
Makefile
@ -10,7 +10,7 @@ NATIVE_MANIFEST = native/Cargo.toml
|
||||
NATIVE_TARGET ?= $(shell $(EMACS_BATCH) -l ebox-native-reflow.el --eval '(princ (ebox-native-reflow--rust-target))')
|
||||
NATIVE_RELEASE_DIR = native/target/$(NATIVE_TARGET)/release
|
||||
|
||||
.PHONY: all check ci load compile test checkdoc source-tests font-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests docs-contract-tests ci-contract-tests performance-evaluator visual-check native-rust-tests native-build diff-check clean package-lint package-lint-install
|
||||
.PHONY: all check ci load compile test checkdoc source-tests font-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests state-contract-tests docs-contract-tests ci-contract-tests performance-evaluator visual-check native-rust-tests native-build diff-check clean package-lint package-lint-install
|
||||
|
||||
all: check
|
||||
|
||||
@ -25,7 +25,7 @@ compile:
|
||||
rm -f *.elc tests/*.elc scripts/*.elc
|
||||
$(EMACS_BATCH) --eval '(setq byte-compile-error-on-warn t byte-compile-warnings (quote (not obsolete)))' -l ebox.el --eval '(ebox-byte-compile)'
|
||||
|
||||
test: source-tests font-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests docs-contract-tests ci-contract-tests
|
||||
test: source-tests font-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests state-contract-tests docs-contract-tests ci-contract-tests
|
||||
|
||||
source-tests:
|
||||
$(EMACS_BATCH) -l tests/ebox-source-tests.el -f ert-run-tests-batch-and-exit
|
||||
@ -63,6 +63,9 @@ dsl-tests:
|
||||
flex-tests:
|
||||
$(EMACS_TEST) -l tests/ebox-flex-tests.el -f ert-run-tests-batch-and-exit
|
||||
|
||||
state-contract-tests:
|
||||
$(EMACS_TEST) -l tests/ebox-state-contract-tests.el -f ert-run-tests-batch-and-exit
|
||||
|
||||
performance-evaluator: surface-tests ebox-commit-tests
|
||||
$(EMACS_BATCH) -l scripts/ebox-performance-evaluator.el -f ebox-performance-evaluator-batch
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ This is the maintainer entry point for the standalone Ebox repository. It descri
|
||||
| `ebox.el` | Public facade, construction helpers, rendering, TP-backed buffer entry points, scrolling, commit, and byte compilation. |
|
||||
| `ebox-cache.el` | Measurement/render cache records, invalidation, and cache reports. |
|
||||
| `ebox-source.el` | Opaque source handles, immutable author source records, and candidate source indexes. |
|
||||
| `ebox-state-contract.el` | Closed retained-state ownership inventory and read-only compatibility-mirror rebuild probes. |
|
||||
| `ebox-style.el` | ECSS property schemas, declarations and cascade, shorthand expansion, computed style, colors, borders, and dirty effects. |
|
||||
| `ebox-tree.el` | Node traversal, logical child access, ECSS subject adaptation, identity, parent paths, keys, and tree snapshots. |
|
||||
| `ebox-child-range.el` | Immutable weighted segment trie, sparse persistent key trie, Range replacement, and Gate A metrics. |
|
||||
@ -37,7 +38,7 @@ This is the maintainer entry point for the standalone Ebox repository. It descri
|
||||
|
||||
The package intentionally does not include application Components, UI controls, reactive data, or a playground implementation. Those are sibling-package responsibilities.
|
||||
|
||||
The active contract also covers `Makefile`, `.github/workflows/ci.yml`, `tests/ebox-core-render-tests.el`, `tests/ebox-child-range-tests.el`, `tests/ebox-grid-tests.el`, `tests/ebox-commit-tests.el`, `tests/ebox-surface-tests.el`, `tests/ebox-dsl-tests.el`, `tests/ebox-flex-tests.el`, `tests/ebox-selector-tests.el`, `tests/ebox-package-tests.el`, `tests/ebox-visual-check-tests.el`, `tests/ebox-docs-contract-tests.el`, `tests/ebox-ci-contract-tests.el`, `native/Cargo.toml`, `native/Cargo.lock`, `native/build.rs`, `native/vendor/emacs-30/emacs-module.h`, `native/src/lib.rs`, `native/src/layout.rs`, `native/c/ebox_module.c`, `scripts/ebox-package-lint.el`, `scripts/ebox-visual-check.el`, and `scripts/ebox-performance-evaluator.el`.
|
||||
The active contract also covers `Makefile`, `.github/workflows/ci.yml`, `tests/ebox-core-render-tests.el`, `tests/ebox-state-contract-tests.el`, `tests/ebox-child-range-tests.el`, `tests/ebox-grid-tests.el`, `tests/ebox-commit-tests.el`, `tests/ebox-surface-tests.el`, `tests/ebox-dsl-tests.el`, `tests/ebox-flex-tests.el`, `tests/ebox-selector-tests.el`, `tests/ebox-package-tests.el`, `tests/ebox-visual-check-tests.el`, `tests/ebox-docs-contract-tests.el`, `tests/ebox-ci-contract-tests.el`, `native/Cargo.toml`, `native/Cargo.lock`, `native/build.rs`, `native/vendor/emacs-30/emacs-module.h`, `native/src/lib.rs`, `native/src/layout.rs`, `native/c/ebox_module.c`, `scripts/ebox-package-lint.el`, `scripts/ebox-visual-check.el`, and `scripts/ebox-performance-evaluator.el`.
|
||||
|
||||
## Runtime model
|
||||
|
||||
@ -61,6 +62,7 @@ Caller-owned Source Tree
|
||||
| Model | Owner | Must not own |
|
||||
| --- | --- | --- |
|
||||
| Source/Element Tree | `ebox-child-range.el`, `ebox-tree.el`, `ebox-dsl.el` | Published buffer mutation. |
|
||||
| State Ownership Contract | `ebox-state-contract.el` | Live publication or mutable authority. |
|
||||
| Computed Style | `ebox-style.el` | Layout identity or patch execution. |
|
||||
| Measurement | `ebox-measure.el` | Application state or dirty policy. |
|
||||
| Formatting Context | `ebox-layout.el`, `ebox-flex.el`, `ebox-grid.el` | Buffer edits. |
|
||||
@ -81,6 +83,7 @@ Caller-owned Source Tree
|
||||
- Keys are local to siblings; visible strings are never used as identity.
|
||||
- Ebox owns logical node-to-subject adaptation and id/class/type candidate indexes; ECSS's public structured matcher is the only selector truth source. Subjects expose built-in id/key plus explicit `:selector-attributes`, never visible content, layout state, or runtime containers.
|
||||
- `ebox-style--property-definitions` is the single Ebox author-property source. Load time derives Ebox's read-only lookup index and one immutable ECSS package schema from it; the surface schema is composed once, and node construction or updates never re-register or copy the whole schema domain.
|
||||
- E1 freezes the closed M2a target classification for retained state: immutable generation facts; generation-bound scroll/native authority; opaque TP client-state custody; one-way compatibility mirrors; and disposable caches. Every inventory row separately records the current v1 storage shape plus its target owner, mutation API, generation binding, rollback, rebuild proof, and cleanup. Current v1 TP client state still holds the whole Ebox state plist until a later checkpoint changes that storage shape. The E1 mirror probes only rebuild and compare projections; they never mutate live state.
|
||||
- `owner-rerender` is broader than `span-patch`, which is broader than `paint-patch`.
|
||||
- Buffer coordinates belong to the generation that produced them and must be refreshed after mutation.
|
||||
- Grid uses the normal measurement and rendering pipeline. Native reflow may reject an ineligible tree and must fall back to Elisp without changing correctness.
|
||||
@ -112,6 +115,7 @@ make package-tests
|
||||
make selector-tests
|
||||
make dsl-tests
|
||||
make flex-tests
|
||||
make state-contract-tests
|
||||
make docs-contract-tests
|
||||
make ci-contract-tests
|
||||
make performance-evaluator
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
| `ebox.el` | 公共门面、构造辅助函数、渲染、基于 TP 的 buffer 入口、滚动、commit 与 byte compile。 |
|
||||
| `ebox-cache.el` | 测量/渲染缓存记录、失效和缓存报告。 |
|
||||
| `ebox-source.el` | opaque source handle、不可变 author source record 与 candidate source index。 |
|
||||
| `ebox-state-contract.el` | 封闭的 retained-state 所有权清单与只读 compatibility-mirror 重建探针。 |
|
||||
| `ebox-style.el` | ECSS property schema、declaration 与 cascade、shorthand 展开、computed style、颜色、border 和 dirty effect。 |
|
||||
| `ebox-tree.el` | 节点遍历、逻辑子节点访问、ECSS subject 适配、identity、父路径、key 和树 snapshot。 |
|
||||
| `ebox-child-range.el` | 不可变 weighted segment trie、稀疏持久 key trie、Range replacement 与 Gate A metrics。 |
|
||||
@ -37,7 +38,7 @@
|
||||
|
||||
本包有意不包含应用 Component、UI control、响应式 data 或 playground 实现;它们属于同级包。历史应用性能记录器和 native reflow 评估器也不属于独立 Ebox 的发布边界;Ebox 只保留 native 模块本身、Rust 构建输入和可重复的构建检查。
|
||||
|
||||
active 合同还覆盖 `Makefile`、`.github/workflows/ci.yml`、`tests/ebox-core-render-tests.el`、`tests/ebox-child-range-tests.el`、`tests/ebox-grid-tests.el`、`tests/ebox-commit-tests.el`、`tests/ebox-surface-tests.el`、`tests/ebox-dsl-tests.el`、`tests/ebox-flex-tests.el`、`tests/ebox-selector-tests.el`、`tests/ebox-package-tests.el`、`tests/ebox-visual-check-tests.el`、`tests/ebox-docs-contract-tests.el`、`tests/ebox-ci-contract-tests.el`、`native/Cargo.toml`、`native/Cargo.lock`、`native/build.rs`、`native/vendor/emacs-30/emacs-module.h`、`native/src/lib.rs`、`native/src/layout.rs`、`native/c/ebox_module.c`、`scripts/ebox-package-lint.el`、`scripts/ebox-visual-check.el` 和 `scripts/ebox-performance-evaluator.el`。
|
||||
active 合同还覆盖 `Makefile`、`.github/workflows/ci.yml`、`tests/ebox-core-render-tests.el`、`tests/ebox-state-contract-tests.el`、`tests/ebox-child-range-tests.el`、`tests/ebox-grid-tests.el`、`tests/ebox-commit-tests.el`、`tests/ebox-surface-tests.el`、`tests/ebox-dsl-tests.el`、`tests/ebox-flex-tests.el`、`tests/ebox-selector-tests.el`、`tests/ebox-package-tests.el`、`tests/ebox-visual-check-tests.el`、`tests/ebox-docs-contract-tests.el`、`tests/ebox-ci-contract-tests.el`、`native/Cargo.toml`、`native/Cargo.lock`、`native/build.rs`、`native/vendor/emacs-30/emacs-module.h`、`native/src/lib.rs`、`native/src/layout.rs`、`native/c/ebox_module.c`、`scripts/ebox-package-lint.el`、`scripts/ebox-visual-check.el` 和 `scripts/ebox-performance-evaluator.el`。
|
||||
|
||||
## 运行时模型
|
||||
|
||||
@ -61,6 +62,7 @@ active 合同还覆盖 `Makefile`、`.github/workflows/ci.yml`、`tests/ebox-cor
|
||||
| 模型 | Owner | 不得拥有 |
|
||||
| --- | --- | --- |
|
||||
| Source/Element Tree | `ebox-child-range.el`、`ebox-tree.el`、`ebox-dsl.el` | 已发布 buffer 的变更。 |
|
||||
| 状态所有权合同 | `ebox-state-contract.el` | Live 发布或可变 authority。 |
|
||||
| Computed Style | `ebox-style.el` | 布局 identity 或 patch 执行。 |
|
||||
| Measurement | `ebox-measure.el` | 应用状态或 dirty 策略。 |
|
||||
| Formatting Context | `ebox-layout.el`、`ebox-flex.el`、`ebox-grid.el` | Buffer 编辑。 |
|
||||
@ -81,6 +83,7 @@ active 合同还覆盖 `Makefile`、`.github/workflows/ci.yml`、`tests/ebox-cor
|
||||
- Key 只在兄弟节点中有效;不能用可见字符串作为 identity。
|
||||
- Ebox 只拥有逻辑 node-to-subject 适配与 id/class/type 候选索引;ECSS 的公共结构化 matcher 是唯一 selector 真相源。subject 只暴露内建 id/key 和显式 `:selector-attributes`,绝不暴露可见 content、布局状态或 runtime 容器。
|
||||
- `ebox-style--property-definitions` 是 Ebox 作者属性的唯一来源:加载时一次生成 Ebox 自己的只读查询索引与一个不可变 ECSS package schema;surface schema 只组合一次,node 构造和更新不重复注册或复制整份 schema。
|
||||
- E1 固化封闭的 M2a 目标分类:不可变 generation fact、绑定 generation 的 scroll/native authority、opaque TP client-state custody、单向 compatibility mirror,以及 disposable cache。每一行分别记录当前 v1 存储形态,以及目标 owner、mutation API、generation binding、rollback、rebuild proof 与 cleanup。在后续 checkpoint 改变存储形态前,当前 v1 TP client state 仍保存完整 Ebox state plist。E1 mirror 探针只重建并比较投影,不修改 live state。
|
||||
- `owner-rerender` 范围大于 `span-patch`,`span-patch` 大于 `paint-patch`。
|
||||
- Buffer 坐标属于生成它的 generation,变更后必须重新获取。
|
||||
- Grid 使用普通测量与渲染流水线;native reflow 可以拒绝不适合的树并回退到 Elisp,正确性不变。
|
||||
@ -110,6 +113,7 @@ make package-tests
|
||||
make selector-tests
|
||||
make dsl-tests
|
||||
make flex-tests
|
||||
make state-contract-tests
|
||||
make docs-contract-tests
|
||||
make ci-contract-tests
|
||||
make performance-evaluator
|
||||
|
||||
277
ebox-state-contract.el
Normal file
277
ebox-state-contract.el
Normal file
@ -0,0 +1,277 @@
|
||||
;;; ebox-state-contract.el --- Ebox retained-state ownership contract -*- lexical-binding: t; -*-
|
||||
|
||||
;; SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Records the M2a target ownership classification for retained Ebox state and
|
||||
;; the distinct current v1 storage shape. It also offers read-only
|
||||
;; compatibility-mirror rebuild probes. This module does not publish a
|
||||
;; generation, mutate live runtime state, or select a transaction route.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
(require 'subr-x)
|
||||
|
||||
(define-error 'ebox-state-contract-error
|
||||
"Invalid Ebox retained-state contract")
|
||||
|
||||
(defconst ebox-state-contract-categories
|
||||
'(generation-fact generation-bound-mutable-authority
|
||||
tp-storage-custody compatibility-mirror disposable-cache)
|
||||
"Closed set of retained-state categories used by the M2a inventory.")
|
||||
|
||||
(defconst ebox-state-contract-required-fields
|
||||
'(:id :storage :current-contract :target-contract :category :owner
|
||||
:mutation-api :generation-binding :rollback :rebuild-proof :cleanup)
|
||||
"Fields required on every M2a retained-state inventory record.")
|
||||
|
||||
(defconst ebox-state-contract--inventory
|
||||
'((:id runtime-generation-indexes
|
||||
:storage (:source-index ebox-source-index/records
|
||||
ebox-source-index/handle-records
|
||||
ebox-source-index/host-ref-table
|
||||
:root-node :node-table :parent-table
|
||||
:runtime-type-count-table :range-ref-table
|
||||
:selector-id-table :selector-class-table :selector-type-table)
|
||||
:current-contract mixed-candidate-and-committed-state
|
||||
:target-contract immutable-generation-value
|
||||
:category generation-fact
|
||||
:owner ebox
|
||||
:mutation-api candidate-construction-only
|
||||
:generation-binding immutable-generation-token
|
||||
:rollback discard-candidate
|
||||
:rebuild-proof source-and-runtime-index-rebuild
|
||||
:cleanup generation-replacement)
|
||||
(:id region-generation-indexes
|
||||
:storage (:region-id-set :region-node-table :region-box-count-table
|
||||
:region-box-table :layout-snapshots)
|
||||
:current-contract candidate-state-plus-global-projection
|
||||
:target-contract immutable-generation-value
|
||||
:category generation-fact
|
||||
:owner ebox
|
||||
:mutation-api candidate-layout-materialization-only
|
||||
:generation-binding immutable-generation-token
|
||||
:rollback discard-candidate
|
||||
:rebuild-proof layout-and-region-index-rebuild
|
||||
:cleanup generation-replacement)
|
||||
(:id scroll-membership
|
||||
:storage (:scroll-region-ids)
|
||||
:current-contract committed-state-membership-list
|
||||
:target-contract immutable-generation-value
|
||||
:category generation-fact
|
||||
:owner ebox
|
||||
:mutation-api candidate-layout-materialization-only
|
||||
:generation-binding immutable-generation-token
|
||||
:rollback discard-candidate
|
||||
:rebuild-proof layout-scroll-membership-rebuild
|
||||
:cleanup generation-replacement)
|
||||
(:id scroll-runtime-authority
|
||||
:storage (:scroll-state-table :scroll-offset :scroll-window
|
||||
ebox--scroll-global-state
|
||||
ebox--smooth-scroll-state-table
|
||||
ebox--scroll-idle-prefetch-timers)
|
||||
:current-contract global-and-state-table-mutable-handles
|
||||
:target-contract stable-id-plus-generation-token-authority
|
||||
:category generation-bound-mutable-authority
|
||||
:owner ebox-scroll
|
||||
:mutation-api stable-scroll-id-and-generation-token
|
||||
:generation-binding required
|
||||
:rollback participant-journal-restores-prior-authority
|
||||
:rebuild-proof not-rebuildable-from-cache
|
||||
:cleanup cancel-timers-and-retire-generation)
|
||||
(:id native-runtime-authority
|
||||
:storage (:native-sync-session :native-sync-pending :native-session
|
||||
ebox-native-reflow-preparation ebox-native-reflow-session)
|
||||
:current-contract candidate-preparation-and-confirmed-session-handles
|
||||
:target-contract generation-token-authority
|
||||
:category generation-bound-mutable-authority
|
||||
:owner ebox-native
|
||||
:mutation-api native-candidate-confirm-or-abort
|
||||
:generation-binding required
|
||||
:rollback abort-candidate-and-keep-confirmed-session
|
||||
:rebuild-proof not-rebuildable-from-cache
|
||||
:cleanup release-losing-session)
|
||||
(:id tp-client-state-custody
|
||||
:storage (tp-surface-client-state)
|
||||
:current-contract entire-ebox-state-plist
|
||||
:target-contract opaque-generation-correlation-only
|
||||
:category tp-storage-custody
|
||||
:owner tp
|
||||
:mutation-api opaque-ebox-generation-correlation-only
|
||||
:generation-binding opaque-correlation
|
||||
:rollback tp-restores-client-state
|
||||
:rebuild-proof ebox-generation-remains-source-of-truth
|
||||
:cleanup tp-surface-unmount)
|
||||
(:id buffer-render-state-mirror
|
||||
:storage (ebox--buffer-render-state-table)
|
||||
:current-contract same-object-alias-of-tp-client-state
|
||||
:target-contract one-way-generation-projection
|
||||
:category compatibility-mirror
|
||||
:owner ebox-compatibility
|
||||
:mutation-api project-from-committed-generation
|
||||
:generation-binding committed-generation-token
|
||||
:rollback participant-restores-prior-projection
|
||||
:rebuild-proof project-buffer-mirror-from-committed-states
|
||||
:cleanup remove-buffer-entry)
|
||||
(:id region-box-lookup-mirror
|
||||
:storage (ebox--region-box-table)
|
||||
:current-contract participant-maintained-global-projection
|
||||
:target-contract rebuildable-generation-projection
|
||||
:category compatibility-mirror
|
||||
:owner ebox-compatibility
|
||||
:mutation-api project-from-committed-generation
|
||||
:generation-binding committed-generation-token
|
||||
:rollback participant-restores-prior-projection
|
||||
:rebuild-proof project-region-mirror-from-generation-indexes
|
||||
:cleanup remove-retired-generation-entries)
|
||||
(:id derived-caches
|
||||
:storage (ebox--char-width-cache ebox--face-height-width-cache
|
||||
ebox--display-signature-cache
|
||||
ebox--render-cache-signature-cache)
|
||||
:current-contract process-or-render-local-derived-values
|
||||
:target-contract discardable-derived-values
|
||||
:category disposable-cache
|
||||
:owner ebox-cache
|
||||
:mutation-api cache-fill-and-evict
|
||||
:generation-binding cache-key-or-display-signature
|
||||
:rollback discard
|
||||
:rebuild-proof recompute-with-identical-semantic-result
|
||||
:cleanup bounded-eviction-or-clear))
|
||||
"M2a retained-state inventory.
|
||||
|
||||
The records classify every state family named by the architecture plan. They
|
||||
do not claim that later M2a target storage is already active. The inventory is
|
||||
fail-closed: adding a retained-state family requires adding a complete record
|
||||
rather than relying on an implicit default.")
|
||||
|
||||
(defun ebox-state-contract-inventory ()
|
||||
"Return a detached copy of the retained-state inventory."
|
||||
(copy-tree ebox-state-contract--inventory))
|
||||
|
||||
(defun ebox-state-contract-record (id)
|
||||
"Return a detached inventory record identified by ID, or nil."
|
||||
(when-let* ((record
|
||||
(cl-find id ebox-state-contract--inventory
|
||||
:key (lambda (item) (plist-get item :id)))))
|
||||
(copy-tree record)))
|
||||
|
||||
(defun ebox-state-contract-validate ()
|
||||
"Validate and return a detached retained-state inventory.
|
||||
Signal `ebox-state-contract-error' when a record is incomplete, duplicated, or
|
||||
uses a category outside `ebox-state-contract-categories'."
|
||||
(let ((seen (make-hash-table :test #'eq)))
|
||||
(dolist (record ebox-state-contract--inventory)
|
||||
(dolist (field ebox-state-contract-required-fields)
|
||||
(unless (plist-get record field)
|
||||
(signal 'ebox-state-contract-error
|
||||
(list :missing-field field :record record))))
|
||||
(let ((id (plist-get record :id))
|
||||
(category (plist-get record :category)))
|
||||
(when (gethash id seen)
|
||||
(signal 'ebox-state-contract-error
|
||||
(list :duplicate-id id)))
|
||||
(puthash id t seen)
|
||||
(unless (memq category ebox-state-contract-categories)
|
||||
(signal 'ebox-state-contract-error
|
||||
(list :unknown-category category :id id)))))
|
||||
(ebox-state-contract-inventory)))
|
||||
|
||||
(defun ebox-state-contract--project-buffer-mirror (entries)
|
||||
"Project BUFFER . STATE ENTRIES into a fresh compatibility mirror."
|
||||
(let ((table (make-hash-table :test #'eq))
|
||||
(missing (make-symbol "missing")))
|
||||
(dolist (entry entries)
|
||||
(unless (and (consp entry) (bufferp (car entry)) (listp (cdr entry)))
|
||||
(signal 'ebox-state-contract-error
|
||||
(list :malformed-committed-state-entry entry)))
|
||||
(unless (eq (gethash (car entry) table missing) missing)
|
||||
(signal 'ebox-state-contract-error
|
||||
(list :duplicate-buffer (car entry))))
|
||||
(puthash (car entry) (cdr entry) table))
|
||||
table))
|
||||
|
||||
(defun ebox-state-contract--project-region-mirror (entries)
|
||||
"Project BUFFER . STATE ENTRIES into a fresh region compatibility mirror."
|
||||
(let ((table (make-hash-table :test #'equal))
|
||||
(missing (make-symbol "missing")))
|
||||
(dolist (entry entries)
|
||||
(when-let* ((regions (plist-get (cdr entry) :region-box-table)))
|
||||
(unless (hash-table-p regions)
|
||||
(signal 'ebox-state-contract-error
|
||||
(list :malformed-region-index (car entry))))
|
||||
(maphash
|
||||
(lambda (region-id box)
|
||||
(let ((existing (gethash region-id table missing)))
|
||||
(when (and (not (eq existing missing)) (not (eq existing box)))
|
||||
(signal 'ebox-state-contract-error
|
||||
(list :duplicate-region-id region-id)))
|
||||
(puthash region-id box table)))
|
||||
regions)))
|
||||
table))
|
||||
|
||||
(defun ebox-state-contract-rebuild-compatibility-mirrors (entries)
|
||||
"Return fresh buffer and region mirrors projected from committed ENTRIES.
|
||||
|
||||
ENTRIES is a list of `(BUFFER . STATE)' pairs. STATE is the current v1 Ebox
|
||||
state plist held by TP client-state custody; a later checkpoint may narrow that
|
||||
custody to opaque generation correlation. The returned plist contains
|
||||
`:buffer-table' and `:region-table'; neither table mutates live Ebox state."
|
||||
(list :buffer-table
|
||||
(ebox-state-contract--project-buffer-mirror entries)
|
||||
:region-table
|
||||
(ebox-state-contract--project-region-mirror entries)))
|
||||
|
||||
(defun ebox-state-contract--hash-diff-report (expected actual)
|
||||
"Return deterministic differences and work count for two hash tables."
|
||||
(unless (and (hash-table-p expected) (hash-table-p actual))
|
||||
(signal 'wrong-type-argument (list 'hash-table-p expected actual)))
|
||||
(let ((missing (make-symbol "missing")) differences keys)
|
||||
(maphash (lambda (key _value) (push key keys)) expected)
|
||||
(maphash (lambda (key _value) (push key keys)) actual)
|
||||
(dolist (key (delete-dups keys))
|
||||
(let ((left (gethash key expected missing))
|
||||
(right (gethash key actual missing)))
|
||||
(unless (or (eq left right)
|
||||
(and (not (eq left missing))
|
||||
(not (eq right missing))
|
||||
(equal left right)))
|
||||
(push key differences))))
|
||||
(list
|
||||
:differences
|
||||
(sort differences
|
||||
(lambda (left right)
|
||||
(string< (prin1-to-string left) (prin1-to-string right))))
|
||||
:comparisons (length (delete-dups keys)))))
|
||||
|
||||
(defun ebox-state-contract-probe-compatibility-mirrors
|
||||
(entries buffer-mirror region-mirror)
|
||||
"Compare live mirrors with fresh projections from committed ENTRIES.
|
||||
|
||||
BUFFER-MIRROR and REGION-MIRROR are observed compatibility tables. The return
|
||||
value is a read-only report with deterministic mismatch lists and a boolean
|
||||
`:consistent-p'."
|
||||
(let* ((rebuilt
|
||||
(ebox-state-contract-rebuild-compatibility-mirrors entries))
|
||||
(buffer-report
|
||||
(ebox-state-contract--hash-diff-report
|
||||
(plist-get rebuilt :buffer-table) buffer-mirror))
|
||||
(region-report
|
||||
(ebox-state-contract--hash-diff-report
|
||||
(plist-get rebuilt :region-table) region-mirror))
|
||||
(buffer-differences (plist-get buffer-report :differences))
|
||||
(region-differences (plist-get region-report :differences)))
|
||||
(list :consistent-p (and (null buffer-differences)
|
||||
(null region-differences))
|
||||
:buffer-differences buffer-differences
|
||||
:region-differences region-differences
|
||||
:entry-count (length entries)
|
||||
:projected-region-count
|
||||
(hash-table-count (plist-get rebuilt :region-table))
|
||||
:buffer-comparisons (plist-get buffer-report :comparisons)
|
||||
:region-comparisons (plist-get region-report :comparisons))))
|
||||
|
||||
(provide 'ebox-state-contract)
|
||||
|
||||
;;; ebox-state-contract.el ends here
|
||||
4
ebox.el
4
ebox.el
@ -23,7 +23,8 @@
|
||||
"Directory containing the active Ebox Lisp sources.")
|
||||
|
||||
(defconst ebox--compile-sources
|
||||
'("ebox-cache.el" "ebox-source.el" "ebox-font.el" "ebox-style.el"
|
||||
'("ebox-cache.el" "ebox-source.el" "ebox-state-contract.el"
|
||||
"ebox-font.el" "ebox-style.el"
|
||||
"ebox-layout-config.el"
|
||||
"ebox-node-factory.el"
|
||||
"ebox-child-range.el" "ebox-tree.el" "ebox-measure.el"
|
||||
@ -43,6 +44,7 @@
|
||||
(require 'subr-x)
|
||||
(require 'ebox-cache)
|
||||
(require 'ebox-source)
|
||||
(require 'ebox-state-contract)
|
||||
(require 'ebox-font)
|
||||
(require 'ebox-style)
|
||||
(require 'ebox-layout-config)
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
(dolist (target '("check:" "ci:" "load:" "compile:" "test:"
|
||||
"core-tests:" "child-range-tests:" "grid-tests:" "ebox-commit-tests:"
|
||||
"surface-tests:"
|
||||
"state-contract-tests:"
|
||||
"docs-contract-tests:"
|
||||
"ci-contract-tests:" "native-rust-tests:"
|
||||
"native-build:" "performance-evaluator:" "diff-check:"))
|
||||
|
||||
@ -24,12 +24,14 @@
|
||||
|
||||
(defconst ebox-docs-test--active-files
|
||||
'("Makefile" ".github/workflows/ci.yml"
|
||||
"ebox.el" "ebox-cache.el" "ebox-style.el" "ebox-child-range.el" "ebox-tree.el"
|
||||
"ebox.el" "ebox-cache.el" "ebox-state-contract.el" "ebox-style.el"
|
||||
"ebox-child-range.el" "ebox-tree.el"
|
||||
"ebox-measure.el" "ebox-fragment.el" "ebox-render-context.el"
|
||||
"ebox-layout.el" "ebox-flex.el" "ebox-grid.el"
|
||||
"ebox-buffer-backend.el" "ebox-incremental.el" "ebox-surface.el" "ebox-dsl.el"
|
||||
"ebox-selector.el" "ebox-native-reflow.el"
|
||||
"tests/ebox-core-render-tests.el" "tests/ebox-child-range-tests.el" "tests/ebox-grid-tests.el"
|
||||
"tests/ebox-core-render-tests.el" "tests/ebox-state-contract-tests.el"
|
||||
"tests/ebox-child-range-tests.el" "tests/ebox-grid-tests.el"
|
||||
"tests/ebox-commit-tests.el" "tests/ebox-surface-tests.el"
|
||||
"tests/ebox-dsl-tests.el" "tests/ebox-flex-tests.el"
|
||||
"tests/ebox-selector-tests.el" "tests/ebox-package-tests.el"
|
||||
@ -45,7 +47,7 @@
|
||||
(defconst ebox-docs-test--targets
|
||||
'("check" "ci" "load" "compile" "core-tests" "child-range-tests" "grid-tests"
|
||||
"ebox-commit-tests" "surface-tests" "visual-check-tests" "package-tests"
|
||||
"selector-tests" "dsl-tests" "flex-tests"
|
||||
"selector-tests" "dsl-tests" "flex-tests" "state-contract-tests"
|
||||
"docs-contract-tests" "ci-contract-tests" "performance-evaluator"
|
||||
"visual-check"
|
||||
"native-rust-tests" "native-build" "package-lint" "diff-check")
|
||||
|
||||
@ -50,7 +50,8 @@
|
||||
(ert-deftest ebox-facade-loads-internal-model-modules ()
|
||||
"The public ebox facade should load all internal foundation modules."
|
||||
(require 'ebox)
|
||||
(dolist (feature '(ebox-cache ebox-source ebox-style ebox-tree ebox-measure
|
||||
(dolist (feature '(ebox-cache ebox-source ebox-state-contract
|
||||
ebox-style ebox-tree ebox-measure
|
||||
ebox-layout-config
|
||||
ebox-node-factory
|
||||
ebox-fragment ebox-layout ebox-flex ebox-grid
|
||||
@ -129,7 +130,8 @@
|
||||
(should (commandp 'ebox-byte-compile))
|
||||
(should
|
||||
(equal ebox--compile-sources
|
||||
'("ebox-cache.el" "ebox-source.el" "ebox-font.el" "ebox-style.el"
|
||||
'("ebox-cache.el" "ebox-source.el" "ebox-state-contract.el"
|
||||
"ebox-font.el" "ebox-style.el"
|
||||
"ebox-layout-config.el"
|
||||
"ebox-node-factory.el"
|
||||
"ebox-child-range.el"
|
||||
|
||||
261
tests/ebox-state-contract-tests.el
Normal file
261
tests/ebox-state-contract-tests.el
Normal file
@ -0,0 +1,261 @@
|
||||
;;; ebox-state-contract-tests.el --- M2a state ownership contracts -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'ert)
|
||||
(require 'cl-lib)
|
||||
(require 'ebox)
|
||||
(require 'ebox-state-contract)
|
||||
(require 'ebox-fixtures)
|
||||
|
||||
(defconst ebox-state-contract-test--plan-expectations
|
||||
'((runtime-generation-indexes
|
||||
(:source-index ebox-source-index/records
|
||||
ebox-source-index/handle-records ebox-source-index/host-ref-table
|
||||
:root-node :node-table :parent-table :runtime-type-count-table
|
||||
:range-ref-table :selector-id-table :selector-class-table
|
||||
:selector-type-table)
|
||||
generation-fact ebox candidate-construction-only
|
||||
immutable-generation-token
|
||||
discard-candidate source-and-runtime-index-rebuild
|
||||
generation-replacement)
|
||||
(region-generation-indexes
|
||||
(:region-id-set :region-node-table :region-box-count-table
|
||||
:region-box-table :layout-snapshots)
|
||||
generation-fact ebox candidate-layout-materialization-only
|
||||
immutable-generation-token
|
||||
discard-candidate layout-and-region-index-rebuild
|
||||
generation-replacement)
|
||||
(scroll-membership (:scroll-region-ids)
|
||||
generation-fact ebox candidate-layout-materialization-only
|
||||
immutable-generation-token
|
||||
discard-candidate layout-scroll-membership-rebuild
|
||||
generation-replacement)
|
||||
(scroll-runtime-authority
|
||||
(:scroll-state-table :scroll-offset :scroll-window
|
||||
ebox--scroll-global-state ebox--smooth-scroll-state-table
|
||||
ebox--scroll-idle-prefetch-timers)
|
||||
generation-bound-mutable-authority ebox-scroll
|
||||
stable-scroll-id-and-generation-token required
|
||||
participant-journal-restores-prior-authority
|
||||
not-rebuildable-from-cache cancel-timers-and-retire-generation)
|
||||
(native-runtime-authority
|
||||
(:native-sync-session :native-sync-pending :native-session
|
||||
ebox-native-reflow-preparation ebox-native-reflow-session)
|
||||
generation-bound-mutable-authority ebox-native
|
||||
native-candidate-confirm-or-abort required
|
||||
abort-candidate-and-keep-confirmed-session
|
||||
not-rebuildable-from-cache release-losing-session)
|
||||
(tp-client-state-custody (tp-surface-client-state)
|
||||
tp-storage-custody tp opaque-ebox-generation-correlation-only
|
||||
opaque-correlation tp-restores-client-state
|
||||
ebox-generation-remains-source-of-truth tp-surface-unmount)
|
||||
(buffer-render-state-mirror (ebox--buffer-render-state-table)
|
||||
compatibility-mirror ebox-compatibility
|
||||
project-from-committed-generation committed-generation-token
|
||||
participant-restores-prior-projection
|
||||
project-buffer-mirror-from-committed-states remove-buffer-entry)
|
||||
(region-box-lookup-mirror (ebox--region-box-table)
|
||||
compatibility-mirror ebox-compatibility
|
||||
project-from-committed-generation committed-generation-token
|
||||
participant-restores-prior-projection
|
||||
project-region-mirror-from-generation-indexes
|
||||
remove-retired-generation-entries)
|
||||
(derived-caches
|
||||
(ebox--char-width-cache ebox--face-height-width-cache
|
||||
ebox--display-signature-cache ebox--render-cache-signature-cache)
|
||||
disposable-cache ebox-cache cache-fill-and-evict
|
||||
cache-key-or-display-signature
|
||||
discard recompute-with-identical-semantic-result
|
||||
bounded-eviction-or-clear))
|
||||
"Exact M2a section 7.2 lifecycle expectations used by the E1 gate.")
|
||||
|
||||
(ert-deftest ebox-state-contract-inventory-is-complete-and-closed ()
|
||||
"Every retained-state family has one complete closed classification."
|
||||
(let* ((inventory (ebox-state-contract-validate))
|
||||
(ids (mapcar (lambda (record) (plist-get record :id)) inventory)))
|
||||
(should (= (length inventory) 9))
|
||||
(should (= (length ids) (length (delete-dups (copy-sequence ids)))))
|
||||
(dolist (record inventory)
|
||||
(dolist (field ebox-state-contract-required-fields)
|
||||
(should (plist-get record field)))
|
||||
(should (memq (plist-get record :category)
|
||||
ebox-state-contract-categories)))
|
||||
(should
|
||||
(equal
|
||||
ids
|
||||
'(runtime-generation-indexes region-generation-indexes
|
||||
scroll-membership scroll-runtime-authority native-runtime-authority
|
||||
tp-client-state-custody buffer-render-state-mirror
|
||||
region-box-lookup-mirror derived-caches)))))
|
||||
|
||||
(ert-deftest ebox-state-contract-inventory-matches-plan-table-exactly ()
|
||||
"Every plan-named storage family keeps its exact lifecycle classification."
|
||||
(dolist (expected ebox-state-contract-test--plan-expectations)
|
||||
(let ((record (ebox-state-contract-record (nth 0 expected))))
|
||||
(should (equal (plist-get record :storage) (nth 1 expected)))
|
||||
(should (eq (plist-get record :category) (nth 2 expected)))
|
||||
(should (eq (plist-get record :owner) (nth 3 expected)))
|
||||
(should (eq (plist-get record :mutation-api) (nth 4 expected)))
|
||||
(should (eq (plist-get record :generation-binding) (nth 5 expected)))
|
||||
(should (eq (plist-get record :rollback) (nth 6 expected)))
|
||||
(should (eq (plist-get record :rebuild-proof) (nth 7 expected)))
|
||||
(should (eq (plist-get record :cleanup) (nth 8 expected))))))
|
||||
|
||||
(ert-deftest ebox-state-contract-inventory-is-detached ()
|
||||
"Callers cannot mutate the retained package inventory."
|
||||
(let ((copy (ebox-state-contract-inventory)))
|
||||
(setf (plist-get (car copy) :category) 'disposable-cache)
|
||||
(should (eq (plist-get (ebox-state-contract-record
|
||||
'runtime-generation-indexes)
|
||||
:category)
|
||||
'generation-fact))))
|
||||
|
||||
(ert-deftest ebox-state-contract-never-classifies-live-authority-as-cache ()
|
||||
"Scroll and native handles retain generation-bound mutable authority."
|
||||
(dolist (id '(scroll-runtime-authority native-runtime-authority))
|
||||
(let ((record (ebox-state-contract-record id)))
|
||||
(should (eq (plist-get record :category)
|
||||
'generation-bound-mutable-authority))
|
||||
(should (eq (plist-get record :generation-binding) 'required))
|
||||
(should (eq (plist-get record :rebuild-proof)
|
||||
'not-rebuildable-from-cache)))))
|
||||
|
||||
(ert-deftest ebox-state-contract-tp-custody-is-opaque ()
|
||||
"The inventory separates current whole-state custody from its M2a target."
|
||||
(let ((record (ebox-state-contract-record 'tp-client-state-custody)))
|
||||
(should (eq (plist-get record :owner) 'tp))
|
||||
(should (eq (plist-get record :current-contract)
|
||||
'entire-ebox-state-plist))
|
||||
(should (eq (plist-get record :target-contract)
|
||||
'opaque-generation-correlation-only))
|
||||
(should (eq (plist-get record :mutation-api)
|
||||
'opaque-ebox-generation-correlation-only))
|
||||
(should (eq (plist-get record :rebuild-proof)
|
||||
'ebox-generation-remains-source-of-truth))))
|
||||
|
||||
(ert-deftest ebox-state-contract-rebuilds-current-compatibility-mirrors ()
|
||||
"Committed client state rebuilds the current buffer and region mirrors."
|
||||
(let ((buffer (generate-new-buffer " *ebox-m2a-e1-mirror*")))
|
||||
(unwind-protect
|
||||
(let* ((_rendered
|
||||
(ebox-render-to-buffer
|
||||
buffer
|
||||
(ebox-test-column
|
||||
(ebox-test-box :key 'first (ebox-test-text "first"))
|
||||
(ebox-test-box :key 'second (ebox-test-text "second")))))
|
||||
(surface (ebox-surface--live-buffer-surface buffer))
|
||||
(state (tp-surface-client-state surface))
|
||||
(before-text
|
||||
(with-current-buffer buffer
|
||||
(buffer-substring (point-min) (point-max))))
|
||||
(before-revision (tp-surface-revision surface))
|
||||
(entries (list (cons buffer state)))
|
||||
(rebuilt
|
||||
(ebox-state-contract-rebuild-compatibility-mirrors entries))
|
||||
(report
|
||||
(ebox-state-contract-probe-compatibility-mirrors
|
||||
entries ebox--buffer-render-state-table
|
||||
ebox--region-box-table)))
|
||||
(should (plist-get report :consistent-p))
|
||||
(should-not (plist-get report :buffer-differences))
|
||||
(should-not (plist-get report :region-differences))
|
||||
(should-not (eq (plist-get rebuilt :buffer-table)
|
||||
ebox--buffer-render-state-table))
|
||||
(should-not (eq (plist-get rebuilt :region-table)
|
||||
ebox--region-box-table))
|
||||
(should (eq (gethash buffer (plist-get rebuilt :buffer-table))
|
||||
state))
|
||||
(should
|
||||
(equal (hash-table-count (plist-get rebuilt :region-table))
|
||||
(hash-table-count (plist-get state :region-box-table))))
|
||||
(should (eq (tp-surface-client-state surface) state))
|
||||
(should (= (tp-surface-revision surface) before-revision))
|
||||
(should
|
||||
(equal (with-current-buffer buffer
|
||||
(buffer-substring (point-min) (point-max)))
|
||||
before-text)))
|
||||
(when (buffer-live-p buffer)
|
||||
(kill-buffer buffer)))))
|
||||
|
||||
(ert-deftest ebox-state-contract-probe-detects-mirror-drift-without-mutation ()
|
||||
"The read-only probe reports stale compatibility entries."
|
||||
(let* ((buffer (generate-new-buffer " *ebox-m2a-e1-drift*"))
|
||||
(region-id 'ebox/m2a-e1-region)
|
||||
(box (list :node-id 'ebox/m2a-e1-box))
|
||||
(regions (make-hash-table :test #'equal))
|
||||
(state (list :region-box-table regions))
|
||||
(entries (list (cons buffer state)))
|
||||
(buffer-mirror (make-hash-table :test #'eq))
|
||||
(region-mirror (make-hash-table :test #'equal))
|
||||
(stale (list :node-id 'stale)))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(puthash region-id box regions)
|
||||
(puthash buffer state buffer-mirror)
|
||||
(puthash region-id stale region-mirror)
|
||||
(let ((report
|
||||
(ebox-state-contract-probe-compatibility-mirrors
|
||||
entries buffer-mirror region-mirror)))
|
||||
(should-not (plist-get report :consistent-p))
|
||||
(should (equal (plist-get report :region-differences)
|
||||
(list region-id)))
|
||||
(should (eq (gethash region-id region-mirror) stale))))
|
||||
(when (buffer-live-p buffer)
|
||||
(kill-buffer buffer)))))
|
||||
|
||||
(ert-deftest ebox-state-contract-rejects-cross-generation-region-collision ()
|
||||
"Two committed generations cannot project different boxes for one region."
|
||||
(let ((first-buffer (generate-new-buffer " *ebox-m2a-e1-first*"))
|
||||
(second-buffer (generate-new-buffer " *ebox-m2a-e1-second*"))
|
||||
(first-regions (make-hash-table :test #'equal))
|
||||
(second-regions (make-hash-table :test #'equal)))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(puthash 'shared (list :node-id 'first) first-regions)
|
||||
(puthash 'shared (list :node-id 'second) second-regions)
|
||||
(should-error
|
||||
(ebox-state-contract-rebuild-compatibility-mirrors
|
||||
(list (cons first-buffer (list :region-box-table first-regions))
|
||||
(cons second-buffer (list :region-box-table second-regions))))
|
||||
:type 'ebox-state-contract-error))
|
||||
(when (buffer-live-p first-buffer) (kill-buffer first-buffer))
|
||||
(when (buffer-live-p second-buffer) (kill-buffer second-buffer)))))
|
||||
|
||||
(ert-deftest ebox-state-contract-probe-work-is-bounded-by-projection-size ()
|
||||
"The E1 probe performs bounded table work and never traverses a node tree."
|
||||
(let (buffers entries)
|
||||
(unwind-protect
|
||||
(progn
|
||||
(dotimes (buffer-index 64)
|
||||
(let ((buffer
|
||||
(generate-new-buffer
|
||||
(format " *ebox-m2a-e1-perf-%d*" buffer-index)))
|
||||
(regions (make-hash-table :test #'equal)))
|
||||
(push buffer buffers)
|
||||
(dotimes (region-index 16)
|
||||
(puthash (cons buffer-index region-index)
|
||||
(list :node-id (cons buffer-index region-index))
|
||||
regions))
|
||||
(push (cons buffer (list :region-box-table regions)) entries)))
|
||||
(setq entries (nreverse entries))
|
||||
(let* ((rebuilt
|
||||
(ebox-state-contract-rebuild-compatibility-mirrors entries))
|
||||
(report
|
||||
(cl-letf (((symbol-function 'ebox-tree-map)
|
||||
(lambda (&rest _arguments)
|
||||
(error "E1 probe traversed the node tree"))))
|
||||
(ebox-state-contract-probe-compatibility-mirrors
|
||||
entries (plist-get rebuilt :buffer-table)
|
||||
(plist-get rebuilt :region-table)))))
|
||||
(should (plist-get report :consistent-p))
|
||||
(should (= (plist-get report :entry-count) 64))
|
||||
(should (= (plist-get report :projected-region-count) 1024))
|
||||
(should (= (plist-get report :buffer-comparisons) 64))
|
||||
(should (= (plist-get report :region-comparisons) 1024))))
|
||||
(dolist (buffer buffers)
|
||||
(when (buffer-live-p buffer) (kill-buffer buffer))))))
|
||||
|
||||
(provide 'ebox-state-contract-tests)
|
||||
|
||||
;;; ebox-state-contract-tests.el ends here
|
||||
Loading…
Reference in New Issue
Block a user