distributed 4.0.0

CQRS/ES framework for Rust using Plain Old Rust Structs — append-only events, replay, snapshots, outbox, service bus, and pluggable infrastructure
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
### What's changed in v4.0.0

* chore(deps): update actions/checkout action to v7 (by @renovate[bot])

* chore(deps): update actions/upload-artifact action to v7 (by @renovate[bot])

* chore(deps): update marocchino/sticky-pull-request-comment action to v3 (by @renovate[bot])

* chore: atc gitignore (by @patrickleet)

* feat: GraphQL query edge, client replica, and e2e-ui demos (#127) (by @patrickleet)

  BREAKING CHANGE: * feat(e2e-ui): polish demos, event domains, blob URL routes

  Teach-first home and cleaned UI across todos/chat/session/admin/blob.
  Todo/chat use public #[event] command methods; handlers stay thin.
  Blob moves to [[gameId]] with local board paint so New game updates
  immediately while history still uses the shared cache.

  * fix(e2e-ui): wait for move confirms before blob next level

  Optimistic level-complete painted the board before the finishing move
  committed, so start_level loaded an incomplete aggregate and rejected.
  Gate Next level on server-confirmed completion and await the move drain.

  * fix(e2e-ui): restore todo/chat event wire formats

  The #[event]+when= domain polish changed todo.completed/reopened/archived
  payloads (empty → owner_id) and renamed (title → owner_id,title). Existing
  streams failed hydrate, so load/command paths looked like nothing saved.
  Restore Result + record_* command/event pairs with the original payloads;
  handlers match again. Chat restored for the same hard-error pattern.

  * fix(e2e-ui): drop fake board from blob empty state

  Show only copy + Start game when no game is selected, so the empty
  state does not look like a broken half-rendered grid.

  * test(e2e-ui): add Playwright browser e2e suite

  Chromium flows against the live Fieldnote UI: OIDC Login V2 as alice/admin,
  todos lifecycle, chat post, blob start/move, session, unauth redirects, and
  role-gated admin. Run with make up + make run, then make test-browser.

  * ci: run e2e-ui offline suite and Playwright on PR/main

  Add integration-e2e-ui workflow (make test + Docker stack + browser e2e)
  and wire it into on-pr-quality and on-push-main so Fieldnote fixture
  regressions gate merges and releases.

  * fix: green CI for e2e-ui HTTP surface, m2m where, blob start

  Disable public HTTP command wildcards (T0 404) while mounting Zitadel
  ingress/scrape explicitly. Include shadow/through tables in GraphQL
  surface so m2m relationship predicates appear on bool_exp. Survive
  blob /blob → /blob/{id} remount via remembered rows and list-seed merge.

  * fix(e2e-ui): hydrate-safe blob start + align offline UI contracts

  Wait for client hydration before Start/New game, paint from mutation
  payload without remount-racing goto, and assert blob_games_start over
  the network in Playwright. Update api-contract tests for the current
  home story and generated command policies re-export.

  * feat: extract Distributed JS client package

  Implements [[tasks/distributed-js-client-1]]

  * feat: select normalized cache engine

  Implements [[tasks/graphql-qs-client-replica-2]]

  * feat: emit authorized client manifests

  * chore: refresh e2e GraphQL artifacts

  * chore: refresh e2e command artifacts

  * feat: add normalized client replica

  * feat: add typed causal command contracts

  Implements tasks/graphql-qs-client-replica-4.

  * feat: add durable causal command dispatch

  Implements [[tasks/graphql-qs-client-replica-5]]

  * feat: harden causal projection protocol

  Implements tasks/graphql-qs-client-replica-15.

  * feat: expose causal GraphQL client protocol

  * feat: add unified client compiler foundation

  * feat: harden client compiler contracts

  * feat: compile recursive client replica plans

  Implements [[tasks/graphql-qs-client-replica-6]]

  * feat: complete compiler-backed client plans

  * docs: move durable decisions to distributed kb

  * feat: bind clients to authorization surfaces

  Implements the server and compiler slice of [[tasks/graphql-qs-client-replica-10]].

  * fix: keep manifest validation test current

  Covers [[tasks/graphql-qs-client-replica-10]].

  * feat!: complete compiler-backed client replica

  Generate role-scoped query and command artifacts from Rust Surface IR, make the normalized causal replica the only public client runtime, and migrate the SvelteKit e2e consumer to the generated contract.

  BREAKING CHANGE: remove the pre-release document cache, manual target/reconcile APIs, raw GraphQL command catalog, and pilot persistence format.

  * fix: isolate GraphiQL introspection limits

  * fix!: split query authority from causal comparability

  * fix: retain projection poll liveness

  * fix: preserve stale replica views during revalidation

  Implements [[tasks/graphql-qs-client-replica-18]]

  * fix: stabilize optimistic command feedback

  * fix: keep todo command controls visually stable

  * fix: preserve optimistic order while revalidating

  * fix: fence stale revalidation after projected commands

  * fix: retain projected rows until read-model echo

  * feat: add direct-only projection owners

  Implements [[tasks/graphql-qs-client-replica-20]].

  * fix(cli): treat exact --documents paths as literal files (#136)

  Skip glob expansion when a --documents value is an existing file so path
  segments like SvelteKit [[gameId]] are not interpreted as character classes.
  Globs continue to expand as before for patterns that are not existing files.

  * fix: enforce asserted roles in strict OIDC mode

  Implements [[tasks--graphql-qs-epic]]

  * fix: share expiring OIDC JWKS cache per engine

  Implements [[tasks--graphql-qs-epic]]

  * test: cover exact GraphQL document paths

  Implements [[tasks--graphql-qs-epic]]

  * fix: address GraphQL query service review findings

  Implements [[tasks--graphql-qs-epic]]

  * fix: keep projected fences against stale high-revision snapshots

  Conflicting GraphQL snapshot bodies must not override a direct projection
  even when response evidence is stamped later than the command. That race
  was rolling back live e2e blob moves after held revalidation responses.

  * fix: fence projected rows by response start

  Implements [[tasks--graphql-qs-epic]]

  * docs: position Distributed as full-stack CQRS + GraphQL + JS

  Expand the README opening, design goals, GraphQL section, e2e-ui template
  table (blob, OIDC, Projected), and @hops-ops/distributed client surface so the
  docs match the platform scope of the query-service epic.

  * docs: lead README with Fieldnote, Blob, and live demos

  Open with runnable full-stack examples (e2e-ui, GraphiQL, capability map)
  before dependency wiring and the library quick start.

  * docs: highlight first-class OIDC and multi-IdP e2e

  Call out built-in OidcBearer on the GraphQL edge and the live Zitadel,
  Keycloak, and Authentik compose/test suites in the README demos and identity
  section.

  * docs: lead demos with e2e-ui code index

  Point root README at the Fieldnote files that make SSR, live chat,
  Projected blob, dual client surfaces, and OIDC feel product-grade.
  Rewrite tests/e2e-ui/README as the deeper code map plus runbook.

  * docs(e2e-ui): refresh home walkthrough for latest patterns

  Update the Fieldnote index route code tour: Fact vs Projected,
  dual client surfaces, CausalCommandContext, real RLS/effects
  samples, and admin as a separate generated client.

  * refactor(e2e-ui): nest CSS natively; keep postcss for custom-media

  Nest app.css and route/component styles with native nesting (&, &-,
  nested @media). Leave nesting-rules off in postcss-preset-env so
  BEM &-suffix is not mis-expanded; keep custom-media and range queries.

  * revert(e2e-ui): undo CSS nesting rewrite

  Restores flat selectors that postcss/Vite were expanding incorrectly
  (&-suffix → -suffix.parent). Style architecture refactor should be
  component extraction + scoped Svelte CSS, not a global nest pass.

  * refactor(e2e-ui): product components + split global CSS

  Extract scoped product primitives (AppPage, PageHeader, InlineAlert,
  Panel, StatRow) and extend Button with ink/ghost/quiet/sm. Migrate
  todos/chat/blob/admin onto them. Split the 1.6k app.css into tokens
  (app.css), layout chrome (chrome.css), and home-only wireframe
  (home.css) so shared CSS is intentional and route/product UI can use
  Svelte scoping.

  * fix(e2e-ui): polish chat and blob after product shell migration

  Restore chat shell CSS variables and live-status states; tidy blob
  markup under AppPage.

  * fix(e2e-ui): read Zitadel project roles from access token

  Admin grants live on the access token (urn:zitadel:iam:org:project:roles),
  not the id_token. Session only decoded the id_token, so admin always became
  user. Merge groups from both tokens and align session/nav admin checks.

  * fix(e2e-ui): type-cast refreshed token when storing groups

  * fix(e2e-ui): always request Zitadel project role scopes at login

  Admin project role is granted in bootstrap, but tokens only include
  role claims when authorize requests the reserved roles scopes. Merge
  those scopes into every OIDC start (not only a complete OIDC_SCOPES
  env) and read roles from access + id tokens including project-id claims.

  * fix(e2e-ui): let admin use the normal fieldnote app surface

  fieldnote was registered for role user only, so sessions with concrete
  role admin failed protocol selection on todos/chat/blob. Register the
  shared surface for admin+user (admin still uses fieldnote-admin for
  elevated ops) and regenerate clients.

  * refactor: split distributed-replica into folder modules

  Move the monolithic replica implementation into package-private modules
  (types, constants, clocks, hydration, optimistic, helpers, watch, impl)
  while keeping createDistributedReplica on the same public export path.

  * fix(e2e-ui): align Playwright with product shell and admin hydration

  Todo specs still targeted removed fn-* classes after the product panel
  refactor. Update selectors, wait for fieldnote-admin hydration before
  force-archive, assert archive on the clicked row, and match optimistic
  order checks to create/reopen list behavior.

  * refactor: split command-runtime into folder modules

  Move the monolithic command runtime into package-private modules
  (symbols, types, errors, lifecycle, helpers, create) while keeping the
  same public export path for createReplicaCommandRuntime and related APIs.

  * refactor: split cache-engine into folder modules

  Move the private purpose-built cache engine into types, errors, helpers,
  engine, and create modules while preserving the internal import path for
  createCacheEngine, cacheIndexKey, and related types.

  * refactor: split query-plan into filter/order/pagination modules

  Move portable query-plan evaluation into focused modules (types, resolve,
  filter, order, pagination, util) while keeping the same public export
  path for evaluateReplicaFilter, compareReplicaOrder, and pagination.

  * refactor(js): extract shared helpers into src/lib

  Deduplicate deepEqual, reportSafely/reportUnhandled, isPlainRecord,
  compareCodeUnits, assertName, and freezeRecord behind js/src/lib so
  cache-engine, replica, and command-runtime share one implementation.

  * fix(js): clear unused imports so tsc check matches CI

  Module splits left type-only and value imports unused; npm test only
  runs build (no unusedLocals), while CI quality runs check --noEmit and
  failed. Strip unused imports so npm run quality is green.

  * refactor(js): split commands into types, prepare, presets, receipt

  Extract command artifact types, contract errors, and implementation so
  prepare/inventory/receipt concerns are separable. Public exports stay on
  ./commands.js. Implements [[tasks/js-less-context-7]].

  * refactor(js): split identity, helpers barrels, persistence, index, diagnostics

  - identity: keys/codec/clone barrels over implementation
  - command-runtime: concern barrels over helpers-impl
  - persistence, index-maintenance, diagnostics: folder + thin re-exports

  Implements [[tasks/js-less-context-8]] [[tasks/js-less-context-9]]
  [[tasks/js-less-context-10]] [[tasks/js-less-context-11]]
  [[tasks/js-less-context-12]].

  * refactor(js): extract distributed-replica impl concern modules

  Move fetch/live, protocol generation, optimistic layers, dehydrate/hydrate
  orchestration, and diagnostics emission into package-private helpers with
  thin class delegates. Implements [[tasks/js-less-context-2]] through
  [[tasks/js-less-context-6]].

  * refactor(js): body-split commands, identity, helpers, persistence, index, diagnostics

  Move function bodies into concern modules instead of thin re-export barrels so
  agents load only the needed concern. implementation.ts/helpers-impl.ts become
  re-export surfaces. Quality: 253 tests + check + pack:smoke + publint.

  Implements [[tasks/js-less-context-7]] [[tasks/js-less-context-8]]
  [[tasks/js-less-context-9]] [[tasks/js-less-context-10]]
  [[tasks/js-less-context-11]] [[tasks/js-less-context-12]]

  * refactor(js): move command-runtime helpers into lib/

  Bodies live under command-runtime/lib/{inventory,binding,effects,transport,
  status,output,projection,util}.ts. helpers.ts remains the stable barrel;
  helpers-impl.ts re-exports helpers.

  * refactor(js): drop command-runtime helpers.ts barrel

  Barrel lives at lib/index.ts; create.ts imports from ./lib/index.js.
  Removes helpers.ts and helpers-impl.ts naming leftover.

  * refactor: split graphql compile modules

  Implements [[tasks/rust-less-context-6]]

  * refactor: split client compiler manifest module

  Implements [[tasks/rust-less-context-3]]

  * refactor: split client compiler graphql pipeline

  Implements [[tasks/rust-less-context-2]]

  * refactor: split client compiler render module

  Implements [[tasks/rust-less-context-4]]

  * refactor: split client manifest module

  Implements [[tasks/rust-less-context-7]]

  * refactor: split command ledger module

  Implements [[tasks/rust-less-context-12]]

  * refactor: split sqlx repository core

  Implements [[tasks/rust-less-context-13]]

  * refactor: split command manifest compiler

  Implements [[tasks/rust-less-context-5]]

  * refactor: split graphql protocol helpers

  Implements [[tasks/rust-less-context-9]]

  * refactor: split GraphQL command contract modules

  Implements [[tasks/rust-less-context-8]]

  * fix(macros): update trybuild paths after command_contract split

  Compile-fail diagnostics now point at command_contract/effect_wire.rs
  (and sibling modules) instead of the old monolith path.

  * refactor: split projection protocol store module

  Implements [[tasks/rust-less-context-15]]

  * fix: re-export ProjectionPartitionSnapshot from store split

  The type lived in store/query.rs but was omitted from store/mod.rs
  pub(crate) re-exports, breaking graphql/sqlx consumers.

  * refactor: split projection protocol codec

  Implements [[tasks/rust-less-context-16]]

  * refactor: split hot rust modules

  Implements [[tasks/rust-less-context-23]]

  * refactor: split projector runtime module

  Implements [[tasks/rust-less-context-18]]

  * refactor: split sqlx read model module

  Implements [[tasks/rust-less-context-14]]

  * refactor: split distributed macros entry modules

  Implements [[tasks/rust-less-context-22]]

  * refactor: split graphql surface modules

  Implements [[tasks/rust-less-context-10]]

  * refactor: split graphql engine orchestration

  Implements [[tasks/rust-less-context-11]]

  * refactor: split microsvc service module

  Implements [[tasks/rust-less-context-17]]

  * refactor: folderize SQLx projection protocol

  Implements [[tasks/rust-less-context-19]]

  * refactor: folderize in-memory projection protocol

  Implements [[tasks/rust-less-context-20]]

  * refactor: share pure projection backend helpers

  Implements [[tasks/rust-less-context-21]]

  * refactor: share projection kind storage decoding

  Implements [[tasks/rust-dry-2]]

  * refactor: share projection digest helper

  Implements [[tasks/rust-dry-3]]

  * refactor: share projection failure batch predicate

  Implements [[tasks/rust-dry-4]]

  * refactor: share projection ownership validation

  Implements [[tasks/rust-dry-5]]

  * test: share projection protocol scenarios

  Implements [[tasks/rust-dry-6]]

  * refactor!: rename client GraphQL protocol version 2 → 1

  Never-released wire family; first public ship should be protocol v1, not v2.
  Constants, envelopes, compiler emit, client checks, fixtures, and docs only.
  Left unrelated versions alone (variableCodec v2, digest domain tags, package semver).

  * fix: update postgres/cli fingerprint goldens for protocol v1

  All-features and distributed_cli integration asserted stale schema
  fingerprints that include protocol_version in the hash input.

  * refactor!: ship unreleased wire versions as v1

  Normalize client manifest, variableCodec, command extension slots, and
  protocol-manifest epoch to 1 so the first public release does not imply
  prior public wire families. Refresh fingerprints, goldens, and fixtures.

  * fix: update orders harness schema fingerprint for v1 wire versions

  Ignored distributed_cli integration test expected the pre-v1 schema
  fingerprint; regenerate to match manifest/epoch/slot version renames.

  * fix: update postgres role-surface schema fingerprint for v1

  all-features client_surface_parity asserted a pre-v1 schema hash for the
  orders postgres role surface after wire version renames.

  * chore: drop NPM_RELEASE_READY gate from tag publish

  Bootstrap is complete; a permanent repo variable is unnecessary. Preflight
  still validates the tag form and that @hops-ops/distributed exists on npm.

  * fix(e2e-ui): drop dead Manage Account and Protected Page links

  Remove account-menu link to Zitadel console and session page button to
  /protected, which has no route in the template.

  * chore(e2e-ui): refresh generated clients after protocol v1 wire renames

  Schema fingerprints in the checked-in user/admin clients drifted after
  manifest/codec/slot versions shipped as v1. Keep gen-client outputs in
  lockstep for make check-client / CI drift gates.

  * chore: apply cargo fix for unused re-exports

  Drop unused imports/re-exports reported on default cargo build. Does not
  remove underlying types or methods still present for internal/store use.

  * chore: silence false dead_code noise without deleting protocol surface

  Default cargo build warns on helpers whose callers live behind graphql/sqlx
  or unit tests. Drop true unused re-exports/imports, restore feature-gated
  re-exports that cargo fix removed, and allow dead_code only on intentional
  store/protocol surface and test-oriented wrappers so product builds stay clean.

  * fix: restore test-only re-exports removed by cargo fix

  cargo fix / dead_code cleanup dropped re-exports that unit tests import
  (client_manifest_from_surface, ProjectionCheckpointProbe, obligation
  resolution types). Non-test lib builds still allow unused_imports so
  featureless cargo build stays quiet; lib tests compile again.

  * refactor(e2e-ui): rename Fieldnote demo to Todos

  Drop the invented product name. Surface IDs are todos / todos-admin so the
  demo is obviously a todos app for engineers, not a fictional brand.

  * refactor(e2e-ui): name template e2e-ui; keep Todos as one demo

  Surfaces are e2e-ui / e2e-ui-admin. Todos is only the /todos demo (alongside
  chat, blob, admin)—not the brand for the whole fixture.

  * feat: infer natural read-model storage names

  Implements [[tasks/graphql-qs-projection-model-5]]

  * refactor: rename successful command outcomes

  Implements [[tasks/graphql-qs-projection-model-2]]

  * feat(js): replace optimistic layers atomically

  Evaluate corrections against the target prefix, preserve layer metadata, and publish only the final rebased graph. Add typed missing-layer handling and private replica coverage.

  Implements [[tasks/graphql-qs-projection-model-14]]

  * feat: add typed domain-event occurrence runtime

  Implements [[tasks/graphql-qs-projection-model-3]]

  * feat: generate sourced domain-event capture

  Implements [[tasks/graphql-qs-projection-model-4]]

  * feat: define portable projection program IR

  Implements [[tasks/graphql-qs-projection-model-7]]

  * Implement fluent projection commit API

  Implements [[tasks/graphql-qs-projection-model-6]]

  * Implements [[tasks/graphql-qs-projection-model-8]]

  * Fix projection direct candidate classification

  * feat: add projection catalog identities and placement

  Implements [[tasks/graphql-qs-projection-model-9]]

  * test: align compile failures with succeeded outcomes

  * Implement causal projection executor and graph workspace

  Implements [[tasks/graphql-qs-projection-model-10]]

  * feat: declare exact command projection events

  * feat: bind modeled projections to GraphQL surfaces

  * feat: expose crate projection expression views

  * fix: authorize modeled projection surfaces

  * feat: type sourced domain event contracts

  * fix: seal adapter event contracts

  * fix: witness exact domain event bodies

  * test: refresh event contract diagnostics

  * feat: export strict authorized projection manifests

  * feat: model authorized optimistic projection occurrences

  * feat: define role-safe projection delta v1

  * chore: export projection delta module

  * fix: seal projection delta authority

  * fix: canonicalize projection delta final state

  * feat: lower sealed role-safe projection deltas

  * fix: harden projection delta recovery scopes

  * feat: expose role-safe projection partitions [[tasks/graphql-qs-projection-model-13]]

  * test: freeze projection delta v1 vectors

  Adds the cross-language canonical fixture, exact boundary coverage, authorization transition matrices, placement and provenance guards, relationship/delete cases, and manifest slot evidence.

  Implements [[tasks/graphql-qs-projection-model-13]]

  * feat: parse projection manifest v2 contracts

  Implements [[tasks/graphql-qs-projection-model-15]]

  * feat: compile projection preview artifacts

  Ports ProjectionDelta wire-v1 validation and canonical ordering, lowers command projection previews with conservative recovery, and emits fail-closed command artifact v2 without legacy effects or confirmations.

  [[tasks/graphql-qs-projection-model-15]]

  * test: lock projection compiler contracts

  Completes compiler validation, conservative relationship recovery, artifact v2 fixtures, and cross-layer regression coverage for projection programs, bindings, previews, and the frozen ProjectionDelta wire contract.

  [[tasks/graphql-qs-projection-model-15]]

  * fix: prove optimistic projection inputs

  Requires complete value-bearing upserts, preserves keyed relationship invalidation authority, and validates preview provenance against consistent opaque slot types with fail-closed numeric and shape semantics.

  [[tasks/graphql-qs-projection-model-15]]

  * fix: close projection preview safety gaps

  Separates absent values from clearing intent, proves scalar record keys, bounds expansion before allocation, narrows invalidation and revalidation scope, and emits an explicit compiler-owned artifact-v2 seam for Task 16.

  [[tasks/graphql-qs-projection-model-15]]

  * fix: fail closed at projection runtime seam

  Matches frozen composite-expression absence semantics, treats nested unset as non-destructive uncertainty, and omits artifact-v2 dispatch through the v1 JavaScript runtime while retaining typed inspectable artifacts.

  [[tasks/graphql-qs-projection-model-15]]

  * fix: fail closed on first-present unset

  Preserves frozen FirstPresent semantics by skipping only absent values, treating nested unset as non-destructive uncertainty, and proving record-scoped recovery without optimistic writes.

  [[tasks/graphql-qs-projection-model-15]]

  * Add authenticated projection metadata authority [[tasks/graphql-qs-projection-model-11]]

  * Persist modeled projection receipts exactly [[tasks/graphql-qs-projection-model-11]]

  * Mount modeled projectors and derive causal obligations [[tasks/graphql-qs-projection-model-11]]

  * Format Task 11 runtime changes [[tasks/graphql-qs-projection-model-11]]

  * Bound modeled replay metadata lifetime and decoding [[tasks/graphql-qs-projection-model-11]]

  * Format integrated projection runtime [[tasks/graphql-qs-projection-model-11]]

  * feat: adapt modeled direct projections to exact proof [[tasks/graphql-qs-projection-model-17]]

  * Bind modeled direct proof to active program [[tasks/graphql-qs-projection-model-17]]

  * feat(projections): implement atomic snapshot adapters [[tasks/graphql-qs-projection-model-12]]

  * Harden projection conformance proofs [[tasks/graphql-qs-projection-model-12]]

  * feat: migrate Todo and Chat projection leaves [[tasks/graphql-qs-projection-model-18]]

  * Keep purge descriptor test-only [[tasks/graphql-qs-projection-model-18]]

  * Fix FK-authoritative relationship delegation [[tasks/graphql-qs-projection-model-12]]

  * fix: keep purged todos terminal [[tasks/graphql-qs-projection-model-18]]

  * Test hydrated purge terminality [[tasks/graphql-qs-projection-model-18]]

  * Reject mixed direct projection epochs [[tasks/graphql-qs-projection-model-17]]

  * Harden direct projection owner compatibility [[tasks/graphql-qs-projection-model-17]]

  * feat: apply authoritative projection deltas in JS [[tasks/graphql-qs-projection-model-16]]

  * fix: harden JS projection reconciliation [[tasks/graphql-qs-projection-model-16]]

  * fix: close projection reconciliation audit gaps [[tasks/graphql-qs-projection-model-16]]

  * fix: close projection runtime follow-up gaps [[tasks/graphql-qs-projection-model-16]]

  * feat: model Blob direct projection [[tasks/graphql-qs-projection-model-19]]

  * Implement modeled projection developer experience [[tasks/graphql-qs-projection-model-20]]

  * Fix modeled topology integration [[tasks/graphql-qs-projection-model-20]]

  * Harden modeled projection rollout integration [[tasks/graphql-qs-projection-model-20]]

  * Fence draining projection replay with lifecycle proofs [[tasks/graphql-qs-projection-model-20]]

  * Revalidate terminal projection receipt replays [[tasks/graphql-qs-projection-model-20]]

  * Fix projection draining revalidation bridge [[tasks/graphql-qs-projection-model-20]]

  * Bind empty projection receipts to command contracts [[tasks/graphql-qs-projection-model-20]]

  * Classify mixed draining projection fallback [[tasks/graphql-qs-projection-model-20]]

  * Correct deletion projection examples [[tasks/graphql-qs-projection-model-20]]

  * Fix generated projection UI integration [[tasks/graphql-qs-projection-model-20]]

  * Migrate typed command manifest coverage [[tasks/graphql-qs-projection-model-20]]

  * Strengthen projection manifest coverage [[tasks/graphql-qs-projection-model-20]]

  * Accept embedded model invalidation [[tasks/graphql-qs-projection-model-20]]

  * Constrain embedded invalidation authority [[tasks/graphql-qs-projection-model-20]]

  * Fix PR 127 CI regressions [[incidents/pr-127-ci-failures-after-projection-model]]

  * Wait for durable Todo commands in live test [[incidents/pr-127-ci-failures-after-projection-model]]

  * Fix application projection visibility authority [[incidents/pr-127-ci-failures-after-projection-model]]

  * Infer projected responses from read models [[tasks/graphql-qs-projection-model-21]]

  * Restore explicit CQRS fixture boundaries [[tasks/graphql-qs-projection-model-22]]

  * chore: better version in e2e-ui of test

  * feat: add event-independent mutation IR and dual-path projectors

  Introduce the public mutation authoring path from the domain-event-projections
  spec: versioned MutationProgram IR, mutation! macro, ReadModel capabilities
  metadata, portable handler catalog, server/cache/preview interpreters, and
  shared Rust/JS golden vectors. Wire SAVE_TODO/DELETE_TODO, SAVE_CHAT_MESSAGE,
  and SAVE_BLOB_GAME mutations into e2e fixtures while retaining projection!
  as the dual-path runtime mount until full cutover.

  Implements [[tasks/graphql-qs-mutation-projectors-1]]
  Implements [[tasks/graphql-qs-mutation-projectors-2]]
  Implements [[tasks/graphql-qs-mutation-projectors-3]]
  Implements [[tasks/graphql-qs-mutation-projectors-4]]
  Implements [[tasks/graphql-qs-mutation-projectors-5]]
  Implements [[tasks/graphql-qs-mutation-projectors-6]]
  Implements [[tasks/graphql-qs-mutation-projectors-7]]
  Implements [[tasks/graphql-qs-mutation-projectors-8]]
  Implements [[tasks/graphql-qs-mutation-projectors-9]]
  Implements [[tasks/graphql-qs-mutation-projectors-10]]

  * feat: placement-selected projected() and wire mutation programs on service path

  - Add placement-selected direct registry so commands call commit()?.projected()
    without naming a projection selector (Blob handlers updated).
  - Register BlobGames executor at e2e service construction.
  - Drive SAVE_TODO/DELETE_TODO/SAVE_CHAT_MESSAGE/SAVE_BLOB_GAME mutation
    programs from service registration and event handlers (not test-only stubs).
  - Hide event-owning projection! authoring from public docs; document
    .project(...) as non-preferred for application commands.
  - Unit proof: placement_selected_projected_uses_registered_executor_without_project_selector

  Implements [[tasks/graphql-qs-mutation-projectors-1]]
  Implements [[tasks/graphql-qs-mutation-projectors-10]]
  Implements [[tasks/graphql-qs-mutation-projectors-11]]

  * feat: drive e2e mounts from mutation IR rewrite (real resolve path)

  Replace event-owning projection! for TODO/CHAT/BLOB e2e mounts with
  mutation-backed ProjectionDescriptor factories:

  - program/resolve built via program_from_mutation_arms (SAVE_*/DELETE_*)
  - lower via shared lower_single_model ORM path
  - service construction asserts descriptor program bytes == mutation rewrite
  - handlers apply those descriptors without id()-only theater
  - Blob eligibility fixtures still use projection! for compile_fail only

  Also expose ResolvedProjectionPlan::resolve publicly for dual-path factories.

  Implements [[tasks/graphql-qs-mutation-projectors-1]]

  * feat: WP10 cutover — remove projection! and command-side .project

  Hard-delete competing projector authoring surfaces:

  - Remove event-owning `projection!` proc-macro export and source module
  - Delete distributed_macros projection unit/compile_fail suites
  - Remove CausalCommitBuilder/CausalRepository `.project(...)` selectors
  - Drop crate-root `ReadModelWritePlanBuilder` re-export (adapter remains at
    `distributed::read_model::ReadModelWritePlanBuilder` and TableWritePlan)
  - Migrate residual framework/test descriptors to mutation-backed factories
  - Add tests/legacy_authoring_absence.rs structural gate

  Task-11 matrix (fmt, clippy -D, workspace tests, npm quality, e2e-ui
  check-client/test) green at this cutover; browser/Postgres live env gates
  remain environment-dependent.

  Implements [[tasks/graphql-qs-mutation-projectors-1]]
  Implements [[tasks/graphql-qs-mutation-projectors-11]]

  * feat: AC4 residual cutover — remove effects macros and public projector ORM export

  Hard-delete separately authored command_effects!/command_confirmations! and
  TypedCommand::{effects,confirmations}; demote ProjectionReadModelWorkspace to
  crate-private; docs teach mutation! + .emits/.preview; expand structural gate;
  restore command_input_defaults trybuild suite under a dedicated name.

  Full task-11 matrix green (fmt, clippy -D, cargo test workspace all-features,
  npm quality, e2e-ui offline, legacy_authoring_absence).

  Implements [[tasks/graphql-qs-mutation-projectors-1]]
  Implements [[tasks/graphql-qs-mutation-projectors-11]]

  * docs: teach placement-selected Blob projected path only

  Replace residual repo.project(BLOB_GAMES).commit(...).projected() examples
  in e2e-ui README and walkthrough with the live
  repo.commit(game)?.projected() path. Keep compile_fail/migration notes only
  for the removed selector.

  Implements [[tasks/graphql-qs-mutation-projectors-1]]
  Implements [[tasks/graphql-qs-mutation-projectors-11]]

  * refactor: release-ready cleanup of leftover projector authoring surfaces

  Delete the crate-private graph_workspace ORM path, strip dead command_effects
  authoring types/re-exports and constructors, rename macros module to
  command_input_defaults, and tighten structural gates. Keep protocol lifecycle
  primitives on CausalProjectorContext and low-level TableWritePlan adapters.

  Full matrix green (fmt, clippy -D, cargo test workspace, npm quality, e2e-ui).

  Implements [[tasks/graphql-qs-mutation-projectors-1]]
  Implements [[tasks/graphql-qs-mutation-projectors-11]]

  * feat: mutation_projector! sugar and arm helpers for nice app mounts

  Framework owns resolve/lower/inventory factory glue via mutation_projector!
  and arm_state_upsert_for_model / arms_state_upsert_for_model /
  arm_delete_pk_from_envelope / build_mutation_projector_program.

  e2e todos/chat/blob projections shrink to mutations + event arms + mount.

  Implements [[tasks/graphql-qs-mutation-projectors-1]]

  * docs(e2e-ui): show mutation_projector! author surface in README

  Implements [[tasks/graphql-qs-mutation-projectors-1]]

  * feat: portable_handlers! matches the spec author model

  App authors declare mutations and which events apply them. Dual-path
  projection IR compile stays internal (compile_portable_handlers).

  Rename public bind_* helpers; hide arm_* vocabulary from the crate root
  docs. Rewrite e2e todos/chat/blob to portable_handlers! / bind language.

  Implements [[tasks/graphql-qs-mutation-projectors-1]]

  * fix: import DomainEventContract in todo projection tests

  * refactor: remove dual-path compatibility surface; ship portable_handlers only

  Delete arm_* / build_mutation_projector_program shims and MutationProjectionArm.
  Public API is mutations + bind_* + portable_handlers! / compile_portable_handlers.
  Rename opaque compile unit to PortableHandler. Strip e2e program wrappers and
  service dual-path equality asserts.

  Implements [[tasks/graphql-qs-mutation-projectors-1]]

  * refactor(e2e): rename TODO_READS to TODOS for model-consistent naming

  Matches BLOB_GAMES and CHAT_MESSAGES — the mount is the Todos model
  handlers, not a "reads" collection.

  Implements [[tasks/graphql-qs-mutation-projectors-1]]

  * refactor(e2e-ui): consistent command previews and portable_handlers demos

  Inline todo.complete preview with the other commands via state_preview! on
  the typed command. Remove complete_preview from projections. Align event
  handler comments and UI walkthrough with mutation + portable_handlers.

  Implements [[tasks/graphql-qs-mutation-projectors-1]]

  * feat: event-first mutations, applies mapping, handler-owned projected commits

  Ship the revised domain-event projection authoring model for the mutation
  projectors cutover:

  - GraphQL-looking mutation documents via mutation_file! / mutation! (syntax-only IR)
  - Event-first portable_handlers! (on <events> apply <mutation>)
  - Command .applies for known mutation-input mapping (preview kept as alias)
  - Blob projected path: Mutation::from_state + readmodel(row).commit()?.projected()
  - e2e command modules renamed with aggregate prefix (todo_reopen, …)

  Implements [[tasks/graphql-qs-mutation-projectors-1]]

  * fix: keep chat live+optimism with author joins and portable todo lists

  Live queries that join unowned tables (e.g. chat_messages.author → auth_users)
  no longer force live.supported=false; index plans skip zero-projector join
  tables so the WebSocket can stay active. Nullable missing relationship edges
  stay complete so optimistic rows remain materializable, and the chat UI no
  longer blanks the list while incomplete.

  Also: application surface uses user grants only so Todos/BlobGames keep a
  portable owner row policy for optimistic list inserts; chat unit partition for
  lobby resume; iMessage-style Sent/Delivered footers and AuthUsers display
  names on chat.

  Implements follow-ups for [[tasks/graphql-qs-mutation-projectors-1]]

  * fix: keep warm same-scope cache and polish lobby chat history

  Same-scope soft-nav rehydrate merges route SSR seeds instead of wiping
  confirmed records/indexes omitted from the page. Lobby chat uses a fixed
  scroll panel, page size 25, infinite history, and Chromium-correct
  column-reverse scroll geometry (negative scrollTop).

  * fix: restore quality and e2e-ui offline CI on mutation-projectors branch

  Gate causal_direct_v1_program behind the graphql feature so default/sqlite
  lib tests compile, and send the exact e2e-ui application surface roles list
  expected by select_protocol_surface.

  * fix(e2e-ui): grant admin principals the user role too

  Local Zitadel bootstrap and offline suite tokens assert both admin and
  user for admin humans/machines so they can use normal app surfaces and
  elevated paths without changing model permissions.

  * fix(e2e-ui): write create_human status to stderr for clean UIDs

  Command-substitution of create_human was capturing "reusing human …"
  lines into E2E_HUMAN_*_UID. Keep only the user id on stdout.

  * fix: multi-role application surfaces + chat history load race

  Make multi-role principals first-class for named application surfaces:
  eligible roles control protocol open (any asserted role may open), while
  schema privilege roles shape the portable client contract. e2e-ui keeps
  eligible {admin,user} with schema {user} so owner-portable optimism is
  preserved without collapsing model permissions.

  Also stop treating incomplete/empty history pages as end-of-history while
  the live chat window is still filling, and harden the Playwright history
  scroll test under column-reverse.

  Implements multi-role surface selection for [[tasks/graphql-qs-mutation-projectors-1]]

  * fix(e2e-ui): update surface structural gate and chat optimism window

  Point the fixture source gate at surface_for_application_contract and
  eligible {admin,user} schema {user}. Widen the chat revalidation
  optimism paint window so CI headroom stays under the delayed mutation
  response without weakening the stale-while-revalidate assertion.

  * feat: set-only identity and surface-privilege GraphQL execution

  Breaking major-release cutover: Session carries x-roles only (no
  priority-picked primary x-role). GraphQL execute/stream binds to the opened
  application surface privilege pack (or a membership-checked role surface).
  Multi-role principals without a named surface fail closed. Anonymous
  eligible surfaces open with empty identity.

  e2e-ui: public e2e-ui-public surface, dual-role admin suite uses admin
  surface for elevated ops, causal grants check any asserted role.

  Implements [[tasks/graphql-qs-surface-authz-1]]

  * fix: prove e2e-ui-public anonymous open and finish AuthZ docs

  Add service test that opens e2e-ui-public with an empty Session and queries
  chat_messages, plus unauthenticated /public route documenting the bare
  protocol path. Specs no longer teach Session::role()/x-role as execution.

  Implements [[tasks/graphql-qs-surface-authz-1]]

  * fix: CI failures for set-only identity (x-roles)

  - graphql_query_protocol: use role binding for x-roles header; WS
    connection_init sends x-roles (not x-role)
  - graphql_oidc_common E1: assert Session::roles() instead of role(),
    which is None under set-only claim mapping

  Implements [[tasks/graphql-qs-surface-authz-1]]

  * fix: remove x-role identity bridges — set-only cutover

  Major-release identity is x-roles only; drop migration paths that still
  accepted or re-injected a singleton primary role.

  - ROLE_KEY is x-roles; Session::roles/has_role are the identity API
  - remove causal ensure_causal_grant legacy x-role fallback
  - schema/metrics privilege fallback uses roles set only
  - OIDC e2e layer re-injects x-roles (not x-role + default user)
  - JS DevHeaders + tests send x-roles
  - keep stripping client x-role as defense-in-depth only

  Implements [[tasks/graphql-qs-surface-authz-1]]

  * fix: align subscription unknown-role test with set-only authority

  Unconfigured singleton roles fail closed at execution-authority resolve
  (same generic surface message), not via legacy primary-role schema lookup.

  Implements [[tasks/graphql-qs-surface-authz-1]]

  * fix(e2e-ui): treat scrape outbox duplicates as skips

  Re-scrape of unchanged profiles hits the content-addressed outbox unique
  key by design. Classify DuplicateOutboxMessageInBatch / unique-violation
  wording as skipped, not errors, so start scrape reports stay clean.

  Implements [[tasks/graphql-qs-surface-authz-1]]

  * feat(e2e-ui): framework home + How it’s built slide-out

  Reframe the home page around Distributed principles and link demos as
  destinations. Each demo route gets a right-hand drawer with tabbed
  walkthroughs (domain → command → projection → client) and a principle
  callout per tab — a Distributed lens on the “code tabs next to the app”
  teaching pattern.

  Implements teaching UX for the e2e-ui template.

  * fix(e2e-ui): hero highlights full-stack CQRS, TS, OIDC, SvelteKit

  Lead the home hero with the end-to-end story: event-sourced CQRS, TypeScript
  clients, first-class OIDC, and SvelteKit SSR/live — not CQRS alone.

  * fix(e2e-ui): wider How-it’s-built panel, no closed shadow, code colors

  Drawer only shadows when open, width ~46rem, and lightweight syntax tint
  for walkthrough samples (keywords, strings, types, attrs, comments).

  * feat(e2e-ui): browser-first How-it’s-built tab order

  Reorder every demo walkthrough: (1) query/live (2) commands + client
  cache optimism vs Projected atomic (3) handlers/repo (4) domain macros
  (5) domain events + projections. Match the teaching path from UI inward.

  * feat(e2e-ui): open lobby chat for anonymous GraphQL reads

  Allow /chat without a session so the anonymous privilege pack is visible
  in the UI: e2e-ui-public client for guests, sign-in CTA instead of the
  composer, require_auth=false for empty OIDC identity, and AuthUsers read
  for public author joins.

  * feat(e2e-ui): show RBAC on How-it’s-built query and command tabs

  Each demo walkthrough now includes short ModelPermissions / command.roles
  samples on tabs 1 and 2 so read grants and mutation roles sit next to the
  browser query and command story.

  * fix(e2e-ui): replace comment-only How-it’s-built samples with real code

  Walkthrough panels now paste actual handler, domain, projection, RBAC, and
  generated-client snippets from the fixture instead of comment stubs.

  * feat(e2e-ui): rewrite home as Distributed product landing

  Frame the site as the framework homepage — full-stack CQRS pitch, pillars,
  domain→service→client flow, compact playground cards, and local run.
  Demos stay destinations; ops/hosting only noted as roadmap.

  * style(e2e-ui): product-home layout styles for dist-* sections

  * fix(e2e-ui): reframe home as GraphQL realtime → owned write model

  Drop Meteor comparisons. Pitch Distributed as the next step after live
  GraphQL/query engines: keep the realtime client feel, add event-sourced
  commands, projections, OIDC surfaces, and generated TS clients.

  * fix(e2e-ui): home is CQRS/domain-first; GraphQL is transport

  Position Distributed around event-sourced commands, projections, and an
  honest client replica. GraphQL and SvelteKit are how the playground speaks,
  not the product definition.

  * fix(e2e-ui): brand Distributed; hero product definition

  Rename header/footer e2e-ui → distributed. Hero states Distributed as a
  cloud-native Rust and TypeScript framework for simple realtime, performant,
  scalable apps on distributed-systems foundations.

  * fix(e2e-ui): hero — start simple, scale to microservices

  * feat(e2e-ui): product home Features from CQRS/ES canon narrative

  Rebuild home Features as the owner product story (two models, event-sourced
  aggregates, SQL+RBAC, inferred query edge, projections, browser replica,
  SvelteKit @load/@live, OIDC) with playground code samples and syntax
  highlighting matching How-it's-built panels.

  * refactor: rename portable_handlers! to projection!

  Public event→mutation authoring is now projection! (and compile_projection /
  ProjectionHandler). Matches CQRS product language and ProjectionDescriptor.

  Call sites use distributed::projection! so the macro does not clash with the
  projection module path. Legacy absence tests now assert the declarative macro
  is public while the old event-owning proc-macro stays gone.

  * feat: event-first projection! on { events, mutation, input }

  Replace apply/as and on_deleted with multi-arm on blocks that bind event
  body or aggregate_id into mutation inputs. Align todos/chat/blob, home
  Features (mutation IR + lifetime highlighting), and demos with the new
  surface. Drop mutation_projector and on_state authoring.

  * refactor(e2e-ui): name replica handles query, data by resource

  Prefer query = Op.use() over list, and todos/games/pageMessages over
  generic rows, in app pages, home samples, and How-it's-built demos.

  * feat(e2e-ui): SOTA home story — claim arc, general bar, delivery

  Expand the product home with a Brunson-style arc: claim band, backend/Rust/frontend
  SOTA as general industry bar (not product hooks), handoff to Distributed, then
  backstory and how it delivers. Teach unidirectional + event-driven as one path;
  tighten CAP wording; claim/band styles on home.css.

  * feat(e2e-ui): walkthrough read models + unidirectional flow diagram

  Expand How-it's-built overlays with ReadModel structs, aggregate shapes,
  domain event samples, and projection GraphQL mutations. Replace the home
  system-flow monospace list with a full-width circular dotted diagram.

  * fix(e2e-ui): gate todos/blob on login; drop redundant /public page

  Show Todos and Blob in nav for guests; requireAuth on page loads so
  client-side navigation redirects to /login?callbackUrl=… and returns after
  sign-in. Remove the standalone /public demo (lobby chat already covers
  anonymous). Honor callbackUrl on login/signup when already signed in;
  hero copy mentions realtime applications.

  * feat: Eventual/Atomic command semantics + restore chat optimism

  Ship one mutation IR with two proofs: Eventual (async projector +
  delta/expects) and Atomic (handler row + records). Rename wire/protocol
  states and APIs from causal/projected to eventual/atomic with no aliases.

  Direct placements export .applies previews for client optimism while
  still sealing from the atomic response. Command ledger migrations use
  atomic state (0004 + CHECK updates).

  Fix chat list optimism regressions: belongs_to joins are GraphQL/client
  nullable so missing author edges materialize, and full first-page offset
  indexes accept local optimistic inserts (re-sort + truncate).

  Regenerate e2e-ui clients, demos/docs, and JS tests for the new contract.

  * test(e2e-ui): gate demo optimism offline and in the browser

  Add a shared hold-mutation helper and optimism.user.spec that requires
  chat post (including full first page), todos create/complete, and blob
  move continuity to paint before a delayed GraphQL response.

  Offline optimism-artifacts.test.mjs locks preview IR, atomic
  directProjection, nullable ChatMessages.author, and local first-page
  insert policy so gen/compiler regressions fail without a browser.

  Todos create under the delayed-route order test now asserts list paint
  before the wire returns.

  * fix(e2e-ui): blob move board optimism via .applies input fields

  Wire blob.move like todos/chat: command input carries the optimistic
  board outcome (map_json, score, status, …) and state_preview maps those
  fields into the client optimistic layer. A pure TypeScript twin of
  blob_domain::simulate_move fills the input; the handler still recomputes
  authority from game_id + direction only.

  Regenerate clients (full upsert preview), unit-test simulate_move parity,
  and require paint-before-wire on the player cell in optimism.user.spec.

  * docs(e2e-ui): clarify blob move uses shared .applies optimism path

  * refactor!: drop TypedCommand.preview alias — use applies only

  No back-compat renames. Call sites and docs use .applies. Wire
  vocabulary test expects eventual/atomic only (rejects causal/projected).

  * docs: application composition — logical mounts, process roles, runtime

  Capture the accepted DX direction: same packages re-cut as monolith or
  microservices; Eventual projectors may split; Atomic seals stay collocated.
  Runtime pairs persistence, locks, and bus; process role selects outbox,
  consumer, and GraphQL. Implementation order and e2e-ui collapse targets
  included. Linked from usage skill and e2e-ui README.

  * fix(ci): finish eventual/atomic rename in CLI fixtures and suite asserts

  Update generated-commands fixture, dctl client_compiler/cli_manifest
  expectations, GraphQL protocol tests, and e2e-ui behavioral suite to the
  eventual/atomic wire vocabulary (no causal/projected aliases).

  * fix: repair PR 170 migration and contract drift

  Preserve applied migration history, register migration 4, complete the Eventual/Atomic rename, refresh generated contracts, and repair the affected e2e behavior.

  Resolves [[pr-170-eventual-atomic-rename-and-migration-failur]]

  * test(e2e-ui): consolidate duplicate coverage

  Fold optimistic assertions into product journeys, remove redundant offline and browser scenarios, and retain the unique stale-response race.\n\nImplements [[tasks/e2e-ui-test-maintenance-3]].\nImplements [[tasks/e2e-ui-test-maintenance-4]].

  * test(js): centralize command protocol fixtures

  Route valid command metadata through one canonical fixture while keeping malformed protocol cases explicit.\n\nImplements [[tasks/e2e-ui-test-maintenance-5]].

  * ci: remove duplicate feedback work

  Cancel superseded PR runs, deduplicate e2e-ui setup, narrow compatibility gates, and compile Postgres tests with only required features.\n\nImplements [[tasks/e2e-ui-test-maintenance-6]].\nImplements [[tasks/e2e-ui-test-maintenance-7]].

  * ci: overlap independent e2e-ui offline gates

  Install shared UI prerequisites once, then run the Rust and sequential UI pipelines concurrently.\n\nImplements [[tasks/e2e-ui-test-maintenance-6]].

* feat: land application lifecycle and workbench stack (by @patrickleet)

  BREAKING CHANGE: Squash the complete stacked change set through PR #178 after GitHub's
  stack merge operation landed only the bottom layer.

  Includes PRs #172, #173, #174, #177, and #178.

  BREAKING CHANGE: the standalone CLI binary is named distributed instead
  of dctl, and application surface roles use the eligible/schema split.


See full diff: [v3.3.4...v4.0.0](https://github.com/hops-ops/distributed/compare/v3.3.4...v4.0.0)