Apr 26, 12-1 PM (6)
Apr 26, 1-2 PM (4)
Apr 26, 2-3 PM (14)
Apr 26, 3-4 PM (14)
Apr 26, 4-5 PM (0)
Apr 26, 5-6 PM (13)
Apr 26, 6-7 PM (13)
Apr 26, 7-8 PM (7)
Apr 26, 8-9 PM (7)
Apr 26, 9-10 PM (5)
Apr 26, 10-11 PM (27)
Apr 26, 11-12 AM (21)
Apr 27, 12-1 AM (7)
Apr 27, 1-2 AM (7)
Apr 27, 2-3 AM (9)
Apr 27, 3-4 AM (9)
Apr 27, 4-5 AM (5)
Apr 27, 5-6 AM (13)
Apr 27, 6-7 AM (7)
Apr 27, 7-8 AM (82)
Apr 27, 8-9 AM (47)
Apr 27, 9-10 AM (33)
Apr 27, 10-11 AM (62)
Apr 27, 11-12 PM (80)
Apr 27, 12-1 PM (66)
Apr 27, 1-2 PM (44)
Apr 27, 2-3 PM (52)
Apr 27, 3-4 PM (42)
Apr 27, 4-5 PM (36)
Apr 27, 5-6 PM (26)
Apr 27, 6-7 PM (13)
Apr 27, 7-8 PM (26)
Apr 27, 8-9 PM (13)
Apr 27, 9-10 PM (16)
Apr 27, 10-11 PM (42)
Apr 27, 11-12 AM (28)
Apr 28, 12-1 AM (17)
Apr 28, 1-2 AM (8)
Apr 28, 2-3 AM (4)
Apr 28, 3-4 AM (5)
Apr 28, 4-5 AM (5)
Apr 28, 5-6 AM (8)
Apr 28, 6-7 AM (8)
Apr 28, 7-8 AM (37)
Apr 28, 8-9 AM (54)
Apr 28, 9-10 AM (59)
Apr 28, 10-11 AM (53)
Apr 28, 11-12 PM (56)
Apr 28, 12-1 PM (49)
Apr 28, 1-2 PM (54)
Apr 28, 2-3 PM (69)
Apr 28, 3-4 PM (31)
Apr 28, 4-5 PM (14)
Apr 28, 5-6 PM (47)
Apr 28, 6-7 PM (9)
Apr 28, 7-8 PM (9)
Apr 28, 8-9 PM (14)
Apr 28, 9-10 PM (20)
Apr 28, 10-11 PM (34)
Apr 28, 11-12 AM (29)
Apr 29, 12-1 AM (13)
Apr 29, 1-2 AM (1)
Apr 29, 2-3 AM (1)
Apr 29, 3-4 AM (6)
Apr 29, 4-5 AM (1)
Apr 29, 5-6 AM (4)
Apr 29, 6-7 AM (12)
Apr 29, 7-8 AM (45)
Apr 29, 8-9 AM (75)
Apr 29, 9-10 AM (49)
Apr 29, 10-11 AM (28)
Apr 29, 11-12 PM (51)
Apr 29, 12-1 PM (39)
Apr 29, 1-2 PM (21)
Apr 29, 2-3 PM (66)
Apr 29, 3-4 PM (25)
Apr 29, 4-5 PM (36)
Apr 29, 5-6 PM (16)
Apr 29, 6-7 PM (10)
Apr 29, 7-8 PM (14)
Apr 29, 8-9 PM (13)
Apr 29, 9-10 PM (17)
Apr 29, 10-11 PM (25)
Apr 29, 11-12 AM (29)
Apr 30, 12-1 AM (6)
Apr 30, 1-2 AM (8)
Apr 30, 2-3 AM (1)
Apr 30, 3-4 AM (6)
Apr 30, 4-5 AM (2)
Apr 30, 5-6 AM (8)
Apr 30, 6-7 AM (15)
Apr 30, 7-8 AM (17)
Apr 30, 8-9 AM (100)
Apr 30, 9-10 AM (19)
Apr 30, 10-11 AM (50)
Apr 30, 11-12 PM (120)
Apr 30, 12-1 PM (69)
Apr 30, 1-2 PM (45)
Apr 30, 2-3 PM (117)
Apr 30, 3-4 PM (29)
Apr 30, 4-5 PM (34)
Apr 30, 5-6 PM (9)
Apr 30, 6-7 PM (20)
Apr 30, 7-8 PM (23)
Apr 30, 8-9 PM (28)
Apr 30, 9-10 PM (13)
Apr 30, 10-11 PM (25)
Apr 30, 11-12 AM (15)
May 01, 12-1 AM (18)
May 01, 1-2 AM (15)
May 01, 2-3 AM (6)
May 01, 3-4 AM (7)
May 01, 4-5 AM (3)
May 01, 5-6 AM (5)
May 01, 6-7 AM (8)
May 01, 7-8 AM (13)
May 01, 8-9 AM (24)
May 01, 9-10 AM (16)
May 01, 10-11 AM (16)
May 01, 11-12 PM (17)
May 01, 12-1 PM (37)
May 01, 1-2 PM (29)
May 01, 2-3 PM (19)
May 01, 3-4 PM (16)
May 01, 4-5 PM (25)
May 01, 5-6 PM (11)
May 01, 6-7 PM (20)
May 01, 7-8 PM (22)
May 01, 8-9 PM (65)
May 01, 9-10 PM (15)
May 01, 10-11 PM (40)
May 01, 11-12 AM (61)
May 02, 12-1 AM (6)
May 02, 1-2 AM (11)
May 02, 2-3 AM (5)
May 02, 3-4 AM (8)
May 02, 4-5 AM (6)
May 02, 5-6 AM (2)
May 02, 6-7 AM (2)
May 02, 7-8 AM (14)
May 02, 8-9 AM (6)
May 02, 9-10 AM (7)
May 02, 10-11 AM (6)
May 02, 11-12 PM (5)
May 02, 12-1 PM (7)
May 02, 1-2 PM (3)
May 02, 2-3 PM (14)
May 02, 3-4 PM (9)
May 02, 4-5 PM (26)
May 02, 5-6 PM (8)
May 02, 6-7 PM (29)
May 02, 7-8 PM (11)
May 02, 8-9 PM (14)
May 02, 9-10 PM (0)
May 02, 10-11 PM (20)
May 02, 11-12 AM (17)
May 03, 12-1 AM (8)
May 03, 1-2 AM (1)
May 03, 2-3 AM (3)
May 03, 3-4 AM (7)
May 03, 4-5 AM (1)
May 03, 5-6 AM (4)
May 03, 6-7 AM (32)
May 03, 7-8 AM (5)
May 03, 8-9 AM (1)
May 03, 9-10 AM (3)
May 03, 10-11 AM (9)
May 03, 11-12 PM (7)
May 03, 12-1 PM (0)
3,789 commits this week Apr 26, 2026 - May 03, 2026
flake.lock: Update
Flake lock file updates:

