;;; tp-transaction.el --- Structured publication transaction contracts -*- lexical-binding: t; -*- ;; Copyright (C) 2026 Geekinney ;; Author: Geekinney (kinneyzhang666@gmail.com) ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License as ;; published by the Free Software Foundation; either version 3 of ;; the License, or (at your option) any later version. ;;; Commentary: ;; Internal immutable artifacts and one-shot state machines for TP publication. ;; The package-owned entry-stage capability stored in a batch candidate drives ;; publication. This module never edits a buffer itself: `tp-reactive' drives ;; the state machine and `tp-surface' supplies and stages exact target entries ;; backed by the shared prepare journals and snapshots. ;;; Code: (require 'cl-lib) (require 'tp-core) (define-error 'tp-transaction-contract-error "Invalid TP publication transaction contract") (define-error 'tp-publication-binding-error "TP publication artifact binding mismatch" 'tp-transaction-contract-error) (define-error 'tp-publication-state-error "Invalid TP publication artifact state transition" 'tp-transaction-contract-error) (define-error 'tp-final-marker-error "Invalid TP final-accept marker" 'tp-transaction-contract-error) (defconst tp-transaction-protocol 'tp-transaction-protocol-v2 "Transaction protocol implemented by this TP package version.") (defconst tp--publication-batch-transitions '((prepared staged rolled-back discarded) (staged participants rolled-back) (participants precommit rolled-back) (precommit final-accepting rolled-back) (final-accepting committed rolled-back)) "Allowed one-way state transitions for publication batch candidates.") (defconst tp--publication-batch-terminal-states '(committed rolled-back discarded) "Terminal publication batch candidate states.") (defconst tp--publication-batch-stage-entry-functions '(tp--surface-stage-publication-entries) "Closed package-owned publication entry stage capabilities.") (defconst tp--final-marker-max-count 8 "Maximum number of opaque final markers in one transaction.") (defconst tp--final-marker-max-slot-writes 16 "Maximum total fixed marker slot writes in one transaction.") (defvar tp--transaction-id-counter 0) (defvar tp--publication-batch-id-counter 0) (defvar tp--publication-candidate-id-counter 0) (defvar tp--final-accept-id-counter 0) (defun tp--next-transaction-id () "Return a fresh monotonic internal transaction identifier." (cl-incf tp--transaction-id-counter)) (defun tp--next-publication-batch-id () "Return a fresh monotonic publication batch identifier." (cl-incf tp--publication-batch-id-counter)) (defun tp--next-publication-candidate-id () "Return a fresh monotonic target candidate identifier." (cl-incf tp--publication-candidate-id-counter)) (defun tp--next-final-accept-id () "Return a fresh monotonic final-accept identifier." (cl-incf tp--final-accept-id-counter)) (defun tp--proper-unique-list-p (items) "Return non-nil when ITEMS is a proper list with no equal duplicates." (and (proper-list-p items) (let (seen (unique t)) (dolist (item items unique) (if (member item seen) (setq unique nil) (push item seen)))))) (cl-defstruct (tp-publication-target-entry (:constructor tp--make-publication-target-entry) (:copier nil)) "One exact, immutable target binding in a publication candidate." (transaction-id nil :read-only t) (batch-id nil :read-only t) (candidate-id nil :read-only t) (surface-id nil :read-only t) (mount-ids nil :read-only t) (buffer nil :read-only t) (old-revision nil :read-only t) (new-revision nil :read-only t) (plan nil :read-only t) (diff nil :read-only t) (ledger nil :read-only t) (objects nil :read-only t) (ranges nil :read-only t) (client-state nil :read-only t) (rollback-snapshot nil :read-only t) (authority-token nil :read-only t) (mapping-generation nil :read-only t) operation-counts (shadow-expected nil :read-only t) (shadow-validator nil :read-only t) rollback-result post-rollback-state shadow-actual shadow-proven-p) (cl-defun tp--publication-target-entry-create (&key transaction-id batch-id candidate-id surface-id mount-ids buffer old-revision new-revision plan diff ledger objects ranges client-state rollback-snapshot authority-token mapping-generation shadow-expected shadow-validator operation-counts) "Create an exact TRANSACTION-ID and BATCH-ID target binding. CANDIDATE-ID, SURFACE-ID, MOUNT-IDS, BUFFER, OLD-REVISION, NEW-REVISION, PLAN, DIFF, LEDGER, OBJECTS, RANGES, CLIENT-STATE, ROLLBACK-SNAPSHOT, and AUTHORITY-TOKEN describe the target. MAPPING-GENERATION is optional. OPERATION-COUNTS is filled from the live report. SHADOW-EXPECTED and SHADOW-VALIDATOR are private comparison artifacts." (unless (and transaction-id batch-id candidate-id surface-id (bufferp buffer) (buffer-live-p buffer) (integerp old-revision) (>= old-revision 0) (integerp new-revision) (= new-revision (1+ old-revision)) (tp--proper-unique-list-p mount-ids) authority-token (or (null shadow-validator) (functionp shadow-validator))) (signal 'tp-publication-binding-error (list :target-entry transaction-id batch-id candidate-id surface-id buffer old-revision new-revision mount-ids authority-token))) (tp--make-publication-target-entry :transaction-id transaction-id :batch-id batch-id :candidate-id candidate-id :surface-id (tp--copy-property-value surface-id) :mount-ids (tp--copy-property-value mount-ids) :buffer buffer :old-revision old-revision :new-revision new-revision :plan plan :diff (tp--copy-property-value diff) :ledger ledger :objects objects :ranges ranges :client-state (tp--copy-property-value client-state) :rollback-snapshot rollback-snapshot :authority-token authority-token :mapping-generation mapping-generation :operation-counts (tp--copy-property-value operation-counts) :shadow-expected shadow-expected :shadow-validator shadow-validator)) (cl-defstruct (tp-publication-outcome-entry (:constructor tp--make-publication-outcome-entry) (:copier nil)) "Frozen observational binding copied from one target entry." (batch-id nil :read-only t) (candidate-id nil :read-only t) (surface-id nil :read-only t) (mount-ids nil :read-only t) (buffer nil :read-only t) (authority-token nil :read-only t) (old-revision nil :read-only t) (new-revision nil :read-only t) (mapping-generation nil :read-only t) (operation-counts nil :read-only t)) (defun tp--publication-outcome-entry-from-target (entry) "Return an observational outcome entry frozen from target ENTRY." (tp--make-publication-outcome-entry :batch-id (tp-publication-target-entry-batch-id entry) :candidate-id (tp-publication-target-entry-candidate-id entry) :surface-id (tp--copy-property-value (tp-publication-target-entry-surface-id entry)) :mount-ids (tp--copy-property-value (tp-publication-target-entry-mount-ids entry)) :buffer (tp-publication-target-entry-buffer entry) :authority-token (tp-publication-target-entry-authority-token entry) :old-revision (tp-publication-target-entry-old-revision entry) :new-revision (tp-publication-target-entry-new-revision entry) :mapping-generation (tp-publication-target-entry-mapping-generation entry) :operation-counts (tp--copy-property-value (tp-publication-target-entry-operation-counts entry)))) (cl-defstruct (tp-committed-success-outcome (:constructor tp--make-committed-success-outcome) (:copier nil)) "Preallocated immutable evidence finalized only after final accept." (tag nil :read-only t) (transaction-id nil :read-only t) (final-accept-id nil :read-only t) (batch-id nil :read-only t) (entries nil :read-only t) (mapping-generation nil :read-only t) (operation-counts nil :read-only t) (phase-timings nil :read-only t) (diagnostics nil :read-only t) (marker-count nil :read-only t)) (defconst tp--committed-success-outcome-tag-slot 1 "Private record offset for the sole postaccept success-tag write.") (cl-defstruct (tp-publication-failure-outcome (:constructor tp--make-publication-failure-outcome) (:copier nil)) "Immutable observational evidence built after publication rollback." (tag 'publication-failure :read-only t) (transaction-id nil :read-only t) (batch-id nil :read-only t) (failure-stage nil :read-only t) (primary-condition nil :read-only t) (target-results nil :read-only t) (rollback-failures nil :read-only t) (post-rollback-state nil :read-only t) (diagnostics nil :read-only t)) (cl-defstruct (tp-publication-batch-candidate (:constructor tp--make-publication-batch-candidate) (:copier nil)) "A one-shot structured publication authority over shared rollback state." (transaction-id nil :read-only t) (id nil :read-only t) state resolution (entries nil :read-only t) (participants nil :read-only t) (journals nil :read-only t) (stage-entries nil :read-only t) final-accept (final-accept-id nil :read-only t) diagnostics operation-counts phase-timings markers success-outcome-draft outcome shadow-proof) (defun tp--publication-target-entry-bound-p (entry transaction-id batch-id) "Return non-nil when ENTRY is exactly bound to TRANSACTION-ID and BATCH-ID." (and (tp-publication-target-entry-p entry) (equal transaction-id (tp-publication-target-entry-transaction-id entry)) (equal batch-id (tp-publication-target-entry-batch-id entry)))) (defun tp--publication-batch-entries-valid-p (entries transaction-id batch-id) "Return non-nil when ENTRIES bind TRANSACTION-ID and BATCH-ID exactly." (and (proper-list-p entries) entries (cl-every (lambda (entry) (tp--publication-target-entry-bound-p entry transaction-id batch-id)) entries) (tp--proper-unique-list-p (mapcar #'tp-publication-target-entry-candidate-id entries)) (tp--proper-unique-list-p (mapcar #'tp-publication-target-entry-surface-id entries)) (tp--proper-unique-list-p (mapcar #'tp-publication-target-entry-authority-token entries)) (let ((generation (tp-publication-target-entry-mapping-generation (car entries)))) (cl-every (lambda (entry) (equal generation (tp-publication-target-entry-mapping-generation entry))) entries)))) (cl-defun tp--publication-batch-prepare (&key transaction-id batch-id entries participants journals stage-entries final-accept diagnostics) "Prepare TRANSACTION-ID and BATCH-ID over exact ENTRIES. PARTICIPANTS is an ordered reference vector, JOURNALS is the shared rollback view, STAGE-ENTRIES is an optional package-owned execution capability, FINAL-ACCEPT is the single coordinator function, and DIAGNOSTICS contains known preaccept observations." (unless (and transaction-id batch-id (tp--publication-batch-entries-valid-p entries transaction-id batch-id) (vectorp participants) (or (null stage-entries) (and (symbolp stage-entries) (memq stage-entries tp--publication-batch-stage-entry-functions) (fboundp stage-entries))) (functionp final-accept)) (signal 'tp-publication-binding-error (list :batch transaction-id batch-id entries participants))) (tp--make-publication-batch-candidate :transaction-id transaction-id :id batch-id :state 'prepared :entries (copy-sequence entries) :participants participants :journals journals :stage-entries stage-entries :final-accept final-accept :final-accept-id (tp--next-final-accept-id) :diagnostics (tp--copy-property-value diagnostics))) (defun tp--publication-batch-execute-stage (candidate) "Execute CANDIDATE's package-owned entry stage capability exactly once." (unless (and (tp-publication-batch-candidate-p candidate) (eq (tp-publication-batch-candidate-state candidate) 'staged) (memq (tp-publication-batch-candidate-stage-entries candidate) tp--publication-batch-stage-entry-functions)) (signal 'tp-publication-state-error (list :batch-stage candidate))) (funcall (tp-publication-batch-candidate-stage-entries candidate) candidate)) (defun tp--publication-batch-terminal-p (candidate) "Return non-nil when CANDIDATE has one terminal disposition." (and (tp-publication-batch-candidate-p candidate) (memq (tp-publication-batch-candidate-state candidate) tp--publication-batch-terminal-states))) (defun tp--publication-batch-transition (candidate next) "Move CANDIDATE to NEXT through its one-way state machine." (unless (tp-publication-batch-candidate-p candidate) (signal 'wrong-type-argument (list 'tp-publication-batch-candidate-p candidate))) (let* ((current (tp-publication-batch-candidate-state candidate)) (allowed (cdr (assq current tp--publication-batch-transitions)))) (unless (memq next allowed) (signal 'tp-publication-state-error (list :batch-state current next (tp-publication-batch-candidate-id candidate)))) (setf (tp-publication-batch-candidate-state candidate) next) (when (memq next tp--publication-batch-terminal-states) (setf (tp-publication-batch-candidate-resolution candidate) next)) candidate)) (defun tp--publication-batch-discard (candidate reason) "Discard prepared CANDIDATE for REASON and return nil." (unless (and (tp-publication-batch-candidate-p candidate) (eq (tp-publication-batch-candidate-state candidate) 'prepared)) (signal 'tp-publication-state-error (list :discard (and (tp-publication-batch-candidate-p candidate) (tp-publication-batch-candidate-state candidate))))) (setf (tp-publication-batch-candidate-diagnostics candidate) (append (tp-publication-batch-candidate-diagnostics candidate) (list (list :discard reason)))) (tp--publication-batch-transition candidate 'discarded) nil) (defun tp--committed-success-outcome-draft (candidate operation-counts phase-timings diagnostics marker-count) "Preallocate CANDIDATE evidence using OPERATION-COUNTS and PHASE-TIMINGS. DIAGNOSTICS contains known preaccept failures and MARKER-COUNT is fixed." (unless (tp-publication-batch-candidate-p candidate) (signal 'wrong-type-argument (list 'tp-publication-batch-candidate-p candidate))) (setf (tp-publication-batch-candidate-operation-counts candidate) (tp--copy-property-value operation-counts) (tp-publication-batch-candidate-phase-timings candidate) (tp--copy-property-value phase-timings) (tp-publication-batch-candidate-diagnostics candidate) (tp--copy-property-value diagnostics)) (tp--make-committed-success-outcome :transaction-id (tp-publication-batch-candidate-transaction-id candidate) :final-accept-id (tp-publication-batch-candidate-final-accept-id candidate) :batch-id (tp-publication-batch-candidate-id candidate) :entries (mapcar #'tp--publication-outcome-entry-from-target (tp-publication-batch-candidate-entries candidate)) :mapping-generation (let ((entries (tp-publication-batch-candidate-entries candidate))) (and entries (tp-publication-target-entry-mapping-generation (car entries)))) :operation-counts (tp--copy-property-value (tp-publication-batch-candidate-operation-counts candidate)) :phase-timings (tp--copy-property-value (tp-publication-batch-candidate-phase-timings candidate)) :diagnostics (tp--copy-property-value (tp-publication-batch-candidate-diagnostics candidate)) :marker-count marker-count)) (defun tp--committed-success-outcome-finalize (outcome) "Finalize preallocated OUTCOME exactly once after final accept." (unless (and (tp-committed-success-outcome-p outcome) (null (tp-committed-success-outcome-tag outcome))) (signal 'tp-publication-state-error (list :success-outcome outcome))) ;; The slot is read-only to every accessor. This single fixed vector write is ;; the coordinator's postaccept tag finalization primitive. (aset outcome tp--committed-success-outcome-tag-slot 'committed-success) outcome) (defun tp--publication-outcome-entry-matches-target-p (outcome-entry target) "Return non-nil when OUTCOME-ENTRY is exactly bound to TARGET." (and (tp-publication-outcome-entry-p outcome-entry) (tp-publication-target-entry-p target) (equal (tp-publication-outcome-entry-batch-id outcome-entry) (tp-publication-target-entry-batch-id target)) (equal (tp-publication-outcome-entry-candidate-id outcome-entry) (tp-publication-target-entry-candidate-id target)) (equal (tp-publication-outcome-entry-surface-id outcome-entry) (tp-publication-target-entry-surface-id target)) (equal (tp-publication-outcome-entry-mount-ids outcome-entry) (tp-publication-target-entry-mount-ids target)) (eq (tp-publication-outcome-entry-buffer outcome-entry) (tp-publication-target-entry-buffer target)) (eq (tp-publication-outcome-entry-authority-token outcome-entry) (tp-publication-target-entry-authority-token target)) (= (tp-publication-outcome-entry-old-revision outcome-entry) (tp-publication-target-entry-old-revision target)) (= (tp-publication-outcome-entry-new-revision outcome-entry) (tp-publication-target-entry-new-revision target)) (equal (tp-publication-outcome-entry-mapping-generation outcome-entry) (tp-publication-target-entry-mapping-generation target)) (equal (tp-publication-outcome-entry-operation-counts outcome-entry) (tp-publication-target-entry-operation-counts target)))) (defun tp--committed-success-outcome-valid-for-p (outcome candidate &optional mapping-generation) "Purely validate OUTCOME against exact CANDIDATE and MAPPING-GENERATION." (and (tp-committed-success-outcome-p outcome) (eq (tp-committed-success-outcome-tag outcome) 'committed-success) (tp-publication-batch-candidate-p candidate) (eq (tp-publication-batch-candidate-state candidate) 'committed) (equal (tp-committed-success-outcome-transaction-id outcome) (tp-publication-batch-candidate-transaction-id candidate)) (equal (tp-committed-success-outcome-batch-id outcome) (tp-publication-batch-candidate-id candidate)) (equal (tp-committed-success-outcome-operation-counts outcome) (tp-publication-batch-candidate-operation-counts candidate)) (equal (tp-committed-success-outcome-phase-timings outcome) (tp-publication-batch-candidate-phase-timings candidate)) (equal (tp-committed-success-outcome-diagnostics outcome) (tp-publication-batch-candidate-diagnostics candidate)) (= (tp-committed-success-outcome-marker-count outcome) (if (consp (tp-publication-batch-candidate-markers candidate)) (cdr (tp-publication-batch-candidate-markers candidate)) 0)) (or (null mapping-generation) (equal mapping-generation (tp-committed-success-outcome-mapping-generation outcome))) (let ((outcome-entries (append (tp-committed-success-outcome-entries outcome) nil)) (targets (tp-publication-batch-candidate-entries candidate))) (and (= (length outcome-entries) (length targets)) (cl-every #'identity (cl-mapcar #'tp--publication-outcome-entry-matches-target-p outcome-entries targets)))))) (defun tp--committed-success-outcome-snapshot (outcome) "Return a defensive observational plist for committed OUTCOME." (unless (and (tp-committed-success-outcome-p outcome) (eq (tp-committed-success-outcome-tag outcome) 'committed-success)) (signal 'tp-publication-binding-error (list :outcome outcome))) (list :tag 'committed-success :transaction-id (tp-committed-success-outcome-transaction-id outcome) :final-accept-id (tp-committed-success-outcome-final-accept-id outcome) :batch-id (tp-committed-success-outcome-batch-id outcome) :entries (mapcar (lambda (entry) (list :batch-id (tp-publication-outcome-entry-batch-id entry) :candidate-id (tp-publication-outcome-entry-candidate-id entry) :surface-id (tp--copy-property-value (tp-publication-outcome-entry-surface-id entry)) :mount-ids (tp--copy-property-value (tp-publication-outcome-entry-mount-ids entry)) :buffer (tp-publication-outcome-entry-buffer entry) :authority-token (tp-publication-outcome-entry-authority-token entry) :old-revision (tp-publication-outcome-entry-old-revision entry) :new-revision (tp-publication-outcome-entry-new-revision entry) :mapping-generation (tp-publication-outcome-entry-mapping-generation entry) :operation-counts (tp--copy-property-value (tp-publication-outcome-entry-operation-counts entry)))) (append (tp-committed-success-outcome-entries outcome) nil)) :mapping-generation (tp-committed-success-outcome-mapping-generation outcome) :operation-counts (tp--copy-property-value (tp-committed-success-outcome-operation-counts outcome)) :phase-timings (tp--copy-property-value (tp-committed-success-outcome-phase-timings outcome)) :diagnostics (tp--copy-property-value (tp-committed-success-outcome-diagnostics outcome)) :marker-count (tp-committed-success-outcome-marker-count outcome))) (defun tp--publication-failure-outcome-valid-for-p (outcome candidate) "Purely validate failure OUTCOME against rolled-back CANDIDATE." (and (tp-publication-failure-outcome-p outcome) (eq (tp-publication-failure-outcome-tag outcome) 'publication-failure) (tp-publication-batch-candidate-p candidate) (eq (tp-publication-batch-candidate-state candidate) 'rolled-back) (equal (tp-publication-failure-outcome-transaction-id outcome) (tp-publication-batch-candidate-transaction-id candidate)) (equal (tp-publication-failure-outcome-batch-id outcome) (tp-publication-batch-candidate-id candidate)) (let ((results (tp-publication-failure-outcome-target-results outcome)) (entries (tp-publication-batch-candidate-entries candidate))) (and (= (length results) (length entries)) (cl-every #'identity (cl-mapcar (lambda (result entry) (and (equal (plist-get result :candidate-id) (tp-publication-target-entry-candidate-id entry)) (equal (plist-get result :surface-id) (tp-publication-target-entry-surface-id entry)))) results entries)))))) (defun tp--publication-failure-outcome-create (candidate stage primary-condition rollback-failures diagnostics) "Build rolled-back CANDIDATE evidence for STAGE and PRIMARY-CONDITION. ROLLBACK-FAILURES and DIAGNOSTICS are observational snapshots." (unless (and (tp-publication-batch-candidate-p candidate) (eq (tp-publication-batch-candidate-state candidate) 'rolled-back)) (signal 'tp-publication-state-error (list :failure-outcome candidate))) (let ((entries (tp-publication-batch-candidate-entries candidate))) (tp--make-publication-failure-outcome :transaction-id (tp-publication-batch-candidate-transaction-id candidate) :batch-id (tp-publication-batch-candidate-id candidate) :failure-stage stage :primary-condition (tp--copy-property-value primary-condition) :target-results (mapcar (lambda (entry) (list :candidate-id (tp-publication-target-entry-candidate-id entry) :surface-id (tp--copy-property-value (tp-publication-target-entry-surface-id entry)) :result (tp--copy-property-value (tp-publication-target-entry-rollback-result entry)))) entries) :rollback-failures (tp--copy-property-value rollback-failures) :post-rollback-state (mapcar (lambda (entry) (list :candidate-id (tp-publication-target-entry-candidate-id entry) :surface-id (tp--copy-property-value (tp-publication-target-entry-surface-id entry)) :state (tp--copy-property-value (tp-publication-target-entry-post-rollback-state entry)))) entries) :diagnostics (tp--copy-property-value diagnostics)))) (cl-defstruct (tp--final-marker-operation (:constructor tp--make-final-marker-operation) (:copier nil)) "One trusted operation descriptor resolved before final accept." (key nil :read-only t) (validate nil :read-only t) (apply nil :read-only t) (restore nil :read-only t) (max-slot-writes nil :read-only t)) (cl-defstruct (tp-final-marker-expectation (:constructor tp--make-final-marker-expectation) (:copier nil)) "One prebuilt expected scalar stored in a fixed vector slot." (target nil :read-only t) (index nil :read-only t) (value nil :read-only t)) (cl-defstruct (tp-final-marker-slot-write (:constructor tp--make-final-marker-slot-write) (:copier nil)) "One prebuilt fixed vector slot write." (target nil :read-only t) (index nil :read-only t) (value nil :read-only t)) (defun tp--final-marker-vector-index-p (target index) "Return non-nil when INDEX denotes a writable slot in TARGET." (and (vectorp target) (integerp index) (<= 0 index) (< index (length target)))) (cl-defun tp--final-marker-expectation-create (&key target index value) "Create an expectation that TARGET slot INDEX currently equals VALUE." (unless (tp--final-marker-vector-index-p target index) (signal 'tp-final-marker-error (list :expectation target index))) (tp--make-final-marker-expectation :target target :index index :value value)) (cl-defun tp-final-marker-expectation-create (&key target index value) "Create a final-marker expectation for TARGET slot INDEX and VALUE." (tp--final-marker-expectation-create :target target :index index :value value)) (cl-defun tp--final-marker-slot-write-create (&key target index value) "Create one prebuilt write of VALUE into TARGET slot INDEX." (unless (tp--final-marker-vector-index-p target index) (signal 'tp-final-marker-error (list :slot-write target index))) (tp--make-final-marker-slot-write :target target :index index :value value)) (cl-defun tp-final-marker-slot-write-create (&key target index value) "Create one bounded final-marker write to TARGET slot INDEX with VALUE." (tp--final-marker-slot-write-create :target target :index index :value value)) (defun tp--final-marker-expectation-current-p (expectation) "Return non-nil when EXPECTATION matches its current fixed slot." (and (tp-final-marker-expectation-p expectation) (equal (aref (tp-final-marker-expectation-target expectation) (tp-final-marker-expectation-index expectation)) (tp-final-marker-expectation-value expectation)))) (defun tp--final-marker-slot-write-shape-p (write) "Return non-nil when WRITE still denotes one valid fixed vector slot." (and (tp-final-marker-slot-write-p write) (tp--final-marker-vector-index-p (tp-final-marker-slot-write-target write) (tp-final-marker-slot-write-index write)))) (defun tp--final-marker-vector-payload-shape-p (marker) "Return non-nil when MARKER has exact paired fixed vector slot payloads." (let ((next (tp-final-accept-marker-next-values marker)) (inverse (tp-final-accept-marker-inverse-values marker)) (count (tp-final-accept-marker-slot-write-count marker)) seen valid) (setq valid (and (vectorp next) (vectorp inverse) (= (length next) count) (= (length inverse) count))) (let ((index 0)) (while (and valid (< index count)) (let ((next-write (aref next index)) (inverse-write (aref inverse index))) (setq valid (and (tp--final-marker-slot-write-shape-p next-write) (tp--final-marker-slot-write-shape-p inverse-write) (eq (tp-final-marker-slot-write-target next-write) (tp-final-marker-slot-write-target inverse-write)) (= (tp-final-marker-slot-write-index next-write) (tp-final-marker-slot-write-index inverse-write)) (not (cl-find-if (lambda (entry) (and (eq (car entry) (tp-final-marker-slot-write-target next-write)) (= (cdr entry) (tp-final-marker-slot-write-index next-write)))) seen)))) (when valid (push (cons (tp-final-marker-slot-write-target next-write) (tp-final-marker-slot-write-index next-write)) seen))) (setq index (1+ index)))) valid)) (defun tp--final-marker-vector-slots-validate (marker) "Validate MARKER expectations and inverse values without changing state." (and (tp--final-marker-vector-payload-shape-p marker) (tp--final-marker-expectation-current-p (tp-final-accept-marker-expected-token marker)) (tp--final-marker-expectation-current-p (tp-final-accept-marker-expected-version marker)) (let* ((inverse (tp-final-accept-marker-inverse-values marker)) (count (length inverse)) (index 0) (valid t)) (while (and valid (< index count)) (let ((write (aref inverse index))) (setq valid (equal (aref (tp-final-marker-slot-write-target write) (tp-final-marker-slot-write-index write)) (tp-final-marker-slot-write-value write)))) (setq index (1+ index))) valid))) (defun tp--final-marker-vector-slots-apply (marker) "Apply MARKER's fixed next-value vector slots in order." (let* ((writes (tp-final-accept-marker-next-values marker)) (count (length writes)) (index 0)) (while (< index count) (let ((write (aref writes index))) (aset (tp-final-marker-slot-write-target write) (tp-final-marker-slot-write-index write) (tp-final-marker-slot-write-value write))) (setq index (1+ index))))) (defun tp--final-marker-vector-slots-restore (marker) "Restore MARKER's fixed inverse-value vector slots in reverse order." (let* ((writes (tp-final-accept-marker-inverse-values marker)) (index (1- (length writes)))) (while (>= index 0) (let ((write (aref writes index))) (aset (tp-final-marker-slot-write-target write) (tp-final-marker-slot-write-index write) (tp-final-marker-slot-write-value write))) (setq index (1- index))))) (defconst tp--final-marker-operation-whitelist (list (tp--make-final-marker-operation :key 'tp-vector-slots/v1 :validate (symbol-function 'tp--final-marker-vector-slots-validate) :apply (symbol-function 'tp--final-marker-vector-slots-apply) :restore (symbol-function 'tp--final-marker-vector-slots-restore) :max-slot-writes tp--final-marker-max-slot-writes)) "Closed package-owned final-marker primitive whitelist.") (defun tp--final-marker-operation-resolve (key) "Return the trusted final marker operation registered for KEY." (let ((operation (cl-find key tp--final-marker-operation-whitelist :key #'tp--final-marker-operation-key :test #'eq))) (or operation (signal 'tp-final-marker-error (list :operation-not-whitelisted key))))) (cl-defstruct (tp-final-accept-marker (:constructor tp--make-final-accept-marker) (:copier nil)) "One opaque, bounded, one-shot final-accept authority marker." (owner-key nil :read-only t) (expected-token nil :read-only t) (expected-version nil :read-only t) (next-values nil :read-only t) (inverse-values nil :read-only t) (slot-write-count nil :read-only t) (operation-key nil :read-only t) (operation nil :read-only t) state) (cl-defun tp--final-accept-marker-create (&key owner-key expected-token expected-version next-values inverse-values slot-write-count operation-key) "Create an OWNER-KEY marker after resolving OPERATION-KEY. EXPECTED-TOKEN and EXPECTED-VERSION bind owner state. NEXT-VALUES and INVERSE-VALUES are opaque prebuilt payloads with fixed SLOT-WRITE-COUNT." (let ((operation (tp--final-marker-operation-resolve operation-key))) (unless (and owner-key (tp-final-marker-expectation-p expected-token) (tp-final-marker-expectation-p expected-version) (integerp (tp-final-marker-expectation-value expected-version)) (>= (tp-final-marker-expectation-value expected-version) 0) (integerp slot-write-count) (> slot-write-count 0) (<= slot-write-count (tp--final-marker-operation-max-slot-writes operation))) (signal 'tp-final-marker-error (list :marker owner-key expected-token expected-version slot-write-count operation-key))) (let ((marker (tp--make-final-accept-marker :owner-key (tp--copy-property-value owner-key) :expected-token expected-token :expected-version expected-version :next-values (and (vectorp next-values) (copy-sequence next-values)) :inverse-values (and (vectorp inverse-values) (copy-sequence inverse-values)) :slot-write-count slot-write-count :operation-key operation-key :operation operation :state 'prepared))) (unless (tp--final-marker-vector-payload-shape-p marker) (signal 'tp-final-marker-error (list :marker-payload owner-key slot-write-count))) marker))) (defun tp--final-accept-marker-validate (marker) "Validate MARKER's expected owner state before the critical section." (unless (and (tp-final-accept-marker-p marker) (eq (tp-final-accept-marker-state marker) 'prepared) (funcall (tp--final-marker-operation-validate (tp-final-accept-marker-operation marker)) marker)) (signal 'tp-final-marker-error (list :expected-state (and (tp-final-accept-marker-p marker) (tp-final-accept-marker-owner-key marker))))) marker) (provide 'tp-transaction) ;;; tp-transaction.el ends here