From a5cff4fa44872bc74dcd4fac7e3d95dac2734652 Mon Sep 17 00:00:00 2001 From: Kinneyzhang Date: Mon, 31 Aug 2026 22:48:39 +0800 Subject: [PATCH] fix: complete standalone M2a E1 state inventory --- ebox-state-contract.el | 250 +++++++++++++++++- tests/ebox-state-contract-tests.el | 396 +++++++++++++++++++++++++++-- 2 files changed, 613 insertions(+), 33 deletions(-) diff --git a/ebox-state-contract.el b/ebox-state-contract.el index d8c229b..969918e 100644 --- a/ebox-state-contract.el +++ b/ebox-state-contract.el @@ -34,7 +34,11 @@ 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) + :selector-id-table :selector-class-table :selector-type-table + ebox--region-line-index ebox--render-source-index + ebox-incremental--candidate-full-source-base-index + ebox-incremental--previous-source-base-index + ebox-incremental--source-base-index) :current-contract mixed-candidate-and-committed-state :target-contract immutable-generation-value :category generation-fact @@ -46,7 +50,8 @@ :cleanup generation-replacement) (:id region-generation-indexes :storage (:region-id-set :region-node-table :region-box-count-table - :region-box-table :layout-snapshots) + :region-box-table :layout-snapshots + :render-owned-text-values ebox--render-owned-text-values) :current-contract candidate-state-plus-global-projection :target-contract immutable-generation-value :category generation-fact @@ -71,7 +76,8 @@ :storage (:scroll-state-table :scroll-offset :scroll-window ebox--scroll-global-state ebox--smooth-scroll-state-table - ebox--scroll-idle-prefetch-timers) + ebox--scroll-idle-prefetch-timers + ebox--scroll-idle-prefetch-inhibited-buffers) :current-contract global-and-state-table-mutable-handles :target-contract stable-id-plus-generation-token-authority :category generation-bound-mutable-authority @@ -83,7 +89,8 @@ :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) + ebox-native-reflow-preparation ebox-native-reflow-session + ebox-native-reflow-preparation/ready-timer) :current-contract candidate-preparation-and-confirmed-session-handles :target-contract generation-token-authority :category generation-bound-mutable-authority @@ -93,6 +100,60 @@ :rollback abort-candidate-and-keep-confirmed-session :rebuild-proof not-rebuildable-from-cache :cleanup release-losing-session) + (:id runtime-prewarm-authority + :storage (ebox--deferred-render-gc-state + ebox--deferred-render-gc-timer + ebox--deferred-render-gc-depth + ebox--deferred-render-gc-generation + ebox--reflow-cache-prewarm-timers + ebox--runtime-prewarm-jobs ebox--runtime-prewarm-timers + ebox--render-burst-records ebox--render-burst-stack) + :current-contract buffer-keyed-jobs-and-cleanup-sensitive-timers + :target-contract generation-revision-guarded-scheduler-authority + :category generation-bound-mutable-authority + :owner ebox-runtime-scheduler + :mutation-api schedule-cancel-and-revision-validate + :generation-binding buffer-and-runtime-revision + :rollback cancel-candidate-work + :rebuild-proof reschedule-from-committed-generation + :cleanup cancel-timers-jobs-and-gc-lease) + (:id incremental-batch-authority + :storage (ebox-incremental--batch-table) + :current-contract process-buffer-keyed-open-batch-state + :target-contract generation-token-bound-batch-authority + :category generation-bound-mutable-authority + :owner ebox-incremental + :mutation-api batch-begin-record-flush-or-abort + :generation-binding captured-base-generation + :rollback discard-pending-batch + :rebuild-proof batch-begin-from-committed-generation + :cleanup batch-end-or-buffer-kill) + (:id identity-allocation-authority + :storage (ebox--region-id-counter ebox--runtime-node-id-counter) + :current-contract process-monotonic-identity-counters + :target-contract allocator-output-copied-into-generation + :category generation-bound-mutable-authority + :owner ebox-identity + :mutation-api next-region-or-runtime-node-id + :generation-binding allocated-identity-enters-one-candidate + :rollback allocator-gaps-are-non-authoritative + :rebuild-proof monotonic-next-allocation + :cleanup process-lifetime-test-reset-only) + (:id buffer-surface-runtime-authority + :storage (ebox-surface--buffer-surface + ebox-surface--buffer-observer + ebox-surface--tp-observer + ebox-surface--observation-contexts + ebox-surface--context-signals) + :current-contract buffer-local-mounted-surface-and-observation-handles + :target-contract generation-revision-bound-surface-authority + :category generation-bound-mutable-authority + :owner ebox-surface + :mutation-api mount-observe-publish-and-unmount + :generation-binding buffer-surface-and-publication-revision + :rollback restore-prior-surface-signals-and-observer-bridge + :rebuild-proof not-rebuildable-from-cache + :cleanup remove-observers-cancel-contexts-and-unmount) (:id tp-client-state-custody :storage (tp-surface-client-state) :current-contract entire-ebox-state-plist @@ -126,10 +187,60 @@ :rollback participant-restores-prior-projection :rebuild-proof project-region-mirror-from-generation-indexes :cleanup remove-retired-generation-entries) + (:id font-cache + :storage (ebox-font--cache) + :current-contract process-display-capability-cache + :target-contract discardable-derived-values + :category disposable-cache + :owner ebox-font + :mutation-api cache-fill-and-evict + :generation-binding display-capability-signature + :rollback discard + :rebuild-proof resolve-font-fact-again + :cleanup ebox-font-clear-cache) (:id derived-caches - :storage (ebox--char-width-cache ebox--face-height-width-cache - ebox--display-signature-cache - ebox--render-cache-signature-cache) + :storage (ebox--box-content-render-cache ebox--char-width-cache + ebox--display-signature-cache ebox--face-height-width-cache + ebox--physical-memory-bytes + ebox--flex-content-min-width-table + ebox--flex-sized-render-observation-table + ebox--flex-slot-safety-cache ebox--layout-fragments-table + ebox--node-region-ids-cache ebox--patchable-owner-cache + ebox--render-cache-entry-side-effects-table + ebox--render-cache-ring-table + ebox--render-cache-scroll-state-region-ids + ebox--render-cache-scroll-state-retained-cost-cache + ebox--render-cache-signature-cache ebox--render-cache-table + ebox--render-output-provenance-table + ebox--render-recached-source-node-cache + ebox--render-root-cache-table-table + ebox--render-string-max-pixel-width-cache + ebox--render-string-pixel-width-cache + ebox--rendered-uniform-width-table ebox--runtime-ancestor-cache + ebox--scroll-window-cached-state ebox--space-pixel-cache + ebox--string-pixel-width-cache + ebox--string-pixel-width-cache-ring + ebox--viewport-dependent-node-ids-cache + ebox--viewport-dependent-subtree-cache + ebox--viewport-height-dependent-subtree-cache + ebox--window-line-prewarmer-table + ebox--window-line-renderer-table + ebox-cache--buffer-report-table ebox-cache--registry + ebox-canonical--declaration-fact-cache + ebox-incremental--allocated-slot-proof-cache + ebox-incremental--candidate-path-copy-origin-table + ebox-incremental--candidate-proof-node-table + ebox-native-reflow--source-cluster-cache + ebox-style--box-engine-longhand-cache + ebox-style--closed-computed-cache + ebox-style--closed-inheritance-cache + ebox-style--computed-snapshot-cache + ebox-style--declaration-cache ebox-style--property-index + ebox-style--text-engine-longhand-cache + ebox-surface--inline-style-value-cache + ebox-surface--paint-node-chain-cache + ebox-surface--paint-node-depth-cache + ebox-surface--scroll-line-fragment-cache) :current-contract process-or-render-local-derived-values :target-contract discardable-derived-values :category disposable-cache @@ -146,6 +257,114 @@ 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.") +(defconst ebox-state-contract-source-scan-exclusions + '((:symbol ebox--defer-scroll-content-index + :reason dynamic-boolean-or-index-mode + :evidence value-is-never-a-container) + (:symbol ebox--string-pixel-width-cache-ring-index + :reason numeric-eviction-cursor + :evidence value-is-an-integer-ring-position) + (:symbol ebox-native-reflow--compile-property-template-ids + :reason dynamically-bound-compile-local-scratch + :evidence let-bound-per-compile-and-never-committed) + (:symbol ebox--render-source-generations + :reason dynamically-bound-render-proof-input + :evidence let-bound-to-candidate-and-prior-generations-for-one-plan) + (:symbol ebox--render-source-states + :reason dynamically-bound-render-proof-input + :evidence let-bound-to-candidate-and-prior-states-for-one-plan) + (:symbol ebox-tree--incoming-source-indexes + :reason dynamically-bound-tree-build-input + :evidence let-bound-for-one-persistent-tree-construction) + (:symbol ebox--rebuilt-scroll-state-region-ids + :reason dynamically-bound-render-observation + :evidence collects-rebuilt-scroll-ids-for-one-transaction) + (:symbol ebox-incremental--buffer-render-state-override + :reason dynamically-bound-candidate-view + :evidence let-bound-to-one-buffer-and-candidate-state) + (:symbol ebox-incremental--candidate-base-state + :reason dynamically-bound-candidate-view + :evidence let-bound-to-one-buffer-and-published-base-state) + (:symbol ebox-incremental--candidate-incoming-source-indexes + :reason dynamically-bound-candidate-input + :evidence let-bound-for-one-logical-candidate-preparation) + (:symbol ebox--prepared-root-render + :reason transaction-local-render-scratch + :evidence dynamically-bound-around-one-prepared-render) + (:symbol ebox--scroll-window-prewarm-state + :reason render-local-prewarm-scratch + :evidence dynamically-bound-for-one-yielding-prefix-pass) + (:symbol ebox--scroll-window-render-result + :reason render-local-result-scratch + :evidence dynamically-bound-for-one-window-render) + (:symbol ebox-incremental--candidate-path-copy-trace + :reason candidate-local-proof-scratch + :evidence dynamically-bound-during-one-candidate-build) + (:symbol ebox-incremental--candidate-range-index-deltas + :reason candidate-local-index-scratch + :evidence dynamically-bound-during-one-candidate-build) + (:symbol ebox-native-reflow--compile-property-templates + :reason dynamically-bound-compile-local-scratch + :evidence let-bound-per-native-compile) + (:symbol ebox-native-reflow--compile-styles + :reason dynamically-bound-compile-local-scratch + :evidence let-bound-per-native-compile) + (:symbol ebox--flex-native-size-lines-backend + :reason optional-backend-capability-not-retained-generation-state + :evidence function-capability-selected-outside-publication) + (:symbol ebox-native-reflow--available-p + :reason process-native-loader-capability + :evidence adapter-availability-not-render-generation-state) + (:symbol ebox-native-reflow--load-attempted-p + :reason process-native-loader-control + :evidence one-time-loader-guard-not-domain-state) + (:symbol ebox-native-reflow--load-error + :reason process-native-loader-diagnostic + :evidence diagnostic-only-not-publication-authority) + (:symbol ebox-native-reflow--loaded-module-hash + :reason process-native-loader-diagnostic + :evidence loaded-binary-identity-outside-render-generation) + (:symbol ebox-native-reflow--loaded-module-path + :reason process-native-loader-diagnostic + :evidence loaded-binary-location-outside-render-generation) + (:symbol ebox-native-reflow--build-process + :reason developer-native-build-handle + :evidence optional-build-command-outside-render-runtime) + (:symbol ebox-native-reflow--last-build-report + :reason developer-native-build-diagnostic + :evidence optional-build-report-outside-render-runtime) + (:symbol ebox-native-status--diagnosis + :reason buffer-local-native-status-diagnostic + :evidence status-buffer-presentation-only) + (:symbol ebox-surface--observation-context + :reason dynamically-bound-publication-observation + :evidence let-bound-for-one-public-ebox-call) + (:symbol ebox-fragment-flex-retention-hit-count + :reason diagnostic-counter + :evidence test-and-observation-metric-only) + (:symbol ebox-fragment-flex-retention-rerender-count + :reason diagnostic-counter + :evidence test-and-observation-metric-only) + (:symbol ebox-fragment-flex-retention-store-count + :reason diagnostic-counter + :evidence test-and-observation-metric-only) + (:symbol ebox-native-reflow--flex-geometry-call-count + :reason diagnostic-counter + :evidence performance-observation-only) + (:symbol ebox-scroll-map + :reason static-ui-keymap + :evidence command-binding-table-not-render-state)) + "Source-state symbols proven not to hold retained runtime authority.") + +(defun ebox-state-contract-storage-symbols () + "Return all symbol-valued storage identities in the retained inventory." + (delete-dups + (cl-mapcan + (lambda (record) + (cl-remove-if-not #'symbolp + (copy-sequence (plist-get record :storage)))) + ebox-state-contract--inventory))) + (defun ebox-state-contract-inventory () "Return a detached copy of the retained-state inventory." (copy-tree ebox-state-contract--inventory)) @@ -161,7 +380,8 @@ rather than relying on an implicit default.") "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))) + (let ((seen-ids (make-hash-table :test #'eq)) + (seen-storage (make-hash-table :test #'eq))) (dolist (record ebox-state-contract--inventory) (dolist (field ebox-state-contract-required-fields) (unless (plist-get record field) @@ -169,13 +389,21 @@ uses a category outside `ebox-state-contract-categories'." (list :missing-field field :record record)))) (let ((id (plist-get record :id)) (category (plist-get record :category))) - (when (gethash id seen) + (when (gethash id seen-ids) (signal 'ebox-state-contract-error (list :duplicate-id id))) - (puthash id t seen) + (puthash id t seen-ids) (unless (memq category ebox-state-contract-categories) (signal 'ebox-state-contract-error - (list :unknown-category category :id id))))) + (list :unknown-category category :id id))) + (dolist (storage (plist-get record :storage)) + (when (and (symbolp storage) (not (keywordp storage))) + (when-let* ((prior-id (gethash storage seen-storage))) + (signal 'ebox-state-contract-error + (list :duplicate-storage storage + :first-record prior-id + :second-record id))) + (puthash storage id seen-storage))))) (ebox-state-contract-inventory))) (defun ebox-state-contract--project-buffer-mirror (entries) diff --git a/tests/ebox-state-contract-tests.el b/tests/ebox-state-contract-tests.el index ee58d38..fb5bb57 100644 --- a/tests/ebox-state-contract-tests.el +++ b/tests/ebox-state-contract-tests.el @@ -14,7 +14,7 @@ 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) + :selector-type-table ebox--region-line-index ebox--render-source-index) generation-fact ebox candidate-construction-only immutable-generation-token discard-candidate source-and-runtime-index-rebuild @@ -34,18 +34,52 @@ (scroll-runtime-authority (:scroll-state-table :scroll-offset :scroll-window ebox--scroll-global-state ebox--smooth-scroll-state-table - ebox--scroll-idle-prefetch-timers) + ebox--scroll-idle-prefetch-timers + ebox--scroll-idle-prefetch-inhibited-buffers) 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) + ebox-native-reflow-preparation ebox-native-reflow-session + ebox-native-reflow-preparation/ready-timer) 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) + (runtime-prewarm-authority + (ebox--deferred-render-gc-state ebox--deferred-render-gc-timer + ebox--deferred-render-gc-depth ebox--deferred-render-gc-generation + ebox--render-burst-records ebox--render-burst-stack + ebox--reflow-cache-prewarm-timers + ebox--runtime-prewarm-jobs ebox--runtime-prewarm-timers) + generation-bound-mutable-authority ebox-runtime-scheduler + schedule-cancel-and-revision-validate buffer-and-runtime-revision + cancel-candidate-work reschedule-from-committed-generation + cancel-timers-jobs-and-gc-lease) + (incremental-batch-authority (ebox-incremental--batch-table) + generation-bound-mutable-authority ebox-incremental + batch-begin-record-flush-or-abort captured-base-generation + discard-pending-batch batch-begin-from-committed-generation + batch-end-or-buffer-kill) + (identity-allocation-authority + (ebox--region-id-counter ebox--runtime-node-id-counter) + generation-bound-mutable-authority ebox-identity + next-region-or-runtime-node-id + allocated-identity-enters-one-candidate + allocator-gaps-are-non-authoritative monotonic-next-allocation + process-lifetime-test-reset-only) + (buffer-surface-runtime-authority + (ebox-surface--buffer-surface ebox-surface--buffer-observer + ebox-surface--tp-observer ebox-surface--observation-contexts + ebox-surface--context-signals) + generation-bound-mutable-authority ebox-surface + mount-observe-publish-and-unmount + buffer-surface-and-publication-revision + restore-prior-surface-signals-and-observer-bridge + not-rebuildable-from-cache + remove-observers-cancel-contexts-and-unmount) (tp-client-state-custody (tp-surface-client-state) tp-storage-custody tp opaque-ebox-generation-correlation-only opaque-correlation tp-restores-client-state @@ -61,39 +95,255 @@ participant-restores-prior-projection project-region-mirror-from-generation-indexes remove-retired-generation-entries) + (font-cache (ebox-font--cache) + disposable-cache ebox-font cache-fill-and-evict + display-capability-signature discard resolve-font-fact-again + ebox-font-clear-cache) (derived-caches (ebox--char-width-cache ebox--face-height-width-cache - ebox--display-signature-cache ebox--render-cache-signature-cache) + ebox--display-signature-cache ebox--render-cache-signature-cache + ebox-style--property-index) 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.") + "M2a section 7.2 minimum lifecycle expectations used by the E1 gate.") + +(defun ebox-state-contract-test--store-name-p (symbol) + "Return non-nil when SYMBOL has a retained-container source name." + (and (symbolp symbol) + (string-match-p + (concat + "\\(?:-table\\|-cache\\|-index\\|-indexes\\|-registry\\|-history" + "\\|-session\\|-jobs\\|-timer\\|-timers\\|-generations\\|-states" + "\\|-contexts\\|-signals\\|-records\\|-stack\\|-surface\\|-observers?" + "\\|-buffers\\)\\'") + (symbol-name symbol)))) + +(defun ebox-state-contract-test--container-constructor-p (form) + "Return non-nil when FORM directly constructs a mutable container." + (memq (car-safe form) + '(make-hash-table make-vector vector list cons copy-sequence + make-ring make-sparse-keymap make-keymap))) + +(defun ebox-state-contract-test--read-forms (file) + "Read every top-level form from FILE." + (with-temp-buffer + (insert-file-contents file) + (let (forms form) + (condition-case nil + (while t + (setq form (read (current-buffer))) + (push form forms)) + (end-of-file nil)) + (nreverse forms)))) + +(defun ebox-state-contract-test--walk-form (form function) + "Call FUNCTION for FORM and every nested cons or vector element." + (funcall function form) + (cond + ((consp form) + (ebox-state-contract-test--walk-form (car form) function) + (ebox-state-contract-test--walk-form (cdr form) function)) + ((vectorp form) + (mapc (lambda (item) + (ebox-state-contract-test--walk-form item function)) + form)))) + +(defun ebox-state-contract-test--declared-globals (forms) + "Return globals declared by top-level FORMS." + (let (globals) + (dolist (form forms) + (when (and (memq (car-safe form) '(defvar defconst defvar-local)) + (symbolp (cadr form))) + (push (cadr form) globals))) + (delete-dups globals))) + +(defun ebox-state-contract-test--form-evidence (form declared-globals) + "Return typed global-state evidence found inside FORM. +Each result is a (SYMBOL . TYPE) pair. Scalar evidence remains visible for +exclusion freshness, but only retained/container evidence is coverage-gated." + (let (evidence) + (ebox-state-contract-test--walk-form + form + (lambda (nested) + (pcase (car-safe nested) + ((or 'let 'let*) + (dolist (binding (cadr nested)) + (when (and (consp binding) + (memq (car binding) declared-globals)) + (push (cons (car binding) + (if (ebox-state-contract-test--container-constructor-p + (cadr binding)) + 'dynamic-container-binding + 'dynamic-scalar-binding)) + evidence)))) + ((or 'setq 'setq-default 'setq-local) + (let ((pairs (cdr nested))) + (while pairs + (when (memq (car pairs) declared-globals) + (push (cons (car pairs) + (if (ebox-state-contract-test--container-constructor-p + (cadr pairs)) + 'container-assignment + 'scalar-assignment)) + evidence)) + (setq pairs (cddr pairs))))) + ((or 'cl-incf 'cl-decf) + (when (memq (cadr nested) declared-globals) + (push (cons (cadr nested) 'scalar-mutation) evidence))) + ('pop + (when (memq (cadr nested) declared-globals) + (push (cons (cadr nested) 'container-mutation) evidence))) + ('push + (when (memq (caddr nested) declared-globals) + (push (cons (caddr nested) 'container-mutation) evidence))) + ('cl-pushnew + (when (memq (caddr nested) declared-globals) + (push (cons (caddr nested) 'container-mutation) evidence))) + ('add-to-list + (let ((symbol (cadr nested))) + (when (and (eq (car-safe symbol) 'quote) + (memq (cadr symbol) declared-globals)) + (push (cons (cadr symbol) 'container-mutation) evidence)))) + ('puthash + (when (memq (nth 3 nested) declared-globals) + (push (cons (nth 3 nested) 'container-mutation) evidence))) + ('remhash + (when (memq (nth 2 nested) declared-globals) + (push (cons (nth 2 nested) 'container-mutation) evidence))) + ('clrhash + (when (memq (cadr nested) declared-globals) + (push (cons (cadr nested) 'container-mutation) evidence))) + ('aset + (when (memq (cadr nested) declared-globals) + (push (cons (cadr nested) 'container-mutation) evidence))) + ('setf + (let ((pairs (cdr nested))) + (while pairs + (when (memq (car pairs) declared-globals) + (push (cons (car pairs) + (if (ebox-state-contract-test--container-constructor-p + (cadr pairs)) + 'container-assignment + 'scalar-assignment)) + evidence)) + (setq pairs (cddr pairs)))))))) + (delete-dups evidence))) + +(defconst ebox-state-contract-test--retained-evidence-types + '(buffer-local-declaration container-initializer store-name + dynamic-container-binding container-assignment container-mutation) + "Evidence types that require retained-state classification or exclusion.") + +(defun ebox-state-contract-test--source-state-evidence () + "Return typed source-backed global-state evidence, grouped by symbol." + (let* ((sources (delete-dups (copy-sequence ebox--compile-sources))) + (entries + (mapcar (lambda (source) + (cons source + (ebox-state-contract-test--read-forms source))) + sources)) + (declared + (delete-dups + (cl-mapcan (lambda (entry) + (ebox-state-contract-test--declared-globals + (cdr entry))) + entries))) + (by-symbol (make-hash-table :test #'eq))) + (cl-labels + ((record (symbol source type) + (when (memq symbol declared) + (let ((entry (or (gethash symbol by-symbol) + (list :symbol symbol :files nil + :evidence-types nil)))) + (cl-pushnew source (plist-get entry :files) :test #'equal) + (cl-pushnew type (plist-get entry :evidence-types)) + (puthash symbol entry by-symbol))))) + (dolist (entry entries) + (let ((source (car entry))) + (dolist (form (cdr entry)) + (when (and (memq (car-safe form) + '(defvar defconst defvar-local)) + (symbolp (cadr form))) + (let ((symbol (cadr form))) + (when (eq (car form) 'defvar-local) + (record symbol source 'buffer-local-declaration)) + (when (ebox-state-contract-test--store-name-p symbol) + (record symbol source 'store-name)) + (when (ebox-state-contract-test--container-constructor-p + (caddr form)) + (record symbol source 'container-initializer)))) + (dolist (item + (ebox-state-contract-test--form-evidence form declared)) + (record (car item) source (cdr item))))))) + (let (result) + (maphash + (lambda (_symbol entry) + (setf (plist-get entry :files) + (sort (plist-get entry :files) #'string<)) + (setf (plist-get entry :evidence-types) + (sort (plist-get entry :evidence-types) + (lambda (left right) + (string< (symbol-name left) (symbol-name right))))) + (push entry result)) + by-symbol) + (sort result + (lambda (left right) + (string< (symbol-name (plist-get left :symbol)) + (symbol-name (plist-get right :symbol)))))))) + +(defun ebox-state-contract-test--source-store-candidates () + "Return retained/container candidates with typed source evidence." + (cl-remove-if-not + (lambda (entry) + (cl-intersection (plist-get entry :evidence-types) + ebox-state-contract-test--retained-evidence-types)) + (ebox-state-contract-test--source-state-evidence))) (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)) + (ids (mapcar (lambda (record) (plist-get record :id)) inventory)) + (storage + (cl-mapcan + (lambda (record) + (cl-remove-if-not + (lambda (symbol) + (and (symbolp symbol) (not (keywordp symbol)))) + (copy-sequence (plist-get record :storage)))) + inventory))) (should (= (length ids) (length (delete-dups (copy-sequence ids))))) + (should (= (length storage) + (length (delete-dups (copy-sequence storage))))) (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))))) + (dolist (expected ebox-state-contract-test--plan-expectations) + (should (memq (car expected) ids))))) -(ert-deftest ebox-state-contract-inventory-matches-plan-table-exactly () - "Every plan-named storage family keeps its exact lifecycle classification." +(ert-deftest ebox-state-contract-validator-rejects-duplicate-storage () + "One source storage identity cannot belong to two inventory records." + (let* ((inventory (ebox-state-contract-inventory)) + (duplicate 'ebox--render-source-index) + (second (cadr inventory)) + (ebox-state-contract--inventory + (cons (car inventory) + (cons (plist-put second :storage + (cons duplicate + (plist-get second :storage))) + (cddr inventory))))) + (should-error (ebox-state-contract-validate) + :type 'ebox-state-contract-error))) + +(ert-deftest ebox-state-contract-inventory-covers-plan-table-minimums () + "Every plan-named storage family keeps its required 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 (cl-subsetp (nth 1 expected) (plist-get record :storage) + :test #'equal)) (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))) @@ -102,6 +352,44 @@ (should (eq (plist-get record :rebuild-proof) (nth 7 expected))) (should (eq (plist-get record :cleanup) (nth 8 expected)))))) +(ert-deftest ebox-state-contract-source-scan-is-fail-closed () + "Every source-declared retained global is classified or proven scalar." + (let* ((classified (ebox-state-contract-storage-symbols)) + (excluded + (mapcar (lambda (entry) (plist-get entry :symbol)) + ebox-state-contract-source-scan-exclusions)) + (evidence (ebox-state-contract-test--source-state-evidence)) + (evidence-symbols + (mapcar (lambda (entry) (plist-get entry :symbol)) evidence)) + (candidates (ebox-state-contract-test--source-store-candidates))) + (should (> (length candidates) 50)) + (should-not (cl-intersection classified excluded)) + (should (= (length excluded) + (length (delete-dups (copy-sequence excluded))))) + (dolist (entry ebox-state-contract-source-scan-exclusions) + (should (plist-get entry :symbol)) + (should (plist-get entry :reason)) + (should (plist-get entry :evidence)) + (should (memq (plist-get entry :symbol) evidence-symbols))) + (dolist (candidate candidates) + (should (plist-get candidate :files)) + (should (plist-get candidate :evidence-types)) + (should + (or (memq (plist-get candidate :symbol) classified) + (memq (plist-get candidate :symbol) excluded)))))) + +(ert-deftest ebox-state-contract-source-scan-sees-boundary-sentinels () + "The scanner sees buffer-local authority and transient render containers." + (let ((symbols + (mapcar (lambda (entry) (plist-get entry :symbol)) + (ebox-state-contract-test--source-store-candidates)))) + (dolist (symbol '(ebox-surface--buffer-surface + ebox-surface--observation-contexts + ebox-surface--context-signals + ebox--render-source-generations + ebox--render-source-states)) + (should (memq symbol symbols))))) + (ert-deftest ebox-state-contract-inventory-is-detached () "Callers cannot mutate the retained package inventory." (let ((copy (ebox-state-contract-inventory))) @@ -112,14 +400,78 @@ '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)) + "Cleanup-sensitive handles retain generation-bound mutable authority." + (dolist (id '(scroll-runtime-authority native-runtime-authority + runtime-prewarm-authority incremental-batch-authority + identity-allocation-authority buffer-surface-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))))) + (should (plist-get record :generation-binding)) + (should (plist-get record :cleanup)))) + (dolist (id '(scroll-runtime-authority native-runtime-authority)) + (should (eq (plist-get (ebox-state-contract-record id) :rebuild-proof) + 'not-rebuildable-from-cache)))) + +(ert-deftest ebox-state-contract-classifies-buffer-local-surface-authority () + "Mounted surfaces, signals, and observer state remain explicit authority." + (let ((record + (ebox-state-contract-record 'buffer-surface-runtime-authority))) + (dolist (symbol '(ebox-surface--buffer-surface + ebox-surface--buffer-observer + ebox-surface--tp-observer + ebox-surface--observation-contexts + ebox-surface--context-signals)) + (should (memq symbol (plist-get record :storage)))) + (should (eq (plist-get record :category) + 'generation-bound-mutable-authority)) + (should (eq (plist-get record :rebuild-proof) + 'not-rebuildable-from-cache)))) + +(ert-deftest ebox-state-contract-excludes-dynamic-render-source-containers () + "Transient old/candidate render inputs are not mislabeled as retained facts." + (dolist (symbol '(ebox--render-source-generations + ebox--render-source-states)) + (should-not (memq symbol (ebox-state-contract-storage-symbols))) + (let ((entry + (cl-find symbol ebox-state-contract-source-scan-exclusions + :key (lambda (item) (plist-get item :symbol))))) + (should entry) + (should (eq (plist-get entry :reason) + 'dynamically-bound-render-proof-input))))) + +(ert-deftest ebox-state-contract-classifies-font-and-prewarm-stores-explicitly () + "Font cache and cleanup-sensitive prewarm stores cannot hide in broad prose." + (let ((font (ebox-state-contract-record 'font-cache)) + (prewarm (ebox-state-contract-record 'runtime-prewarm-authority))) + (should (equal (plist-get font :storage) '(ebox-font--cache))) + (should (eq (plist-get font :category) 'disposable-cache)) + (dolist (symbol '(ebox--runtime-prewarm-jobs + ebox--runtime-prewarm-timers + ebox--reflow-cache-prewarm-timers + ebox--deferred-render-gc-state + ebox--deferred-render-gc-timer + ebox--deferred-render-gc-depth + ebox--deferred-render-gc-generation + ebox--render-burst-records + ebox--render-burst-stack)) + (should (memq symbol (plist-get prewarm :storage)))) + (should (eq (plist-get prewarm :category) + 'generation-bound-mutable-authority)))) + +(ert-deftest ebox-state-contract-excludes-native-compile-scratch-from-authority () + "Compile-local template ids are not mislabeled as native session authority." + (should-not + (memq 'ebox-native-reflow--compile-property-template-ids + (plist-get (ebox-state-contract-record 'native-runtime-authority) + :storage))) + (let ((exclusion + (cl-find 'ebox-native-reflow--compile-property-template-ids + ebox-state-contract-source-scan-exclusions + :key (lambda (entry) (plist-get entry :symbol))))) + (should exclusion) + (should (eq (plist-get exclusion :reason) + 'dynamically-bound-compile-local-scratch)))) (ert-deftest ebox-state-contract-tp-custody-is-opaque () "The inventory separates current whole-state custody from its M2a target."