• Updated input 'CHaP':
    'github:input-output-hk/cardano-haskell-packages/3932849518c1c50018ee3dfba06b391f93a33f76?narHash=sha256-c9oScWEa0q5X0VF7G6mSsOrh%2BglHfwnUGdbHnPTVPVQ%3D' (2026-04-24)
  → 'github:input-output-hk/cardano-haskell-packages/e8a483522ee73c8c9493ea6055553e5c2532e66b?narHash=sha256-ZzXz2vOhqethlqPgBExPXEnKWvaTbidsIxh5MGv%2BpwE%3D' (2026-05-02)
• Updated input 'emanote':
    'github:srid/emanote/51993925f8120c6c1fa40b5c6061ac05c459054f?narHash=sha256-k4WxRhYi0n3hheV/DtWV53qTMdA2NhWNEdQ/KjcBXL4%3D' (2026-04-26)
  → 'github:srid/emanote/d98c9abbef3416b98ffa759c60e829ae00d848ee?narHash=sha256-pvKCvjpJJLFbniLecA3tTImzaPAjX6Bhd2XZGtSXMQ8%3D' (2026-05-02)
• Updated input 'emanote/commonmark-wikilink':
    'github:srid/commonmark-wikilink/5ab01515939047b58943cc1234e7ee0cb82d1c22?narHash=sha256-MWOb0Ojc4EQd9fOnQEveRDdbH5Cr6kjUt04uWzBPLGQ%3D' (2025-08-19)
  → 'github:srid/commonmark-wikilink/e47ba496ceaf68ec119e56eb22670ae017b35a42?narHash=sha256-dxjsU2SGG2txShwdXCl18ND18Y5Qbqt3J/FlrKhrkp4%3D' (2026-04-27)
