ebox/ebox-state-contract.el
Kinneyzhang e55798e0e9
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
refactor: classify Ebox retained state for M2a E1
2026-08-31 20:11:16 +08:00

278 lines
12 KiB
EmacsLisp

;;; 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