refactor!: publish transactions exclusively through protocol v2

This commit is contained in:
Kinneyzhang 2026-09-05 05:25:29 +08:00
parent 25cb66c040
commit c05174ff9d
15 changed files with 290 additions and 577 deletions

View File

@ -2,19 +2,20 @@
All notable changes to the tp library are documented here.
## 1.0.1 (Unreleased)
## 2.0.0 (Unreleased)
### Added
- A standalone retained surface runtime with pure defensive plans, prepare-scoped stable objects, keyed/positional reconciliation, content and properties capabilities, marker-backed range anchors, object/mount indexes, scoped updates, opaque client state, generic reports, and lifecycle cleanup.
- Exact signal-to-binding and binding-to-binding dependency tracking with conditional rewiring, equality cutoffs, batched transactions, nested-write stabilization, owner disposal, buffer-scoped sources, variable adapters, cycle paths, and public structural counters.
- Atomic single- and multi-surface publication with candidate source values, prepare-all/publish-all ordering, explicit property journals, rollback-capable transaction participants, observer isolation, and authoritative kill-buffer cleanup.
- Transaction protocol v2 artifacts: transaction-scoped publication batches, structured v1 participant bridges, bounded opaque final-accept markers using the closed `tp-vector-slots/v1` primitive, immutable tagged outcomes, and property/revision shadow proofs over the selected single live writer.
- `tp-runtime-manifest`, advertising `tp-transaction-protocol-v1+v2` without removing the v1 participant route.
- Supported `tp-transaction-execution-route` kill switch with `structured` as
the default live authority and `v1` as the complete legacy rollback route.
- Transaction protocol v2: transaction-scoped publication batches, structured
participants, bounded opaque final-accept markers using the closed
`tp-vector-slots/v1` primitive, immutable tagged outcomes, and one live writer.
- `tp-runtime-manifest`, advertising `tp-transaction-protocol-v2` and package
version 2.0.0.
- Public `tp-transaction-participate-v2` registration for cross-package
structured participants, with the v1 call-shape adapter retained unchanged.
structured participants.
- Native property policies and contribution composition with explicit nil/absence, normalization, validation, equality, merge, projection, named direct styles, and explicit `tp-computed` value sources.
- `tp-propertize`, `tp-apply`, and `tp-watch` as the one-shot string, one-shot buffer-range, and reactive existing-text conveniences over the same direct property/surface core.
- Retained logical objects with `tp-object-retain` and `tp-object-attach-fragment`, allowing one object to own multiple disjoint physical fragments without placing handles or positions in plans.
@ -22,18 +23,9 @@ All notable changes to the tp library are documented here.
### Changed
- Structured publication batches now own the single live surface-entry loop,
- Structured publication batches own the single live surface-entry loop,
participant stage/precommit/commit vector, and candidate-bound final accept.
The public `tp-transaction-participate` API remains a v1 adapter over the same
participant object; structured and v1 routes are mutually exclusive.
- Runtime manifest fields now report active structured batch execution and the
supported route list/option, single-writer invariant, and retained v1
adapter/rollback route. The package version remains 1.0.1 because
this is the planned authority cutover within the unreleased 1.0.1 protocol,
with no public API removal or dependency change.
- Package metadata now distinguishes builds that include
`tp-transaction.el`; consumers can require TP 1.0.1 without an older 1.0.0
development snapshot being accepted as a complete transaction runtime.
- Package metadata now identifies the v2-only breaking transaction contract.
- `define-tp` and `define-tps` now define static or parameterized direct declaration recipes. Applying a recipe produces ordinary properties and never publishes runtime identity metadata.
- TP is no longer a CSS engine. Selector, stylesheet, specificity, origin/importance, cascade layer, CSS-wide value, custom property, winner, and provenance behavior belongs to the independent ECSS package.
- Function-valued properties are always literal. Only values wrapped by `tp-computed` execute and participate in dependency collection.
@ -42,6 +34,12 @@ All notable changes to the tp library are documented here.
### Removed
- The public `tp-transaction-participate` v1 facade. Replace
`(tp-transaction-participate KEY PUBLISH ROLLBACK)` with
`(tp-transaction-participate-v2 :key KEY :stage PUBLISH :rollback ROLLBACK)`.
- The v1 publication writer, execution-route kill switch, artifact-mode switch,
and their runtime manifest claims.
- `tp-render.el`, `tp-stack.el`, the scan-driven renderer, layer-to-buffer registry, and duplicate managed transaction path.
- Managed stack mutation, attach/detach, diagnostics, and lifecycle APIs that depended on inline stack storage.
- `tp-text`, `$variable` declaration syntax, automatic layer refresh, and character-level `tp-name`/`tp-layers`/`tp-meta` runtime storage.

View File