• Updated input 'emanote/ema':
    'github:srid/ema/e92e52dbefea57de08ef64b4db0ea795170d6b81?narHash=sha256-rMb1a7VzMS2nmfEWAtJEKgZYg9Mc5JdSNK4y%2Bz1S/ec%3D' (2026-04-23)
  → 'github:srid/ema/87da4f3c678b78bc3ee96225910a3932b3e8b6f9?narHash=sha256-bzypHo%2BZ/fLJRK1hTEuxCzK/ybxJixDrfML8M6cfDtk%3D' (2026-04-26)
• Updated input 'emanote/heist-extra':
    'github:srid/heist-extra/bddc871dd0fe68c6376c6514498320b23ff9320e?narHash=sha256-4ArDqHLR82XJJE9AA3sX1w9eyChY1HRVwAzTYwRdEx8%3D' (2026-04-25)
  → 'github:srid/heist-extra/85c433921629d0de9374bc5deaa866ec830748c2?narHash=sha256-YefkrB6jip7AV9ssRLngjSbp9xHE/xcSgfQMzDjpga0%3D' (2026-04-30)
• Updated input 'flake-parts':
    'github:hercules-ci/flake-parts/3107b77cd68437b9a76194f0f7f9c55f2329ca5b?narHash=sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA%3D' (2026-04-01)
  → 'github:hercules-ci/flake-parts/5250617bffd85403b14dbf43c3870e7f255d2c16?narHash=sha256-EPIFsulyon7Z1vLQq5Fk64GR8L7cQsT%2BIPhcsukVbgk%3D' (2026-05-01)
• Updated input 'flake-parts/nixpkgs-lib':
    'github:nix-community/nixpkgs.lib/333c4e0545a6da976206c74db8773a1645b5870a?narHash=sha256-%2BU7gF3qxzwD5TZuANzZPeJTZRHS29OFQgkQ2kiTJBIQ%3D' (2026-03-29)
  → 'github:nix-community/nixpkgs.lib/f5901329dade4a6ea039af1433fb087bd9c1fe14?narHash=sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ%3D' (2026-04-26)
• Updated input 'haskell-nix':
    'github:input-output-hk/haskell.nix/0d65e5a5caa0d79b2aa02c49602eff0c3a079f23?narHash=sha256-T5wVbrpXJGVPefF6%2BttKcNepqk0fAST6OKxSstJyzSc%3D' (2026-04-26)
  → 'github:input-output-hk/haskell.nix/7542beb47f1314e7045c6049d94779d11aca04bb?narHash=sha256-jFMGdUFcXRNxLDNbvtEN8jRYhj0TI9WvDmralnMdSYg%3D' (2026-05-03)
• Updated input 'haskell-nix/hackage':
    'github:input-output-hk/hackage.nix/164126a0bf3cf52632a0d90cbeeb930720a02b44?narHash=sha256-USlr9HTzI/HjQC3COQ6PUZ0jtlA3grrffVOM3fr%2BSeQ%3D' (2026-04-26)
  → 'github:input-output-hk/hackage.nix/9f772a193f43b7a68bdc2bd77386a9a2d9084543?narHash=sha256-OboBWbZ774JqMeirIsHRufHBogQwbKEet2dFhDXRBz0%3D' (2026-05-03)
• Updated input 'haskell-nix/hackage-for-stackage':
    'github:input-output-hk/hackage.nix/b0ff6df0b5220214704f2590ead8a58e97af84a0?narHash=sha256-VpWgq0FevPggXeiuHickC/q62qxm6czZIDZ2aeIkNuA%3D' (2026-04-26)
  → 'github:input-output-hk/hackage.nix/3953b112518c63f045d5c0b3739545d21a18fddb?narHash=sha256-ByNMSXMZzK%2BaLizUM89rFMnYxN3hZSrVPLo6n/Agt1M%3D' (2026-05-03)
• Updated input 'haskell-nix/stackage':
    'github:input-output-hk/stackage.nix/3b6af61aee3e6d6479ffd8f8ef012df6e96f0983?narHash=sha256-3qY4r/l1vEpz5cybYkA0A42t6Ou4mhtx6x/awuRTyRU%3D' (2026-04-26)
  → 'github:input-output-hk/stackage.nix/541f13cec99cd26d4a1dc74b94a617a95253b2b0?narHash=sha256-Jdgt8rIJ/RLn9kdsD7fnSVwcGsecUYfd6M1pVSrgI/M%3D' (2026-05-03)
• Updated input 'iohk-nix':
    'github:input-output-hk/iohk-nix/fdfc53bc51c684fe086117de651f36572b26655a?narHash=sha256-hkfKwhbhCiDVBwDeeKKXQiBg9VAI3KMM1GZ3yhO6cT8%3D' (2026-04-17)
  → 'github:input-output-hk/iohk-nix/912e9dcdc68ef241ab5f1e542a7a294b46c9b765?narHash=sha256-ahNiZOg9KX8V5lUMUGwtSCYmmtEcC7UvP6F/cIjhwPc%3D' (2026-05-03)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/a5951fb3cf2eeeac787ece2e04fd23823c75ab73?narHash=sha256-RpIS/Whk/kw4A78sfi23kKzXumQLxxHQRf/11K5J80g%3D' (2026-04-26)
  → 'github:NixOS/nixpkgs/e2aae6df2b28735b1cc80b89d3c675fa4788e988?narHash=sha256-PJHvrK76K%2Bqan%2BzxP8RNYgHxzeTzg0pApJGnxsJAowQ%3D' (2026-05-03)
testnet(amaru): bump bootstrap-producer image to 6910d79c
Picks up lambdasistemi/amaru-bootstrap PR #38 — the era-history
wiring + chain-DB-resolved nonce tail fix that closes
lambdasistemi/amaru-bootstrap#37. Required preconditions:

- amaru #2 (saturating epoch subtraction)
- amaru #3 (ln(1-f) guard for activeSlotsCoeff = 1)
- amaru #4 (--era-history-file on import-nonces +
  convert-ledger-state, GO snapshot retention)

All five fixes are in the new producer image; cardano_amaru and
cardano_amaru_epoch3600 testnets repinned.

Signed-off-by: Paolo Veronelli <[email protected]>
asteria-game: wrap PlayerMain in top-level try
The composer's "Always: zero exit" property still flagged
stub/parallel_driver_asteria_player.sh (failing examples at vtimes
~272s, ~291s, ~3017s on the 2026-05-03 run). Each failing example
exits 1 in ~3s with empty stderr — consistent with an exception
escaping main from withN2C / readWalletKey before the inner
try wrapper covers it.

Mirror the c99e992 BootstrapMain pattern: wrap the whole pass in
a top-level try; on SomeException emit
asteria_player_pass_top_errored_<id> as sdkUnreachable and exit 0.
The inner observeAndAct try is preserved (same payload signal name
asteria_player_pass_errored_<id>) so the two failure surfaces stay
distinguishable in the report.
chore(testnet): bump adversary + sidecar pins to b8a0521 for #123 perturbation metrics
Pulls in:
- adversary:b8a0521 — emits SDK assertions per attack (Reachable
  adversary_chain_sync_started + Sometimes adversary_chain_sync_completed)
- sidecar:b8a0521 — finally_tips_agree.sh emits finally_perturbation_metrics
  with blocks_produced_total and max_slot_lag