@ -5,7 +5,7 @@
# make test-shuffled # run the suite in a random order (SHUFFLE_SEED=n reproduces)
# make test-m0a # run current TP completion characterization
# make test-m1a # run additive transaction contract + fault gates
# make test-v1 # run every legacy suite with v2 artifacts disabled
# make test-c1b # run the v2-only transaction regression gate
# make doctest # execute README examples against the code
# make benchmark # run reproducible correctness-first benchmarks
# make compile # byte-compile the library modules
@ -27,12 +27,11 @@ LOADPATH = -L . -L $(TEST_DIR) -L examples $(LOAD_EXTRA)
SRC = tp-core.el tp-style.el tp-transaction.el tp-reactive.el tp-surface.el tp-layer.el tp-ops.el tp-search.el \
tp-query.el tp-palette.el tp-builtins.el tp.el
TESTS = $(wildcard $(TEST_DIR)/*-tests.el)
V1_TESTS = $(filter-out $(TEST_DIR)/tp-transaction-tests.el,$(TESTS))
TEST_SUPPORT = $(TEST_DIR)/tp-doctest.el $(TEST_DIR)/tp-run-shuffled.el
EXAMPLES = $(wildcard examples/*.el)
DEV = $(TEST_SUPPORT) $(EXAMPLES) tp-benchmark.el
.PHONY: test test-m0a test-m1a test-v1 test-shuffled doctest benchmark compile compile-all checkdoc package-lint diff-check clean
.PHONY: test test-m0a test-m1a test-c1b test-shuffled doctest benchmark compile compile-all checkdoc package-lint diff-check clean
test:
$(EMACS) -Q --batch $(LOADPATH) -l tp.el $(patsubst %,-l %,$(TESTS)) \
@ -53,11 +52,13 @@ test-m1a:
-l $(TEST_DIR)/tp-transaction-tests.el \
--eval '(ert-run-tests-batch-and-exit "tp-transaction-test-\\|tp-m0a-characterization-test-\\|tp-binding-test-signal-commit-journal-rolls-back-every-write\\|tp-binding-test-rollback-preserves-primary-and-runs-all-phases\\|tp-surface-test-global-signal-update-is-multi-surface-atomic\\|tp-surface-test-full-and-scoped-precommit-stages-roll-back\\|tp-surface-test-full-and-scoped-final-accept-roll-back")'
test-v1:
test-c1b:
$(EMACS) -Q --batch $(LOADPATH) -l tp.el \
--eval '(setq tp-transaction-execution-route (quote v1))' \
--eval '(setq tp--transaction-artifact-mode (quote v1))' \
$(patsubst %,-l %,$(V1_TESTS)) -f ert-run-tests-batch-and-exit
-l $(TEST_DIR)/tp-binding-tests.el \
-l $(TEST_DIR)/tp-surface-tests.el \
-l $(TEST_DIR)/tp-m0a-characterization-tests.el \
-l $(TEST_DIR)/tp-transaction-tests.el \
--eval '(ert-run-tests-batch-and-exit "tp-transaction-test-\\|tp-m0a-characterization-test-participant-failure-preserves-condition\\|tp-binding-test-participant-key-is-owned-by-transaction\\|tp-binding-test-rollback-preserves-primary-and-runs-all-phases\\|tp-surface-test-transaction-participant\\|tp-surface-test-failing-participant")'
test-shuffled:
$(EMACS) -Q --batch $(LOADPATH) -l tp.el $(patsubst %,-l %,$(TESTS)) \

View File

@ -1,13 +1,13 @@
# TP
TP 1.0 is a standalone retained/reactive text runtime for Emacs. It projects declarative properties, reactive data, and stable text objects onto strings and buffers while owning text-property composition, exact dependency tracking, retained identity, marker-backed mounts, diffing, transactions, rollback, and final buffer publication.
TP 2.0 is a standalone retained/reactive text runtime for Emacs. It projects declarative properties, reactive data, and stable text objects onto strings and buffers while owning text-property composition, exact dependency tracking, retained identity, marker-backed mounts, diffing, transactions, rollback, and final buffer publication.
TP does not depend on Ebox or ECSS. It does not implement CSS selectors, stylesheets, specificity, cascade winners, Box, Flex, Grid, measurement, or layout. A CSS consumer may compute final declarations with ECSS and publish them through TP, but TP itself only understands Emacs text properties and generic retained text surfaces.
Chinese documentation: [README_CN.md](README_CN.md).
Complete public API reference: [API-REFERENCE.md](docs/API-REFERENCE.md) (中文).
It is the symbol-level usage index for the current TP 1.0 implementation; this
It is the symbol-level usage index for the current TP 2.0 implementation; this
README remains the conceptual quick start.
## Requirements
@ -146,14 +146,12 @@ TP records the host baseline and each TP contribution per property interval. Ove
Observers run only after a successful commit. Their failures are recorded and do not roll back an already committed transaction. A buffer killed during publication remains killed; rollback never recreates it.
TP's default `structured` route drives the single live publication from exact
transaction-scoped batch entries, one frozen participant vector, and the
candidate-bound final accept. The same journals, surface snapshots, and change
group are shared rather than copied. Set `tp-transaction-execution-route` to
`v1` before an outer transaction to use the complete legacy writer; the route is
frozen for that transaction and the two writers are mutually exclusive. Shadow
mode remains read-only compatibility evidence over whichever single route was
selected; it never opens a second change group or writes a buffer twice.
TP 2.0 drives the single live publication from exact transaction-scoped batch
entries, one frozen participant vector, and the candidate-bound final accept.
The same journals, surface snapshots, and change group are shared rather than
copied. Transaction participants register through
`tp-transaction-participate-v2`; TP has no alternate transaction writer or
runtime route switch.
Generic opaque authority markers are bounded and whitelist-validated before
final accept, then reverse-restored before ordinary rollback on partial apply or
accept failure. `tp-with-transaction` still returns its body value, and
@ -187,7 +185,7 @@ cycles instead of spinning.
| Core inspection and debug | `tp-debug-*`, `tp-intervals`, `tp-intervals-map`, `tp-plist`, `tp-text-snapshot`, `tp-empty-p` |
| Property policy and declarations | `tp-define-property-policy`, `tp-register-text-property`, `tp-text-declarations`, `tp-computed`, `tp-resolve-value`, `tp-merge-declarations`, `tp-define-style`, `tp-style-declarations` |
| Static recipes | `define-tp`/`tp-define-layer`, `define-tps`/`define-tp-group`/`tp-define-group`, layer/group queries, undefine/reset/describe |
| Signals and bindings | signal create/read/peek/set/dispose, binding install/read/dispose, `tp-variable-signal`, `tp-with-transaction`, `tp-transaction-participate`, `tp-transaction-participate-v2`, read-only `tp-transaction-active-p`, `tp-runtime-manifest`, counters/reset |
| Signals and bindings | signal create/read/peek/set/dispose, binding install/read/dispose, `tp-variable-signal`, `tp-with-transaction`, `tp-transaction-participate-v2`, read-only `tp-transaction-active-p`, `tp-runtime-manifest`, counters/reset |
| Objects and plans | plan/result constructors, `tp-object-ensure`, retain/reuse, fragment/content-range attachment, resolve, mounted/mounts |
| Host ranges | `tp-range-anchor-create`, `tp-range-anchor-live-p`, `tp-object-attach-range`, `tp-range-rebase` |
| Surfaces | mount/update/scoped update, materialize, live/revision/client-state, at-point, report/report-summary/inspect, unmount |
@ -205,6 +203,15 @@ for module boundaries and transaction flow.
TP 1.0 removes the 0.3 managed stack/renderer runtime instead of hiding it behind compatibility branches. Removed behavior includes `tp-render.el`, `tp-stack.el`, stack mutation APIs, `tp-text`, `$variable` declarations, layer-to-buffer registries, scan-driven refresh, managed attach/detach/diagnostics, and inline `tp-name`/`tp-layers`/`tp-meta` runtime storage.
## TP 2.0 transaction migration
TP 2.0 removes the v1 transaction participant facade and the legacy execution
route. Replace `(tp-transaction-participate KEY PUBLISH ROLLBACK)` exactly with
`(tp-transaction-participate-v2 :key KEY :stage PUBLISH :rollback ROLLBACK)`.
Callers that inspect `tp-runtime-manifest` must require
`tp-transaction-protocol-v2`; route-selection and v1-adapter fields are no
longer published.
Use direct recipes for reusable static declarations, `tp-watch` for reactive properties on existing text, and retained content surfaces for reactive text or structured UI. TP does not automatically scan historical propertized text to reconstruct runtime identity.
## Examples

View File

@ -1,13 +1,13 @@
# TP
TP 1.0 是一个可独立使用的 Emacs retained/reactive text runtime。它把声明式属性、响应式数据和稳定文本对象投影到 string 与 buffer并拥有文本属性 contribution 合成、精确依赖追踪、保留式身份、marker-backed mount、diff、事务、回滚和最终 Buffer publication。
TP 2.0 是一个可独立使用的 Emacs retained/reactive text runtime。它把声明式属性、响应式数据和稳定文本对象投影到 string 与 buffer并拥有文本属性 contribution 合成、精确依赖追踪、保留式身份、marker-backed mount、diff、事务、回滚和最终 Buffer publication。
TP 不依赖 Ebox 或 ECSS也不实现 CSS selector、stylesheet、specificity、cascade winner、Box、Flex、Grid、测量或布局。需要 CSS 的调用者可以先用 ECSS 算出最终 declarations再交给 TP 发布TP 自身只理解 Emacs 文本属性和通用 retained text surface。
英文文档:[README.md](README.md)。
完整公共 API 参考:[API-REFERENCE.md](docs/API-REFERENCE.md)。本文负责概念
和快速开始API 参考按当前 TP 1.0 源码列出入口、参数语义、返回值和用法。
和快速开始API 参考按当前 TP 2.0 源码列出入口、参数语义、返回值和用法。
## 运行要求
@ -145,13 +145,11 @@ TP 为每个 property interval 保存 host baseline 和各个 TP contribution。
observer 只在成功提交之后运行observer failure 只记录,不回滚已完成的 transaction。publication 过程中被 kill 的 buffer 保持死亡rollback 不会把它重新创建。
TP 默认由 `structured` route 从 transaction-scoped batch 的 exact entries、
同一个冻结 participant vector 和 candidate-bound final accept 驱动唯一 live
publicationjournal、surface snapshot 与 change group 仍只保留一份。outer
transaction 开始前把 `tp-transaction-execution-route` 设为 `v1`,即可使用完整
legacy writerroute 会在该 transaction 内冻结,两条 writer 分支始终互斥。
shadow mode 只对已选择的单一路径生成只读兼容证据,不会建立第二个 change
group也不会双写 Buffer。generic opaque authority marker 在 final accept 前
TP 2.0 从 transaction-scoped batch 的 exact entries、同一个冻结 participant
vector 和 candidate-bound final accept 驱动唯一 live publicationjournal、
surface snapshot 与 change group 仍只保留一份。transaction participant 统一通过
`tp-transaction-participate-v2` 注册,不再提供备用 transaction writer 或 runtime
route switch。generic opaque authority marker 在 final accept 前
完成固定上界与 whitelist 校验partial apply 或 accept failure 时先逆序恢复
marker再执行普通 rollback。`tp-with-transaction` 仍返回 body result内部
outcome 仅作为只读 side-channel evidence。
@ -182,7 +180,7 @@ effect 的 input/version tuple重复或超过图规模上限时停止循环
| Core inspection 与 debug | `tp-debug-*`、`tp-intervals`、`tp-intervals-map`、`tp-plist`、`tp-text-snapshot`、`tp-empty-p` |
| Property policy 与 declaration | `tp-define-property-policy`、`tp-register-text-property`、`tp-text-declarations`、`tp-computed`、`tp-resolve-value`、`tp-merge-declarations`、`tp-define-style`、`tp-style-declarations` |
| 静态 recipe | `define-tp`/`tp-define-layer`、`define-tps`/`define-tp-group`/`tp-define-group`、layer/group 查询、undefine/reset/describe |
| Signal 与 binding | signal create/read/peek/set/dispose、binding install/read/dispose、`tp-variable-signal`、`tp-with-transaction`、`tp-transaction-participate`、`tp-transaction-participate-v2`、只读 `tp-transaction-active-p`、`tp-runtime-manifest`、counter/reset |
| Signal 与 binding | signal create/read/peek/set/dispose、binding install/read/dispose、`tp-variable-signal`、`tp-with-transaction`、`tp-transaction-participate-v2`、只读 `tp-transaction-active-p`、`tp-runtime-manifest`、counter/reset |
| Object 与 plan | plan/result constructor、`tp-object-ensure`、retain/reuse、fragment/content-range attach、resolve、mounted/mounts |
| Host range | `tp-range-anchor-create`、`tp-range-anchor-live-p`、`tp-object-attach-range`、`tp-range-rebase` |
| Surface | mount/update/scoped update、materialize、live/revision/client-state、at-point、report/report-summary/inspect、unmount |
@ -199,6 +197,14 @@ ownership、生命周期、错误和返回值合同见 [API semantics](docs/API-
TP 1.0 直接删除 0.3 的 managed stack/renderer runtime不使用隐藏兼容分支。删除的行为包括 `tp-render.el`、`tp-stack.el`、stack mutation APIs、`tp-text`、`$variable` declarations、layer-to-buffer registry、scan-driven refresh、managed attach/detach/diagnostics以及以 `tp-name`/`tp-layers`/`tp-meta` 作为权威 runtime storage 的机制。
## TP 2.0 transaction 迁移
TP 2.0 删除 v1 transaction participant facade 和 legacy execution route。把
`(tp-transaction-participate KEY PUBLISH ROLLBACK)` 精确替换为
`(tp-transaction-participate-v2 :key KEY :stage PUBLISH :rollback ROLLBACK)`
检查 `tp-runtime-manifest` 的调用方必须要求 `tp-transaction-protocol-v2`manifest
不再发布 route-selection 和 v1-adapter 字段。
可复用静态声明使用 direct recipe已有文本的响应式属性使用 `tp-watch`;响应式文字或结构化 UI 使用 retained content surface。TP 不会自动扫描历史 propertized text 来重建 runtime identity。
## 示例

View File

@ -1,4 +1,4 @@
# TP 1.0 公共 API 与用法参考
# TP 2.0 公共 API 与用法参考
本文是 TP 当前实现的完整公共入口索引。它以 tp.el 加载的模块为准;带
tp-- 前缀的函数、变量和结构体是内部实现,不属于本文的稳定 API。
@ -226,20 +226,14 @@ tp-binding-dispose 释放单个 binding。
(tp-signal-dispose left)
(tp-signal-dispose right))
(tp-transaction-participate
'my-external-state
(lambda () (my-publish))
(lambda () (my-rollback)))
(tp-with-transaction
(tp-transaction-participate-v2
:key 'my-structured-state
:stage (lambda () (my-stage))
:rollback (lambda () (my-rollback)))
:rollback (lambda () (my-rollback))))
(tp-transaction-active-p)
(setq tp-transaction-execution-route 'structured) ; structured 或 v1
(tp-runtime-manifest)
(tp-variable-signal 'my-variable)
@ -251,8 +245,8 @@ tp-binding-dispose 释放单个 binding。
tp-with-transaction 将 signal、binding、surface 和注册的 transaction
participant 一起原子处理。participant 必须在 active transaction 内注册;
publish 在 surface publication 后、source commit 前运行,失败时按逆序
rollback。跨包 structured consumer 使用 `tp-transaction-participate-v2`;它
stage 在 surface publication 后、source commit 前运行,失败时按逆序
rollback。consumer 使用 `tp-transaction-participate-v2`;它
返回 key不暴露内部 participant 对象。tp-variable-signal 用 Emacs variable
watcher 适配全局或指定
Buffer 的变量,不是旧的 $variable API。
@ -262,25 +256,17 @@ tp-transaction-active-p 是只读边界查询:只在当前 dynamic extent 已
对象、participant 或内部状态,调用方只能用它在 mutation 前拒绝不支持的
嵌套事务边界。
`tp-transaction-execution-route` 是受支持的 live authority kill switch默认
`structured`,也可在 outer transaction 开始前设为 `v1`。outer transaction
会校验并冻结选择;中途修改 public option 不改变当前 transaction只在下一次
outer transaction 生效。`structured` 从 publication batch 的 exact entries、
同一个 participant vector 和 candidate-bound final accept 执行;`v1` 恢复原 prepared-list、
registration-list 与 transaction final-accept 循环。两条 route 互斥。
tp-runtime-manifest 返回防御性 capability snapshot本版本的
`:transaction-protocol``tp-transaction-protocol-v1+v2`。structured core
现在是默认 live authority因此 `:execution-default``structured`
`:execution-routes``(structured v1)`、`:route-option` 为
`tp-transaction-execution-route`,兼容字段 `:execution-route`
`:batch-execution` 也均为 `structured``:batch-execute` 为 non-nil。公开
structured registration 由 `:structured-participant-api` 指向
`tp-transaction-participate-v2``tp-transaction-participate` 仍是 v1 adapter
manifest 以 `:v1-adapter`
`:v1-rollback-route` 明确声明 adapter 与完整旧 route 均保留;
`:single-live-writer` 明确声明 route 不会双写;
`:batch-artifacts``:shadow-proof` 也保持 non-nil。
`:transaction-protocol``tp-transaction-protocol-v2``:version` 为
`"2.0.0"`。`:structured-participant-api` 指向
`tp-transaction-participate-v2``:batch-execute`、`:batch-artifacts` 和
`:single-live-writer` 均为 non-nil。manifest 不再发布 execution route、
v1 adapter 或 v1 rollback route。
从 TP 1.x 迁移时,把 `(tp-transaction-participate KEY PUBLISH ROLLBACK)`
精确替换为 `(tp-transaction-participate-v2 :key KEY :stage PUBLISH
:rollback ROLLBACK)`。删除所有对 `tp-transaction-execution-route`
`tp--transaction-artifact-mode` 的设置。
ETAF registers one opaque participant for its immutable generation and Ebox
client state. Its publish is paired with rollback across TP final accept; the
@ -632,7 +618,7 @@ TP 1.0 已删除并且不应在新代码中使用:
| tp-core.el | tp-debug-*、tp-with-current-buffer、tp-intervals、tp-intervals-map、tp-empty-p、tp-plist、tp-text-snapshot |
| tp-style.el | tp-define-property-policy、tp-property-policy、tp-text-property-id、tp-register-text-property、tp-text-declarations、tp-computed、tp-resolve-value、tp-merge-declarations、tp-define-style、tp-style-declarations、tp-undefine-style |
| tp-layer.el | define-tp/tp-define-layer、define-tps/define-tp-group/tp-define-group、layer/group query、tp-layer-reset、tp-undefine-*、tp-describe-layer |
| tp-transaction.el | additive batch/entry、final-marker 与 tagged-outcome 内部合同 |
| tp-transaction.el | structured batch/entry、final-marker 与 tagged-outcome 内部合同 |
| tp-reactive.el | signal、binding、transaction coordinator、variable adapter、counter 和 reset API |
| tp-surface.el | plan/result、object、range anchor、surface lifecycle、scoped update、report、tp-watch |
| tp-ops.el | tp-propertize、tp-apply、tp-set、tp-reset、tp-add、tp-remove、tp-clear、tp-get、tp-at、tp-member |

View File

@ -1,6 +1,6 @@
# TP 1.0 API Semantics
# TP 2.0 API Semantics
本文记录 TP 1.0 当前公共 API 的 ownership、presence、响应式、retained surface、事务与失败合同。它描述已经实现的行为目标背景与设计理由见 [retained runtime architecture](retained-runtime-target-architecture.md)。
本文记录 TP 2.0 当前公共 API 的 ownership、presence、响应式、retained surface、事务与失败合同。它描述已经实现的行为目标背景与设计理由见 [retained runtime architecture](retained-runtime-target-architecture.md)。
完整的公共符号、参数形状、返回值和示例见 [API reference](API-REFERENCE.md)。
@ -209,24 +209,19 @@ TP 为每个 interval 保存:
嵌套 transaction 加入最外层。一个 global signal 可以原子触达多个 buffers任一 surface 失败时,已发布 surfaces 和 source/binding state 全部回滚。
`tp-transaction-participate` 允许 client side state 在 surfaces 发布后、source commit 前加入同一 rollback boundary。它保留公共 v1 调用形状,但在默认 structured route 中适配成同一个 participant record 的 `stage` capability只执行一次。participant key 在一个 outer transaction 中必须唯一。它不是 observer失败会回滚 transaction。Observer failure 只记录,不回滚已提交结果。
跨包 consumer 使用公开 `tp-transaction-participate-v2` 直接注册 structured
`stage`/`rollback` pair返回值仍是 key内部 participant identity、state 与
journal 不暴露。完整 v1 execution route 也能执行该同一 v2 participant object
因此 Ebox 可以独立 cutover而不要求同步改变 TP live route
`tp-transaction-participate-v2` 允许 client side state 在 surfaces 发布后、
source commit 前加入同一 rollback boundary。调用方通过 `:key`、`:stage` 和
`:rollback` 注册 structured participant返回值仍是 key内部 participant
identity、state 与 journal 不暴露。participant key 在一个 outer transaction
中必须唯一。它不是 observer失败会回滚 transaction。Observer failure 只记录
不回滚已提交结果
publication batch、structured participant、final marker 与 tagged outcome 共享
现有 journal/change-group不复制第二份 live state。默认 `structured` route 是
唯一 live writersurface 从 candidate entries 执行participant 从 candidate
现有 journal/change-group不复制第二份 live state。publication batch 是唯一
live writersurface 从 candidate entries 执行participant 从 candidate
绑定的同一 identity vector 执行final accept 从 candidate binding 执行;任何
binding/order 漂移都会 fail-fast 并回滚。把
`tp-transaction-execution-route` 在 outer transaction 前设为 `v1` 会立即恢复
完整 legacy prepared-list、registration-list 与 final-accept route。route 在 outer
transaction 入口冻结,中途修改 option 只影响下一次 outer transaction
structured/v1 分支始终互斥,不双写 Buffer
或 participant state。`tp--transaction-artifact-mode` 只控制 v1/shadow 兼容证据,
不能关闭 structured candidate。`tp-with-transaction` 的返回值仍是 body result
binding/order 漂移都会 fail-fast 并回滚。TP 2.0 不再提供 alternate writer、
execution route 或 artifact-mode switch。`tp-with-transaction` 的返回值仍是 body result
success/failure outcome 只走内部 side channel。zero-surface 与 output-equal
operation 不创建 publication batch。

View File

@ -1,6 +1,6 @@
# TP 1.0 Current Architecture
# TP 2.0 Current Architecture
本文描述 TP 1.0 当前实现的模块边界、权威状态、数据流和事务模型。公共行为合同见 [API semantics](API-SEMANTICS.md),设计背景见 [retained runtime architecture](retained-runtime-target-architecture.md)。
本文描述 TP 2.0 当前实现的模块边界、权威状态、数据流和事务模型。公共行为合同见 [API semantics](API-SEMANTICS.md),设计背景见 [retained runtime architecture](retained-runtime-target-architecture.md)。
按功能查找公共入口和用法时,使用 [API reference](API-REFERENCE.md)。
@ -45,7 +45,7 @@ tp.el loads the public package surface
| --- | --- | --- |
| `tp-core.el` | canonical ranges/requests/results、interval traversal、plist/face merge、native property facts | runtime identity、reactivity、publication |
| `tp-style.el` | native property policies、direct declarations、explicit computed source、projection | selector、stylesheet、specificity、CSS winner |
| `tp-transaction.el` | additive batch/entry validators、one-shot states、opaque final-marker descriptors、tagged outcomes | live buffer writer、consumer semantics、parallel journals |
| `tp-transaction.el` | structured batch/entry validators、one-shot states、opaque final-marker descriptors、tagged outcomes | live buffer writer、consumer semantics、parallel journals |
| `tp-reactive.el` | signals、bindings、dynamic dependency graph、scheduler、candidate source state、transaction participants | buffer scans、mount positions、layout impact |
| `tp-surface.el` | prepare context、objects、plans、anchors、mount/index、contribution ledger、diff、publication、rollback、reports | stylesheet/cascade、consumer layout decisions |
| `tp-layer.el` | `define-tp`/`define-tps` declaration recipes and registry | live layer stack、inline runtime metadata、watcher engine |
@ -182,11 +182,10 @@ An equal candidate produces no prepared publication. It preserves revision, repo
The outer transaction owns candidate source values, dirty bindings, prepared surfaces, participants, inverse journals, view state, and final observer scheduling.
The additive v2 contract is a structured view over those exact owners. It does
not copy participant, scheduler, snapshot, journal, or change-group state. The
public v1 participant façade and the v1 surface writer remain authoritative;
shadow mode constructs canonical entries and compares property-for-property and
revision-for-revision after commit or rollback.
The v2 contract uses those exact owners. It does not copy participant,
scheduler, snapshot, journal, or change-group state. The publication batch is
the sole writer and validates its canonical entries against the committed
property and revision state after commit or rollback.
```text
freeze candidate writes
@ -208,11 +207,11 @@ Content publication edits the minimal text span and then exact property runs. Pr
Rollback restores text, properties, marker/index state, plans, producer, client state, signal values, binding values/dependencies, dirty queues, revisions and reports. Property journals are explicit because `atomic-change-group` alone does not cover every silent property mutation path.
`tp-transaction-participate` lets a consumer promote rollback-capable opaque state inside this boundary. Observers are different: they run only after the transaction commits, and observer failure is recorded rather than rolled back.
`tp-transaction-participate-v2` lets a consumer promote rollback-capable opaque state inside this boundary. Observers are different: they run only after the transaction commits, and observer failure is recorded rather than rolled back.
The v1 participant record is also its structured v2 bridge: one stable key,
registration order, stage, rollback, optional declared precommit, contained
after-commit work, owner journal, and one-shot state. Final markers are not
Each structured participant has one stable key, registration order, stage,
rollback, optional declared precommit, contained after-commit work, owner
journal, and one-shot state. Final markers are not
participants. TP treats their values as opaque and accepts only predeclared,
fixed-bound operations. M1a's closed `tp-vector-slots/v1` primitive accepts
only prebuilt vector-slot expectations and writes; marker registration cannot

View File

@ -399,20 +399,20 @@
(setq failure
(condition-case condition
(tp-with-transaction
(tp-transaction-participate
'first
(lambda ()
(tp-transaction-participate-v2
:key 'first
:stage (lambda ()
(push 'publish-first
tp-binding-test-transaction-trace))
(lambda ()
:rollback (lambda ()
(push 'rollback-first
tp-binding-test-transaction-trace)))
(tp-transaction-participate
'second
(lambda ()
(tp-transaction-participate-v2
:key 'second
:stage (lambda ()
(push 'publish-second
tp-binding-test-transaction-trace))
(lambda ()
:rollback (lambda ()
(push 'rollback-second
tp-binding-test-transaction-trace)))
(tp-signal-set signal 2))
@ -689,7 +689,8 @@
(caller-vector (vector (copy-sequence "key")))
(key (list 'test caller-string caller-vector)))
(tp-with-transaction
(tp-transaction-participate key #'ignore #'ignore)
(tp-transaction-participate-v2
:key key :stage #'ignore :rollback #'ignore)
(let ((stored (tp--transaction-participant-key
(car tp--transaction-participants))))
(should-not (eq (nth 1 stored) caller-string))
@ -701,8 +702,9 @@
(should (equal (nth 1 stored) "participant"))
(should (equal (nth 2 stored) ["key"]))
(should-error
(tp-transaction-participate
(list 'test "participant" ["key"]) #'ignore #'ignore)
(tp-transaction-participate-v2
:key (list 'test "participant" ["key"])
:stage #'ignore :rollback #'ignore)
:type 'tp-reactive-error))))))
(ert-deftest tp-binding-test-dirty-target-can-break-an-old-cycle-edge ()

View File

@ -107,12 +107,12 @@
(tp-m0a-characterization--capture
(lambda ()
(tp-with-transaction
(tp-transaction-participate
'm0a-participant
(lambda ()
(tp-transaction-participate-v2
:key 'm0a-participant
:stage (lambda ()
(setq external 'candidate)
(signal (car injected) (cdr injected)))
(lambda () (setq external 'old)))
:rollback (lambda () (setq external 'old)))
(tp-signal-set source 2))))))
(should (equal failure injected)))
(should (eq external 'old))

View File

@ -2687,14 +2687,14 @@
(push (list 'observer (buffer-string)) events))))))
(setq events nil)
(tp-with-transaction
(tp-transaction-participate
'(test . promotion)
(lambda ()
(tp-transaction-participate-v2
:key '(test . promotion)
:stage (lambda ()
(push (list 'participant
(buffer-string)
(tp-surface-client-state surface))
events))
(lambda () (push '(rollback) events)))
:rollback (lambda () (push '(rollback) events)))
(tp-surface-update
surface
(lambda (context)
@ -2717,12 +2717,12 @@
rollback-ran)
(should-error
(tp-with-transaction
(tp-transaction-participate
'(test . failure)
(lambda ()
(tp-transaction-participate-v2
:key '(test . failure)
:stage (lambda ()
(setq external 'candidate)
(error "Participant failure"))
(lambda ()
:rollback (lambda ()
(setq external 'old
rollback-ran t)))
(tp-signal-set signal 2)))
@ -2736,8 +2736,10 @@
"Participant keys are unique within the outer transaction."
(should-error
(tp-with-transaction
(tp-transaction-participate 'same #'ignore #'ignore)
(tp-transaction-participate 'same #'ignore #'ignore))
(tp-transaction-participate-v2
:key 'same :stage #'ignore :rollback #'ignore)
(tp-transaction-participate-v2
:key 'same :stage #'ignore :rollback #'ignore))
:type 'tp-reactive-error))
(ert-deftest tp-surface-test-equal-reactive-plan-skips-surface-publication ()

View File

@ -4,8 +4,8 @@
;;; Commentary:
;; Characterization and fault tests for the v1+v2 transaction protocol and its
;; controlled structured-authority cutover. These tests deliberately exercise
;; Characterization and fault tests for the v2 transaction protocol. These
;; tests deliberately exercise
;; the internal protocol: the public contract remains `tp-with-transaction'
;; body return and primary condition preservation.
@ -23,10 +23,6 @@
(declare-function tp--transaction-participant-journal "tp-reactive" (value))
(declare-function tp--transaction-register-final-marker
"tp-reactive" (&rest args))
(declare-function tp-runtime-manifest "tp" ())
(defvar tp--transaction-artifact-mode)
(defvar tp-transaction-execution-route)
(defvar tp--transaction-publication-batch)
(defvar tp--transaction-outcome)
(defvar tp--last-shadow-proof)
@ -237,8 +233,8 @@
(when (tp-signal-live-p ,source)
(tp-signal-dispose ,source)))))
(ert-deftest tp-transaction-test-v1-body-return-and-phase-order ()
"The v1 facade returns BODY and retains its established phase order."
(ert-deftest tp-transaction-test-v2-body-return-and-phase-order ()
"The v2 facade returns BODY and retains its established phase order."
(let ((tp-transaction-test--trace nil)
(tp--transaction-precommit-functions
'(tp--transaction-test-precommit-inject))
@ -247,10 +243,10 @@
(should
(equal
(tp-with-transaction
(tp-transaction-participate
'v1
(lambda () (push 'participant tp-transaction-test--trace))
(lambda () (push 'rollback tp-transaction-test--trace)))
(tp-transaction-participate-v2
:key 'v2
:stage (lambda () (push 'participant tp-transaction-test--trace))
:rollback (lambda () (push 'rollback tp-transaction-test--trace)))
(tp--enqueue-after-commit
(lambda () (push 'after-commit tp-transaction-test--trace)))
(push 'body tp-transaction-test--trace)
@ -259,8 +255,8 @@
(should (equal (nreverse tp-transaction-test--trace)
'(body participant precommit after-commit)))))
(ert-deftest tp-transaction-test-v1-participant-and-precommit-fault-order ()
"A late v1 fault rolls staged participants back in reverse order."
(ert-deftest tp-transaction-test-v2-participant-and-precommit-fault-order ()
"A late v2 fault rolls staged participants back in reverse order."
(let* ((injected '(tp-transaction-test-error :phase precommit :raw (1 2)))
(tp-transaction-test--trace nil)
(tp-transaction-test--precommit-condition injected)
@ -272,14 +268,14 @@
(tp-transaction-test--capture
(lambda ()
(tp-with-transaction
(tp-transaction-participate
'first
(lambda () (push 'stage-first tp-transaction-test--trace))
(lambda () (push 'rollback-first tp-transaction-test--trace)))
(tp-transaction-participate
'second
(lambda () (push 'stage-second tp-transaction-test--trace))
(lambda () (push 'rollback-second tp-transaction-test--trace))))))))
(tp-transaction-participate-v2
:key 'first
:stage (lambda () (push 'stage-first tp-transaction-test--trace))
:rollback (lambda () (push 'rollback-first tp-transaction-test--trace)))
(tp-transaction-participate-v2
:key 'second
:stage (lambda () (push 'stage-second tp-transaction-test--trace))
:rollback (lambda () (push 'rollback-second tp-transaction-test--trace))))))))
(should (equal failure injected))
(should (equal (nreverse tp-transaction-test--trace)
'(stage-first stage-second precommit
@ -356,14 +352,15 @@
(should-error (tp--publication-batch-transition discarded 'staged)
:type 'tp-publication-state-error))))
(ert-deftest tp-transaction-test-v1-bridge-is-the-structured-participant ()
"The v1 facade installs one v2 record, not parallel participant state."
(ert-deftest tp-transaction-test-v2-participant-is-single-structured-record ()
"The v2 facade installs one structured record with no legacy bridge."
(let (saved)
(tp-with-transaction
(tp-transaction-participate 'legacy #'ignore #'ignore)
(tp-transaction-participate-v2 :key 'structured :stage #'ignore
:rollback #'ignore)
(setq saved (car tp--transaction-participants))
(should (= (length tp--transaction-participants) 1))
(should (eq (tp--transaction-participant-protocol saved) 'v1-bridge))
(should (eq (tp--transaction-participant-protocol saved) 'v2))
(should (eq (tp--transaction-participant-state saved) 'prepared)))
(should (eq (tp--transaction-participant-state saved) 'committed))))
@ -389,11 +386,9 @@
'(:owner old-state)))
(should (eq (tp--transaction-participant-state participant) 'committed))))
(ert-deftest tp-transaction-test-public-v2-participant-api-spans-both-routes ()
"The public v2 API registers one v2 object under structured and v1 writers."
(dolist (route '(structured v1))
(let ((tp-transaction-execution-route route)
(tp-transaction-test--trace nil)
(ert-deftest tp-transaction-test-public-v2-participant-api-registers-v2-record ()
"The public v2 API registers one v2 object on the sole live route."
(let ((tp-transaction-test--trace nil)
participant)
(should
(eq
@ -413,7 +408,7 @@
(should (eq (tp--transaction-participant-protocol participant) 'v2))
(should (equal (tp--transaction-participant-journal participant)
'(:owner public)))
(should (eq (tp--transaction-participant-state participant) 'committed)))))
(should (eq (tp--transaction-participant-state participant) 'committed))))
(ert-deftest tp-transaction-test-v2-participant-stage-fault-rolls-back-prior-only ()
"A structured stage fault reverses only participants that entered staged."
@ -606,6 +601,10 @@
tp--last-transaction-outcome))
(tp-transaction-test--should-match-outcome-counts
tp--last-transaction-outcome (list surface))
(should (equal (plist-get tp--last-shadow-proof :phase) 'commit))
(should (plist-get tp--last-shadow-proof :equivalent))
(should (plist-get tp--last-shadow-proof :outcome-equivalent))
(should (= (plist-get tp--last-shadow-proof :entry-count) 1))
(should (eq (plist-get
(tp--committed-success-outcome-snapshot
tp--last-transaction-outcome)
@ -791,8 +790,7 @@
(dolist (id before)
(should (memq id outcome-ids)))
(tp-transaction-test--should-match-outcome-counts
tp--last-transaction-outcome (list surface))
(should (plist-get tp--last-shadow-proof :equivalent))))
tp--last-transaction-outcome (list surface))))
(when (buffer-live-p buffer) (kill-buffer buffer)))))
(ert-deftest tp-transaction-test-candidates-remain-invisible-until-staged ()
@ -811,8 +809,8 @@
(with-current-buffer buffer
(should (equal (buffer-string) text))))))
(ert-deftest tp-transaction-test-shadow-is-multisurface-and-single-writer ()
"Shadow artifacts cover all surfaces while each live writer runs once."
(ert-deftest tp-transaction-test-v2-is-multisurface-and-single-writer ()
"The v2 artifacts cover all surfaces while each live writer runs once."
(let* ((source (tp-signal-create 1))
(producer (tp-transaction-test--producer source))
(first-buffer (generate-new-buffer " *tp-transaction-first*"))
@ -828,16 +826,17 @@
(let ((surface (tp--prepared-surface-surface prepared)))
(puthash surface (1+ (gethash surface calls 0)) calls))
(funcall writer prepared))))
(let ((tp--transaction-artifact-mode 'shadow))
(tp-signal-set source 2))
(tp-signal-set source 2)
(should (= (gethash first calls 0) 1))
(should (= (gethash second calls 0) 1))
(should (tp-committed-success-outcome-p
tp--last-transaction-outcome))
(tp-transaction-test--should-match-outcome-counts
tp--last-transaction-outcome (list first second))
(should (equal (plist-get tp--last-shadow-proof :phase) 'commit))
(should (plist-get tp--last-shadow-proof :equivalent))
(should (plist-get tp--last-shadow-proof :outcome-equivalent))
(should (= (plist-get tp--last-shadow-proof :entry-count) 2))
(should (integerp
(tp-committed-success-outcome-mapping-generation
tp--last-transaction-outcome)))
@ -855,6 +854,31 @@
(when (buffer-live-p second-buffer) (kill-buffer second-buffer))
(when (tp-signal-live-p source) (tp-signal-dispose source)))))
(ert-deftest tp-transaction-test-canonical-artifact-mismatch-is-diagnostic ()
"A postcommit canonical artifact mismatch is detected and contained."
(tp-transaction-test--with-surface (buffer surface source)
(let ((current-artifact (symbol-function 'tp--shadow-current-artifact)))
(cl-letf (((symbol-function 'tp--shadow-current-artifact)
(lambda (target)
(let ((artifact (funcall current-artifact target)))
(plist-put artifact :revision
(1+ (plist-get artifact :revision)))))))
(tp-signal-set source 2))
(should (= (tp-signal-peek source) 2))
(should (= (tp-surface-revision surface) 2))
(with-current-buffer buffer
(should (equal (buffer-string) "2")))
(should (equal (plist-get tp--last-shadow-proof :phase) 'commit))
(should-not (plist-get tp--last-shadow-proof :equivalent))
(should (plist-get tp--last-shadow-proof :outcome-equivalent))
(should (= (plist-get tp--last-shadow-proof :entry-count) 1))
(should
(cl-some
(lambda (entry)
(and (eq (car entry) 'shadow-proof)
(eq (cadr entry) 'commit)))
tp--last-transaction-diagnostics)))))
(ert-deftest tp-transaction-test-zero-surface-does-not-create-batch-or-outcome ()
"Pure, semantic and external-only transactions stay outside batch API."
(let ((tp--last-transaction-outcome 'sentinel)
@ -868,7 +892,8 @@
(should-not captured-outcome)
(should-not tp--last-transaction-outcome)
(tp-with-transaction
(tp-transaction-participate 'external-only #'ignore #'ignore))
(tp-transaction-participate-v2 :key 'external-only
:stage #'ignore :rollback #'ignore))
(should-not tp--last-transaction-outcome)))
(ert-deftest tp-transaction-test-unobserved-signal-excludes-publication-batch ()
@ -885,8 +910,7 @@
(should (= (tp-signal-peek signal) 2))
(should (= (tp-signal-revision signal) 1))
(should (= batch-calls 0))
(should-not tp--last-transaction-outcome)
(should-not tp--last-shadow-proof))
(should-not tp--last-transaction-outcome))
(when (tp-signal-live-p signal) (tp-signal-dispose signal)))))
(ert-deftest tp-transaction-test-surface-unmount-excludes-publication-batch ()
@ -900,8 +924,7 @@
(batch-calls 0))
(unwind-protect
(progn
(setq tp--last-transaction-outcome nil
tp--last-shadow-proof nil)
(setq tp--last-transaction-outcome nil)
(cl-letf
(((symbol-function 'tp--transaction-begin-publication-batch)
(lambda (&rest arguments)
@ -910,7 +933,6 @@
(tp-surface-unmount surface))
(should (= batch-calls 0))
(should-not tp--last-transaction-outcome)
(should-not tp--last-shadow-proof)
(should-not (tp-surface-live-p surface))
(with-current-buffer buffer
(should (equal (buffer-string) ""))))
@ -966,13 +988,11 @@
(should (= (length outcome-cell) 1))
(should (tp-committed-success-outcome-p (aref outcome-cell 0)))
(should (eq (aref outcome-cell 0) tp--last-transaction-outcome))
(should (plist-get tp--last-shadow-proof :equivalent))
(should (plist-get tp--last-shadow-proof :outcome-equivalent))
(should (= (tp-surface-revision surface) (1+ old-revision)))
(with-current-buffer buffer
(should (equal (buffer-string) "2"))))))
(ert-deftest tp-transaction-test-scoped-content-shadow-equivalence ()
(ert-deftest tp-transaction-test-scoped-content-v2-publication ()
"Scoped content artifacts equal the single committed writer result."
(let ((buffer (generate-new-buffer " *tp-transaction-scoped*"))
(middle "B")
@ -1005,9 +1025,8 @@
(revision (tp-surface-revision surface)))
(setq middle "LONG"
middle-face 'italic)
(let ((tp--transaction-artifact-mode 'shadow))
(tp-surface-update-scoped
surface (list middle-object) producer))
surface (list middle-object) producer)
(should (= (tp-surface-revision surface) (1+ revision)))
(with-current-buffer buffer
(should (equal (buffer-string) "ALONGC"))
@ -1015,14 +1034,10 @@
(should (tp-committed-success-outcome-p
tp--last-transaction-outcome))
(tp-transaction-test--should-match-outcome-counts
tp--last-transaction-outcome (list surface))
(should (equal (plist-get tp--last-shadow-proof :phase) 'commit))
(should (plist-get tp--last-shadow-proof :equivalent))
(should (plist-get tp--last-shadow-proof :outcome-equivalent))
(should (= (plist-get tp--last-shadow-proof :entry-count) 1)))
tp--last-transaction-outcome (list surface)))
(when (buffer-live-p buffer) (kill-buffer buffer)))))
(ert-deftest tp-transaction-test-properties-only-shadow-equivalence ()
(ert-deftest tp-transaction-test-properties-only-v2-publication ()
"Properties-only artifacts equal live properties without replacing text."
(let ((buffer (generate-new-buffer " *tp-transaction-properties*"))
(value 'bold))
@ -1043,19 +1058,14 @@
buffer producer '(:capability properties)))
(revision (tp-surface-revision surface)))
(setq value 'italic)
(let ((tp--transaction-artifact-mode 'shadow))
(tp-surface-update surface producer))
(tp-surface-update surface producer)
(should (equal (buffer-string) "host"))
(should (eq (get-text-property 2 'face) 'italic))
(should (= (tp-surface-revision surface) (1+ revision)))
(should (tp-committed-success-outcome-p
tp--last-transaction-outcome))
(tp-transaction-test--should-match-outcome-counts
tp--last-transaction-outcome (list surface))
(should (equal (plist-get tp--last-shadow-proof :phase) 'commit))
(should (plist-get tp--last-shadow-proof :equivalent))
(should (plist-get tp--last-shadow-proof :outcome-equivalent))
(should (= (plist-get tp--last-shadow-proof :entry-count) 1))))
tp--last-transaction-outcome (list surface))))
(when (buffer-live-p buffer) (kill-buffer buffer)))))
(ert-deftest tp-transaction-test-property-surface-fault-matrix-restores-exact-state ()
@ -1126,11 +1136,11 @@
(lambda ()
(tp-with-transaction
(when (eq phase 'participant)
(tp-transaction-participate
'property-fault-participant
(lambda ()
(tp-transaction-participate-v2
:key 'property-fault-participant
:stage (lambda ()
(signal (car injected) (cdr injected)))
#'ignore))
:rollback #'ignore))
(tp-signal-set source 2)))))
injected)))
(should (= (tp-signal-peek source) 1))
@ -1144,106 +1154,18 @@
(with-current-buffer second-buffer
(should (eq (get-text-property 2 'face) 'bold)))
(should (tp-publication-failure-outcome-p
tp--last-transaction-outcome))
(should (equal (plist-get tp--last-shadow-proof :phase)
'rollback))
(should (plist-get tp--last-shadow-proof :equivalent))
(should (plist-get tp--last-shadow-proof :outcome-equivalent))
(should (= (plist-get tp--last-shadow-proof :entry-count) 2)))
tp--last-transaction-outcome)))
(when (buffer-live-p first-buffer) (kill-buffer first-buffer))
(when (buffer-live-p second-buffer) (kill-buffer second-buffer))
(when (tp-signal-live-p source) (tp-signal-dispose source))))))
(ert-deftest tp-transaction-test-v1-and-shadow-artifact-modes-are-equivalent ()
"Independent v1 and shadow surfaces commit identical live artifacts."
(let* ((v1-buffer (generate-new-buffer " *tp-transaction-v1*"))
(shadow-buffer (generate-new-buffer " *tp-transaction-shadow*"))
(initial
(tp-surface-plan-create
:key 'root :kind 'text :text "old" :props '(face bold)
:capability 'content))
(next
(tp-surface-plan-create
:key 'root :kind 'text :text "new" :props '(face italic)
:capability 'content))
(v1-surface
(tp-surface-mount v1-buffer initial '(:capability content)))
(shadow-surface
(tp-surface-mount shadow-buffer initial '(:capability content)))
(writer (symbol-function 'tp--publish-one-surface))
(shadow-writes 0))
(unwind-protect
(progn
(let ((tp-transaction-execution-route 'v1)
(tp--transaction-artifact-mode 'v1))
(tp-surface-update v1-surface next))
(should-not tp--last-transaction-outcome)
(should-not tp--last-shadow-proof)
(cl-letf (((symbol-function 'tp--publish-one-surface)
(lambda (prepared)
(cl-incf shadow-writes)
(funcall writer prepared))))
(let ((tp--transaction-artifact-mode 'shadow))
(let ((tp-transaction-execution-route 'v1))
(tp-surface-update shadow-surface next))))
(should (= shadow-writes 1))
(should (tp-committed-success-outcome-p
tp--last-transaction-outcome))
(should (plist-get tp--last-shadow-proof :equivalent))
(should (plist-get tp--last-shadow-proof :outcome-equivalent))
(should (= (tp-surface-revision v1-surface)
(tp-surface-revision shadow-surface)))
(should
(equal-including-properties
(with-current-buffer v1-buffer (buffer-string))
(with-current-buffer shadow-buffer (buffer-string))))
(with-current-buffer v1-buffer
(should (equal (buffer-string) "new"))
(should (eq (get-text-property 1 'face) 'italic))))
(when (buffer-live-p v1-buffer) (kill-buffer v1-buffer))
(when (buffer-live-p shadow-buffer) (kill-buffer shadow-buffer)))))
(ert-deftest tp-transaction-test-structured-and-v1-routes-are-equivalent ()
"Structured and v1 authorities commit equivalent state with one write each."
(let* ((structured-buffer (generate-new-buffer " *tp-route-structured*"))
(v1-buffer (generate-new-buffer " *tp-route-v1*"))
(initial (tp-transaction-test--leaf "old"))
(next (tp-transaction-test--leaf "new"))
(structured (tp-surface-mount structured-buffer initial
'(:capability content)))
(v1 (tp-surface-mount v1-buffer initial '(:capability content)))
(writer (symbol-function 'tp--publish-one-surface))
(writes (make-hash-table :test #'eq)))
(unwind-protect
(cl-letf (((symbol-function 'tp--publish-one-surface)
(lambda (prepared)
(let ((surface (tp--prepared-surface-surface prepared)))
(puthash surface (1+ (gethash surface writes 0)) writes))
(funcall writer prepared))))
(let ((tp-transaction-execution-route 'structured)
(tp--transaction-artifact-mode 'v1))
(tp-surface-update structured next))
(let ((tp-transaction-execution-route 'v1)
(tp--transaction-artifact-mode 'v1))
(tp-surface-update v1 next))
(should (= (gethash structured writes 0) 1))
(should (= (gethash v1 writes 0) 1))
(should (= (tp-surface-revision structured) (tp-surface-revision v1)))
(should (equal-including-properties
(with-current-buffer structured-buffer (buffer-string))
(with-current-buffer v1-buffer (buffer-string)))))
(when (buffer-live-p structured-buffer) (kill-buffer structured-buffer))
(when (buffer-live-p v1-buffer) (kill-buffer v1-buffer)))))
(ert-deftest tp-transaction-test-final-accept-source-follows-route ()
"Structured validates candidate final accept; v1 shadow uses legacy binding."
(ert-deftest tp-transaction-test-final-accept-source-uses-v2-binding ()
"V2 validates the candidate final accept binding before publication."
(let* ((structured-buffer (generate-new-buffer " *tp-final-structured*"))
(v1-buffer (generate-new-buffer " *tp-final-v1*"))
(initial (tp-transaction-test--leaf "old"))
(next (tp-transaction-test--leaf "new"))
(structured (tp-surface-mount structured-buffer initial
'(:capability content)))
(v1 (tp-surface-mount v1-buffer initial '(:capability content)))
(sync (symbol-function 'tp--transaction-sync-publication-batch)))
(unwind-protect
(cl-letf (((symbol-function 'tp--transaction-sync-publication-batch)
@ -1253,23 +1175,14 @@
(setf (tp-publication-batch-candidate-final-accept
tp--transaction-publication-batch)
#'ignore)))))
(let ((tp-transaction-execution-route 'structured))
(should-error (tp-surface-update structured next)
:type 'tp-publication-binding-error))
:type 'tp-publication-binding-error)
(with-current-buffer structured-buffer
(should (equal (buffer-string) "old")))
(let ((tp-transaction-execution-route 'v1)
(tp--transaction-artifact-mode 'shadow))
(tp-surface-update v1 next))
(with-current-buffer v1-buffer
(should (equal (buffer-string) "new")))
(should (tp-committed-success-outcome-p tp--last-transaction-outcome)))
(should (tp-publication-failure-outcome-p
tp--last-transaction-outcome)))
(when (buffer-live-p structured-buffer) (kill-buffer structured-buffer))
(when (buffer-live-p v1-buffer) (kill-buffer v1-buffer)))))
(ert-deftest tp-transaction-test-default-execution-route-is-structured ()
"The supported live execution route defaults to the structured core."
(should (eq (default-value 'tp-transaction-execution-route) 'structured)))
)))
(ert-deftest tp-transaction-test-structured-batch-owns-surface-stage-once ()
"Structured execution enters the candidate seam and writes each entry once."
@ -1293,9 +1206,7 @@
(let ((surface (tp--prepared-surface-surface prepared)))
(puthash surface (1+ (gethash surface calls 0)) calls))
(funcall writer prepared))))
(let ((tp-transaction-execution-route 'structured)
(tp--transaction-artifact-mode 'v1))
(tp-signal-set source 2))
(tp-signal-set source 2)
(should (= stage-calls 1))
(should (= (gethash first calls 0) 1))
(should (= (gethash second calls 0) 1))
@ -1304,93 +1215,27 @@
(when (buffer-live-p second-buffer) (kill-buffer second-buffer))
(when (tp-signal-live-p source) (tp-signal-dispose source)))))
(ert-deftest tp-transaction-test-public-v1-adapter-stages-once-from-batch ()
"The public v1 adapter is one structured participant in the batch vector."
(ert-deftest tp-transaction-test-public-v2-participant-stages-once-from-batch ()
"The public v2 participant is one structured participant in the batch vector."
(tp-transaction-test--with-surface (buffer _surface source)
(let ((stages 0) captured participant)
(tp-with-transaction
(tp-transaction-participate
'public-v1
(lambda ()
(tp-transaction-participate-v2
:key 'public-v2
:stage (lambda ()
(cl-incf stages)
(setq captured tp--transaction-publication-batch
participant
(aref (tp-publication-batch-candidate-participants
tp--transaction-publication-batch)
0)))
#'ignore)
:rollback #'ignore)
(tp-signal-set source 2))
(should (= stages 1))
(should (tp-publication-batch-candidate-p captured))
(should (eq (tp--transaction-participant-protocol participant)
'v1-bridge))
(should (eq (tp--transaction-participant-protocol participant) 'v2))
(should (eq (tp--transaction-participant-state participant) 'committed)))))
(ert-deftest tp-transaction-test-v1-kill-switch-preserves-legacy-authority ()
"The v1 kill switch uses legacy loops without a structured batch writer."
(tp-transaction-test--with-surface (buffer _surface source)
(let ((tp-transaction-execution-route 'v1)
(tp--transaction-artifact-mode 'v1)
(stage-seam-calls 0)
(participant-calls 0)
(seam (symbol-function 'tp--publication-batch-execute-stage)))
(cl-letf (((symbol-function 'tp--publication-batch-execute-stage)
(lambda (candidate)
(cl-incf stage-seam-calls)
(funcall seam candidate))))
(should
(eq (tp-with-transaction
(tp-transaction-participate
'legacy (lambda () (cl-incf participant-calls)) #'ignore)
(tp-signal-set source 2)
'legacy-result)
'legacy-result)))
(should (= stage-seam-calls 0))
(should (= participant-calls 1))
(should-not tp--last-transaction-outcome))))
(ert-deftest tp-transaction-test-invalid-route-fails-before-body-mutation ()
"An invalid route is rejected before the transaction body can mutate state."
(let ((tp-transaction-execution-route 'invalid)
(body-calls 0))
(should-error
(tp-with-transaction (cl-incf body-calls))
:type 'tp-transaction-contract-error)
(should (= body-calls 0))))
(ert-deftest tp-transaction-test-route-change-applies-to-next-outer-transaction ()
"An active outer transaction keeps its snapshot; the next uses the new route."
(tp-transaction-test--with-surface (buffer surface source)
(let ((revision (tp-surface-revision surface))
(tp-transaction-execution-route 'structured)
(tp--transaction-artifact-mode 'v1)
(stage (symbol-function 'tp--publication-batch-execute-stage))
(writer (symbol-function 'tp--publish-one-surface))
(stage-calls 0)
(writer-calls 0)
first-outcome)
(cl-letf (((symbol-function 'tp--publication-batch-execute-stage)
(lambda (candidate)
(cl-incf stage-calls)
(funcall stage candidate)))
((symbol-function 'tp--publish-one-surface)
(lambda (prepared)
(cl-incf writer-calls)
(funcall writer prepared))))
(tp-with-transaction
(tp-signal-set source 2)
(setq tp-transaction-execution-route 'v1))
(setq first-outcome tp--last-transaction-outcome)
(tp-signal-set source 3))
(should (tp-committed-success-outcome-p first-outcome))
(should-not tp--last-transaction-outcome)
(should (= stage-calls 1))
(should (= writer-calls 2))
(should (= (tp-signal-peek source) 3))
(should (= (tp-surface-revision surface) (+ revision 2)))
(with-current-buffer buffer
(should (equal (buffer-string) "3"))))))
(ert-deftest tp-transaction-test-batch-rejects-foreign-stage-capability ()
"A publication batch accepts only the closed package-owned stage seam."
(should-error
@ -1411,7 +1256,9 @@
(stage (symbol-function 'tp--publication-batch-execute-stage)))
(cl-letf (((symbol-function 'tp--publication-batch-execute-stage)
(lambda (candidate)
(tp-transaction-participate 'late #'ignore #'ignore)
(tp-transaction-participate-v2 :key 'late
:stage #'ignore
:rollback #'ignore)
(funcall stage candidate))))
(should-error (tp-signal-set source 2)
:type 'tp-publication-binding-error))
@ -1494,7 +1341,7 @@
(should (eq (aref overflow-target 0) 'old)))))
(ert-deftest tp-transaction-test-marker-restore-faults-exhaust-all-markers ()
"Restore error, quit, and throw cannot skip markers or v1 rollback."
"Restore error, quit, and throw cannot skip markers or rollback."
(dolist (kind '(error quit throw))
(tp-transaction-test--with-surface (buffer surface source)
(let* ((targets
@ -1577,7 +1424,7 @@
'(tp-transaction-test-error :phase after-commit))))
tp--last-transaction-diagnostics))))
(ert-deftest tp-transaction-test-marker-nonlocal-throw-restores-v1-state ()
(ert-deftest tp-transaction-test-marker-nonlocal-throw-restores-state ()
"Marker apply and accept throws reverse authority and live publication."
(dolist (phase '(apply accept))
(tp-transaction-test--with-surface (buffer surface source)
@ -1614,41 +1461,31 @@
(with-current-buffer buffer
(should (equal (buffer-string) "1")))))))
(ert-deftest tp-transaction-test-manifest-advertises-structured-cutover ()
"The manifest advertises structured authority and the retained v1 route."
(should (eq tp-transaction-protocol
'tp-transaction-protocol-v1+v2))
(ert-deftest tp-transaction-test-manifest-advertises-v2-only-contract ()
"The manifest advertises v2 without any legacy route capability."
(should (eq tp-transaction-protocol 'tp-transaction-protocol-v2))
(let ((manifest (tp-runtime-manifest)))
(should (equal (plist-get manifest :version) "1.0.1"))
(should (equal (plist-get manifest :version) "2.0.0"))
(should (eq (plist-get manifest :transaction-protocol)
'tp-transaction-protocol-v1+v2))
(should (plist-get manifest :batch-artifacts))
(should (eq (plist-get manifest :execution-route) 'structured))
(should (eq (plist-get manifest :execution-default) 'structured))
(should (equal (plist-get manifest :execution-routes)
'(structured v1)))
(should (eq (plist-get manifest :route-option)
'tp-transaction-execution-route))
(should (eq (plist-get manifest :batch-execution) 'structured))
(should (plist-get manifest :batch-execute))
'tp-transaction-protocol-v2))
(should (eq (plist-get manifest :structured-participant-api)
'tp-transaction-participate-v2))
(should (plist-get manifest :v1-adapter))
(should (plist-get manifest :v1-rollback-route))
(should (plist-get manifest :batch-artifacts))
(should (plist-get manifest :batch-execute))
(should (plist-get manifest :single-live-writer))
(should (plist-get manifest :shadow-proof))
(should (eq (plist-get manifest :final-marker-operation)
'tp-vector-slots/v1))
(setcar (plist-get manifest :execution-routes) 'mutated)
(setf (plist-get manifest :transaction-protocol) 'mutated
(plist-get manifest :execution-route) 'v1
(plist-get manifest :batch-execute) nil)
(let ((fresh (tp-runtime-manifest)))
(should (eq (plist-get fresh :transaction-protocol)
'tp-transaction-protocol-v1+v2))
(should (eq (plist-get fresh :execution-route) 'structured))
(should (equal (plist-get fresh :execution-routes) '(structured v1)))
(should (plist-get fresh :batch-execute)))))
(dolist (property '(:execution-route :execution-default :execution-routes
:route-option :batch-execution :v1-adapter
:v1-rollback-route))
(should-not (plist-member manifest property)))))
(ert-deftest tp-transaction-test-v1-public-controls-are-absent ()
"TP 2.0 exposes no executable v1 facade or route switches."
(should-not (fboundp 'tp-transaction-participate))
(should-not (fboundp 'tp--publish-transaction-participants))
(should-not (fboundp 'tp--surface-stage-v1-prepared))
(should-not (fboundp 'tp--transaction-validate-execution-route))
(should-not (boundp 'tp-transaction-execution-route))
(should-not (boundp 'tp--transaction-artifact-mode)))
(provide 'tp-transaction-tests)

View File

@ -11,7 +11,7 @@
;;; Commentary:
;; TP 1.0's exact signal-to-binding dependency graph, transaction-local
;; TP 2.0's exact signal-to-binding dependency graph, transaction-local
;; scheduler, scoped variable adapters, and rollback state.
;;; Code:
@ -44,8 +44,8 @@
(cl-defstruct (tp--transaction-participant
(:constructor tp--make-transaction-participant))
"One structured participant shared by the v1 and v2 transaction views."
key publish rollback protocol order stage precommit after-commit journal state)
"One structured transaction participant."
key rollback protocol order stage precommit after-commit journal state)
(cl-defstruct (tp--signal-commit-entry
(:constructor tp--make-signal-commit-entry))
@ -95,7 +95,6 @@
(defvar tp--transaction-phase-timings nil)
(defvar tp--transaction-publication-batch nil)
(defvar tp--transaction-structured-participants nil)
(defvar tp--transaction-frozen-execution-route nil)
(defvar tp--transaction-final-marker-registry nil)
(defvar tp--transaction-final-marker-owner-keys nil)
(defvar tp--transaction-final-marker-count 0)
@ -128,18 +127,6 @@
(defvar tp--transaction-rollback-final-functions nil)
(defvar tp--transaction-committed-functions nil)
(defvar tp--transaction-artifact-mode 'shadow
"Internal v1/shadow compatibility evidence mode.")
;;;###autoload
(defcustom tp-transaction-execution-route 'structured
"Live transaction execution authority.
`structured' executes package-owned batch and participant capabilities.
`v1' immediately restores the legacy prepared-list and participant-list loops."
:type '(choice (const :tag "Structured batch" structured)
(const :tag "Legacy v1" v1))
:group 'tp)
(defvar tp--transaction-participant-precommit-allowed-functions nil
"Declared internal structured-participant precommit validators.")
@ -671,7 +658,7 @@ When CREATE is non-nil, install and return a fresh hash table when absent."
(fboundp function))))
(defun tp--transaction-register-participant (participant)
"Register PARTICIPANT once for structured and legacy transaction views."
"Register structured PARTICIPANT once in the active transaction."
(unless tp--transaction-active
(signal 'tp-reactive-error
(list :participant-outside-transaction
@ -686,12 +673,12 @@ When CREATE is non-nil, install and return a fresh hash table when absent."
participant))
(defun tp--transaction-make-participant
(key publish rollback protocol precommit after-commit journal)
"Build a participant from KEY, PUBLISH, ROLLBACK, and PROTOCOL.
(key stage rollback protocol precommit after-commit journal)
"Build a participant from KEY, STAGE, ROLLBACK, and PROTOCOL.
PRECOMMIT and AFTER-COMMIT are optional internal callbacks; JOURNAL is opaque
owner-local rollback state."
(unless (functionp publish)
(signal 'wrong-type-argument (list 'functionp publish)))
(unless (functionp stage)
(signal 'wrong-type-argument (list 'functionp stage)))
(unless (functionp rollback)
(signal 'wrong-type-argument (list 'functionp rollback)))
(unless (tp--transaction-participant-precommit-function-p precommit)
@ -701,36 +688,16 @@ owner-local rollback state."
(signal 'wrong-type-argument (list 'functionp after-commit)))
(tp--make-transaction-participant
:key (tp--copy-property-value key)
:publish publish
:rollback rollback
:protocol protocol
:order (prog1 tp--transaction-participant-order
(cl-incf tp--transaction-participant-order))
:stage publish
:stage stage
:precommit precommit
:after-commit after-commit
:journal journal
:state 'prepared))
;;;###autoload
(defun tp-transaction-participate (key publish rollback)
"Register rollback-capable PUBLISH work under transaction-local KEY.
PUBLISH runs after every affected surface has published its candidate buffer
and side state, but before the transaction commits its source values. If this
or any later publication step fails, ROLLBACK runs in reverse publication
order. Both functions take no arguments. KEY must be unique in the outer
transaction. The public v1 route is retained and receives an internal v2
structured view over the same participant object."
(unless tp--transaction-active
(signal 'tp-reactive-error (list :participant-outside-transaction key)))
(unless key
(signal 'tp-reactive-error (list :participant-key key)))
(let ((participant
(tp--transaction-make-participant
key publish rollback 'v1-bridge nil nil nil)))
(tp--transaction-register-participant participant)
key))
(cl-defun tp--transaction-participate-v2
(&key key stage rollback precommit after-commit journal)
"Register internal KEY with structured STAGE and ROLLBACK capabilities.
@ -761,14 +728,6 @@ without exposing TP's internal participant object."
"Return the authoritative participants in deterministic declaration order."
(reverse tp--transaction-participants))
(defun tp--publish-transaction-participants ()
"Publish registered transaction participants in declaration order."
(dolist (participant (tp--transaction-participants-in-registration-order))
(push participant tp--transaction-published-participants)
;; Mark first so a stage that mutates and then signals remains rollbackable.
(setf (tp--transaction-participant-state participant) 'staged)
(funcall (tp--transaction-participant-publish participant))))
(defun tp--transaction-validate-structured-participants ()
"Return the frozen participant vector after exact identity validation."
(let* ((participants tp--transaction-structured-participants)
@ -820,15 +779,6 @@ without exposing TP's internal participant object."
(setf (tp--transaction-participant-state participant) 'rolled-back)))
(nreverse failures)))
(defun tp--run-transaction-participant-precommits ()
"Run declared structured participant validators in registration order."
(dolist (participant (tp--transaction-participants-in-registration-order))
(when-let* ((function (tp--transaction-participant-precommit participant)))
(unless (tp--transaction-participant-precommit-function-p function)
(signal 'tp-reactive-error
(list :invalid-participant-precommit function)))
(funcall function))))
(defun tp--run-structured-transaction-participant-precommits ()
"Run validators from the frozen structured participant vector."
(let ((participants (tp--transaction-validate-structured-participants)))
@ -841,15 +791,6 @@ without exposing TP's internal participant object."
(list :invalid-participant-precommit function)))
(funcall function)))))
(defun tp--commit-transaction-participants ()
"Commit participant states and queue their contained after-commit work."
(dolist (participant (tp--transaction-participants-in-registration-order))
(when (eq (tp--transaction-participant-state participant) 'staged)
(setf (tp--transaction-participant-state participant) 'committed)
(when-let* ((function
(tp--transaction-participant-after-commit participant)))
(tp--enqueue-after-commit function)))))
(defun tp--commit-structured-transaction-participants ()
"Commit states from the frozen structured participant vector."
(let ((participants (tp--transaction-validate-structured-participants)))
@ -947,34 +888,6 @@ without exposing TP's internal participant object."
tp--transaction-final-accept-function function)))
(setq tp--transaction-final-accept-function function))
(defun tp--transaction-v2-artifacts-enabled-p ()
"Return non-nil when the additive v2 shadow artifacts are enabled."
(pcase tp--transaction-artifact-mode
('v1 nil)
((or 'shadow 'v1+v2-shadow) t)
(_ (signal 'tp-transaction-contract-error
(list :artifact-mode tp--transaction-artifact-mode)))))
(defun tp--transaction-validate-execution-route (route)
"Return supported execution ROUTE or signal before transaction mutation."
(unless (memq route '(structured v1))
(signal 'tp-transaction-contract-error (list :execution-route route)))
route)
(defun tp--transaction-current-execution-route ()
"Return the validated route frozen at outer transaction entry."
(tp--transaction-validate-execution-route
tp--transaction-frozen-execution-route))
(defun tp--transaction-structured-route-p ()
"Return non-nil when the frozen live route is `structured'."
(eq (tp--transaction-current-execution-route) 'structured))
(defun tp--transaction-publication-candidate-enabled-p ()
"Return non-nil when this transaction must build a publication candidate."
(or (tp--transaction-structured-route-p)
(tp--transaction-v2-artifacts-enabled-p)))
(defun tp--transaction-current-outcome-cell ()
"Return the active transaction's caller-retainable one-slot outcome cell."
(unless tp--transaction-active
@ -1020,7 +933,6 @@ without exposing TP's internal participant object."
(when tp--transaction-publication-batch
(signal 'tp-publication-state-error
(list :duplicate-transaction-batch batch-id)))
(when (tp--transaction-publication-candidate-enabled-p)
(setq tp--transaction-publication-batch
(tp--publication-batch-prepare
:transaction-id tp--transaction-id
@ -1031,7 +943,7 @@ without exposing TP's internal participant object."
:journals (tp--transaction-batch-journal-view surface-journals)
:stage-entries stage-entries
:final-accept tp--transaction-final-accept-function
:diagnostics nil)))
:diagnostics nil))
tp--transaction-publication-batch)
(defun tp--transaction-batch-transition (next)
@ -1247,8 +1159,7 @@ through TP's closed marker-operation whitelist."
(unwind-protect
(progn
(tp--transaction-apply-final-markers)
(if (and (tp--transaction-structured-route-p)
tp--transaction-publication-batch)
(if tp--transaction-publication-batch
(let ((candidate-function
(tp-publication-batch-candidate-final-accept
tp--transaction-publication-batch)))
@ -1281,8 +1192,7 @@ through TP's closed marker-operation whitelist."
(defun tp--transaction-run-shadow-proof (phase)
"Compare structured artifacts with the selected single live result for PHASE."
(when (and tp--transaction-publication-batch
(tp--transaction-v2-artifacts-enabled-p))
(when tp--transaction-publication-batch
(let ((ok t) results outcome-equivalent)
(dolist (entry
(tp-publication-batch-candidate-entries
@ -1519,10 +1429,7 @@ the primary condition data."
"Call FUNCTION in one atomic signal and binding transaction."
(if tp--transaction-active
(funcall function)
(let ((frozen-route
(tp--transaction-validate-execution-route
tp-transaction-execution-route))
after-commit result
(let (after-commit result
(tp--transaction-contained-failures nil))
(setq tp--last-transaction-outcome nil
tp--last-shadow-proof nil)
@ -1551,7 +1458,6 @@ the primary condition data."
(tp--transaction-signal-commit-journal nil)
(tp--transaction-publication-batch nil)
(tp--transaction-structured-participants nil)
(tp--transaction-frozen-execution-route frozen-route)
(tp--transaction-final-marker-registry
(make-vector tp--final-marker-max-count nil))
(tp--transaction-final-marker-owner-keys
@ -1577,21 +1483,16 @@ the primary condition data."
(tp--transaction-enter-phase 'recompute)
(tp--flush-dirty-bindings)
(tp--transaction-enter-phase 'publication)
(tp--transaction-current-execution-route)
(setq tp--transaction-structured-participants
(vconcat
(tp--transaction-participants-in-registration-order)))
(run-hooks 'tp--transaction-publish-functions)
(tp--transaction-batch-transition 'participants)
(tp--transaction-enter-phase 'participants)
(if (tp--transaction-structured-route-p)
(tp--stage-structured-transaction-participants)
(tp--publish-transaction-participants))
(tp--transaction-batch-transition 'precommit)
(tp--transaction-enter-phase 'precommit)
(if (tp--transaction-structured-route-p)
(tp--run-structured-transaction-participant-precommits)
(tp--run-transaction-participant-precommits))
(tp--run-transaction-precommit-functions)
(tp--transaction-freeze-final-markers)
(tp--transaction-sync-publication-batch)
@ -1616,9 +1517,7 @@ the primary condition data."
tp--transaction-final-accept-function)))
(tp--transaction-run-final-accept)
(setq success t)
(if (tp--transaction-structured-route-p)
(tp--commit-structured-transaction-participants)
(tp--commit-transaction-participants))
(tp--transaction-run-shadow-proof 'commit)
(when quit-flag
(setq pending-quit t

View File

@ -3639,13 +3639,6 @@ JOURNALS and VIEWS are exact references to the shared rollback state."
(tp--publish-one-surface prepared)
(tp--surface-record-publication-operation-counts prepared))))
(defun tp--surface-stage-v1-prepared (prepared next-generation)
"Publish legacy PREPARED list after exposing NEXT-GENERATION."
(setq tp--surface-transaction-id next-generation)
(dolist (candidate prepared)
(tp--publish-one-surface candidate)
(tp--surface-record-publication-operation-counts candidate)))
(defun tp--surface-record-publication-operation-counts (prepared)
"Record PREPARED's actual live report counts into its exact batch entry."
(when tp--transaction-publication-batch
@ -3691,7 +3684,6 @@ JOURNALS and VIEWS are exact references to the shared rollback state."
(puthash 'change-group group state)
(puthash 'surface-transaction-id-before
tp--surface-transaction-id state)
(when (tp--transaction-publication-candidate-enabled-p)
(let* ((batch-id (tp--next-publication-batch-id))
(entries
(tp--surface-shadow-target-entries
@ -3699,12 +3691,10 @@ JOURNALS and VIEWS are exact references to the shared rollback state."
next-generation)))
(tp--transaction-begin-publication-batch
batch-id entries state 'tp--surface-stage-publication-entries)
(tp--transaction-batch-transition 'staged)))
(tp--transaction-batch-transition 'staged))
(let ((tp--surface-publishing t))
(if (tp--transaction-structured-route-p)
(tp--publication-batch-execute-stage
tp--transaction-publication-batch)
(tp--surface-stage-v1-prepared prepared next-generation)))))))
tp--transaction-publication-batch))))))
(defun tp--surface-precommit-step (step state)
"Report precommit STEP for transaction STATE."

View File

@ -12,9 +12,8 @@
;;; Commentary:
;; Internal immutable artifacts and one-shot state machines for TP publication.
;; The default structured route executes the package-owned entry-stage
;; capability stored in a batch candidate; the retained v1 route bypasses that
;; capability. This module never edits a buffer itself: `tp-reactive' drives
;; 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.
@ -35,7 +34,7 @@
"Invalid TP final-accept marker"
'tp-transaction-contract-error)
(defconst tp-transaction-protocol 'tp-transaction-protocol-v1+v2
(defconst tp-transaction-protocol 'tp-transaction-protocol-v2
"Transaction protocol implemented by this TP package version.")
(defconst tp--publication-batch-transitions

14
tp.el
View File

@ -2,7 +2,7 @@
;; Copyright (C) 2024-2026 Geekinney
;; Version: 1.0.1
;; Version: 2.0.0
;; Keywords: convenience text-properties
;; Author: Geekinney (kinneyzhang666@gmail.com)
;; Package-Requires: ((emacs "28.1"))
@ -26,7 +26,7 @@
;; tp-style.el Native property policies, contribution composition,
;; named declarations, and explicit computed values.
;; tp-transaction.el
;; Additive publication batch, marker, and outcome contracts.
;; Structured publication batch, marker, and outcome contracts.
;; tp-reactive.el Exact signals, bindings, transactions, and scoped variable
;; adapters.
;; tp-surface.el Retained plans, objects, range anchors, mounts, indexes,
@ -66,20 +66,12 @@
(require 'tp-builtins)
(defconst tp--runtime-manifest
`(:package tp :version "1.0.1"
`(:package tp :version "2.0.0"
:transaction-protocol ,tp-transaction-protocol
:batch-artifacts t
:execution-route structured
:execution-default structured
:execution-routes (structured v1)
:route-option tp-transaction-execution-route
:batch-execution structured
:batch-execute t
:structured-participant-api tp-transaction-participate-v2
:v1-adapter t
:v1-rollback-route t
:single-live-writer t
:shadow-proof t
:final-marker-operation tp-vector-slots/v1)
"Immutable package capability facts for cross-package compatibility checks.")