Both pins resolve to the post-cabal-fix sidecar feat commit so
publish-images can rebuild against the corrected build-depends list
(directory + filepath were missing in the first push).
feat(sidecar): emit blocks_total + max_slot_lag perturbation metrics from finally_tips_agree
Adds an emit_perturbation_metrics() helper to
components/sidecar/composer/convergence/finally_tips_agree.sh that, on
every run, computes:

  - blocks_produced_total : sum of producers' block heights at end-of-test
  - max_slot_lag          : max(slot) − min(slot) across producers

and emits them as a Sometimes(true) "finally_perturbation_metrics" event
with both numbers in details. The Antithesis report's per-bucket
distribution view then shows the curve across runs without needing a
separate observer.

Slot lag is 0 on a perfectly synchronous cluster, mildly positive
under fault injection, large under attack pressure — gives us a
quantitative comparison for master vs adversary side-by-sides.

Layer 3 of three for issue #123.
feat(adversary): emit Antithesis SDK assertions to prove the attacker fired
Adds Adversary.SDK with Reachable / Sometimes assertion emitters that
write to \$ANTITHESIS_OUTPUT_DIR/sdk.jsonl (default /tmp/sdk.jsonl).
Wires two assertions into app/Main.hs:

  - reachable("adversary_chain_sync_started", {target_host, point, limit})
    fires once per invocation before connectToNode. Antithesis report
    will show, segmented by target_host, "the adversary fired against
    pN at least once". A host that never gets attacked is visible as
    a missing Reachable hit.

  - sometimes(true|false, "adversary_chain_sync_completed",
              {target_host, tip|reason})
    fires once per invocation on completion. true on clean exit,
    false on connect/protocol failure. Sometimes-true vs Sometimes-
    false buckets quantify how often the adversary actually completed
    a full --limit sync vs being cut short by chaos.

Layer 1 of three for issue #123.
chore(testnet): bump adversary + sidecar pins to 9b439a3 for #123 perturbation metrics
Pulls in:
- adversary:9b439a3 — emits SDK assertions per attack (Reachable
  adversary_chain_sync_started + Sometimes adversary_chain_sync_completed)
- sidecar:9b439a3 — finally_tips_agree.sh emits finally_perturbation_metrics
  with blocks_produced_total and max_slot_lag

Both pins move to the SHA that introduces the perturbation Layer 1
+ Layer 3 changes.
feat(adversary): emit Antithesis SDK assertions to prove the attacker fired
Adds Adversary.SDK with Reachable / Sometimes assertion emitters that
write to \$ANTITHESIS_OUTPUT_DIR/sdk.jsonl (default /tmp/sdk.jsonl).
Wires two assertions into app/Main.hs:

  - reachable("adversary_chain_sync_started", {target_host, point, limit})
    fires once per invocation before connectToNode. Antithesis report
    will show, segmented by target_host, "the adversary fired against
    pN at least once". A host that never gets attacked is visible as
    a missing Reachable hit.

  - sometimes(true|false, "adversary_chain_sync_completed",
              {target_host, tip|reason})
    fires once per invocation on completion. true on clean exit,
    false on connect/protocol failure. Sometimes-true vs Sometimes-
    false buckets quantify how often the adversary actually completed
    a full --limit sync vs being cut short by chaos.

Layer 1 of three for issue #123.
feat(sidecar): emit blocks_total + max_slot_lag perturbation metrics from finally_tips_agree
Adds an emit_perturbation_metrics() helper to
components/sidecar/composer/convergence/finally_tips_agree.sh that, on
every run, computes:

  - blocks_produced_total : sum of producers' block heights at end-of-test
  - max_slot_lag          : max(slot) − min(slot) across producers

and emits them as a Sometimes(true) "finally_perturbation_metrics" event
with both numbers in details. The Antithesis report's per-bucket
distribution view then shows the curve across runs without needing a
separate observer.

Slot lag is 0 on a perfectly synchronous cluster, mildly positive
under fault injection, large under attack pressure — gives us a
quantitative comparison for master vs adversary side-by-sides.

Layer 3 of three for issue #123.
docs(adversary): rewrite for the CLI-per-tick redesign
The two adversary docs described the long-running daemon shape that
was retired in #110. Rewrites both pages to match the current state:

- adversary.md — sleep-forever container, single CLI per tick, single
  driver with --target-host fan-out over all 5 cluster hosts,
  --seed-driven random pick. New CLI flag table. New compose
  fragment. Local test loop via scripts/smoke-test.sh
  cardano_node_adversary. Closing section explains why the daemon
  was retired (two schedulers, targeting bias, lifecycle complexity
  for no benefit).
- adversary-roadmap.md — restructures around CLIs, not endpoints.
  Each future archetype is one binary + one driver, not a new daemon
  endpoint. Tier list preserved; "what's behind us" replaces the
  daemon-era status table. Tickets section reflects the closures
  done in PRs #110 and #122.

Also drops the dead adversary-CI.yaml badge from the top README —
that workflow was retired in 9bc9b23.
fix(composer): hard 5s wall-clock on tx-generator nc -U calls
The faults-enabled 1h Antithesis run on cardano_node_tx_generator
(commit 12a80b8, session 95887c0ae21ed981bf9e85943bda257f-50-7,
report OOdYZcg__MdS4qqvkORdLTR-) flagged 2 driver-script findings:

  tx-generator/parallel_driver_refill.sh        command_runtime=25.78s
  tx-generator/eventually_population_grew.sh    command_runtime=24.54s

both with command_return_code=1 and empty stderr. Reading the
indexed log streams returns 0 events for these scripts' .err
streams — they didn't crash, they were killed by the composer's
per-step deadline while blocked inside 'nc -U' against the daemon's
control socket.

Likely path: under a specific fault window the daemon's accept loop
wedges (mid-reconnect / mid-deadlock during a network-partition or
node-pause event), 'nc -U -q 1' has no kernel-side timeout to bail
on, the composer wrapper's ~25s deadline fires, exit code propagates
as 1 — surfacing as a finding on the built-in 'Commands finish with
zero exit code' Always property.

Wrap each control-socket request in 'timeout --kill-after=2s 5s sh -c
"..."' so any blocked nc is bailed out within 5s + 2s SIGKILL grace,
the empty-RSP branch hits the 'tx_generator_*_daemon_unreachable'
Reachability marker, and the script exits 0. The underlying daemon
wedge is a separate ticket on the upstream cardano-node-clients repo
— this composer fix masks the symptom so the testnet baseline isn't
contaminated by it.
docs(adversary): rewrite for the CLI-per-tick redesign
The two adversary docs described the long-running daemon shape that
was retired in #110. Rewrites both pages to match the current state:

- adversary.md — sleep-forever container, single CLI per tick, single
  driver with --target-host fan-out over all 5 cluster hosts,
  --seed-driven random pick. New CLI flag table. New compose
  fragment. Local test loop via scripts/smoke-test.sh
  cardano_node_adversary. Closing section explains why the daemon
  was retired (two schedulers, targeting bias, lifecycle complexity
  for no benefit).
- adversary-roadmap.md — restructures around CLIs, not endpoints.
  Each future archetype is one binary + one driver, not a new daemon
  endpoint. Tier list preserved; "what's behind us" replaces the
  daemon-era status table. Tickets section reflects the closures
  done in PRs #110 and #122.

Also drops the dead adversary-CI.yaml badge from the top README —
that workflow was retired in 9bc9b23.
feat(testnet): cardano_node_tx_generator iteration testnet for the Haskell tx-generator daemon
Rebases on current main (which now has the workflow inputs.test fix
e3b09a0 and the publish-images all-testnets glob 81f3bf1, so the
sibling publish workflow + script from earlier iterations of this
branch are dropped). Master-side files (testnets/cardano_node_master/,
scripts/, master workflows) are untouched.

Adds:

  * testnets/cardano_node_tx_generator/{docker-compose.yaml,
    testnet.yaml,relay-topology.json,tracer-config.yaml,README.md}
    — mirrors master's image set 1:1 (cardano-node x3 by digest,
    cardano-tracer by digest, configurator/log-tailer/tracer-sidecar
    by digest, sidecar:65039df) plus the tx-generator service
    active. Network name 'cardano-node-tx-generator-testnet' to
    avoid collision with master's network when both run on the
    same docker daemon.
  * components/tx-generator/flake.nix bumped to upstream
    711eb22ac03e67b753f7ce70e635cddcf6f3cdce — full
    reconnect-resilience stack: PR #105 (supervisor +
    BlockedIndefinitelyOnSTM catch), #110 (post-reconnect indexer
    freshness gate), #114 (pre-submit chain-tip probe), #115
    (refill duplicate-submit recovery), #116 (recovery-await timeout
    aligned with dcAwaitTimeoutSeconds), #117 (refill recovery-await
    timeout -> IndexNotReady), #118 (same recovery in transact arm).
  * Composer scripts hardened (set -u, always exit 0, lastTxId gate
    on did_not_grow).
  * docs/components/tx-generator.md — daemon architecture: composer-
    as-clock contract, deterministic per-request flow, single-bearer
    N2C topology with in-tree address-to-UTxO indexer, NDJSON wire
    schema with response classes, per-request build/probe/submit/
    recovery flow, reconnect-resilience PR stack, composer scripts
    convention, persistent state, assertion classes.
  * docs/testnets/cardano-node-tx-generator.md — testnet rationale,
    image-set parity table with master, dispatch invocations, ref to
    the repo-wide publish-images flow.
  * mkdocs.yml — Components and Testnets nav entries.

Verified clean on a 1h no-faults Antithesis dispatch:
findings_new=0, all tx_generator_*_landed assertions firing, no
tx_generator_*_submit_rejected. Compose tag in this commit is
PLACEHOLDER; the next commit on this branch sets it to this commit's
SHA so publish-images.sh can resolve it as a downstream commit ref.
static-nix-tools: patch cabal-install to pin unit-id OS via env var
`hashedInstalledPackageId` selects between Long / Short / VeryShort
unit-id formats based on `buildOS` — the OS where cabal-install is
currently executing.  For haskell.nix that's the *eval* platform of
the plan-nix derivation, not the *build* platform where cabal will
later actually do the compile.  When the two differ (e.g. evaluating
on Darwin while building x86_64-linux derivations), plan-nix unit-ids
diverge from the unit-ids slice cabal v2-build computes — every slice
then tries to rebuild every dep from source.

Add a `CABAL_INSTALLED_PACKAGE_ID_OS` env var that overrides
`buildOS` for unit-id format selection.  haskell.nix sets it to the
build platform's OS when invoking `make-install-plan`.
docs: architectural docs for tx-generator component + cardano_node_tx_generator testnet
Two new docs pages:

- docs/components/tx-generator.md — describes the daemon's role
  (composer-as-clock, deterministic per-request, monotonic
  population growth), the in-process N2C topology (single bearer
  for ChainSync + LSQ + LTxS, in-tree address-to-UTxO indexer),
  the NDJSON wire schema with response classes (Ok / IndexNotReady
  / NoPickableSource / SubmitRejected), the per-request
  build → pre-submit probe → submit → post-submit recovery flow,
  the reconnect-resilience PR stack (#105/#110/#114/#115/#116/#117/#118)
  and what each PR contributes, the composer scripts and the
  always-exit-0 + set -u rule, persistent state on disk, and the
  hard-failure vs reachability assertion classes.

- docs/testnets/cardano-node-tx-generator.md — sibling testnet
  rationale (master is read-only on feature branches), image-set
  parity table with master, network topology mirror (3-pool
  + 2-relay; tx-generator → relay1 over single N2C bearer),
  local-run + Antithesis-dispatch invocations, the sibling
  publish-images workflow, and the master-promotion pattern.

mkdocs.yml updated with both pages in the Components and Testnets
nav sections respectively.