Apr 18, 5-6 PM (6)
Apr 18, 6-7 PM (2)
Apr 18, 7-8 PM (2)
Apr 18, 8-9 PM (4)
Apr 18, 9-10 PM (9)
Apr 18, 10-11 PM (21)
Apr 18, 11-12 AM (23)
Apr 19, 12-1 AM (1)
Apr 19, 1-2 AM (4)
Apr 19, 2-3 AM (1)
Apr 19, 3-4 AM (0)
Apr 19, 4-5 AM (0)
Apr 19, 5-6 AM (3)
Apr 19, 6-7 AM (0)
Apr 19, 7-8 AM (2)
Apr 19, 8-9 AM (1)
Apr 19, 9-10 AM (1)
Apr 19, 10-11 AM (4)
Apr 19, 11-12 PM (7)
Apr 19, 12-1 PM (6)
Apr 19, 1-2 PM (8)
Apr 19, 2-3 PM (23)
Apr 19, 3-4 PM (7)
Apr 19, 4-5 PM (4)
Apr 19, 5-6 PM (3)
Apr 19, 6-7 PM (8)
Apr 19, 7-8 PM (3)
Apr 19, 8-9 PM (8)
Apr 19, 9-10 PM (6)
Apr 19, 10-11 PM (25)
Apr 19, 11-12 AM (23)
Apr 20, 12-1 AM (4)
Apr 20, 1-2 AM (5)
Apr 20, 2-3 AM (2)
Apr 20, 3-4 AM (7)
Apr 20, 4-5 AM (1)
Apr 20, 5-6 AM (8)
Apr 20, 6-7 AM (12)
Apr 20, 7-8 AM (29)
Apr 20, 8-9 AM (42)
Apr 20, 9-10 AM (37)
Apr 20, 10-11 AM (95)
Apr 20, 11-12 PM (42)
Apr 20, 12-1 PM (39)
Apr 20, 1-2 PM (53)
Apr 20, 2-3 PM (68)
Apr 20, 3-4 PM (47)
Apr 20, 4-5 PM (41)
Apr 20, 5-6 PM (31)
Apr 20, 6-7 PM (24)
Apr 20, 7-8 PM (10)
Apr 20, 8-9 PM (7)
Apr 20, 9-10 PM (16)
Apr 20, 10-11 PM (28)
Apr 20, 11-12 AM (18)
Apr 21, 12-1 AM (24)
Apr 21, 1-2 AM (5)
Apr 21, 2-3 AM (13)
Apr 21, 3-4 AM (4)
Apr 21, 4-5 AM (3)
Apr 21, 5-6 AM (8)
Apr 21, 6-7 AM (15)
Apr 21, 7-8 AM (44)
Apr 21, 8-9 AM (119)
Apr 21, 9-10 AM (36)
Apr 21, 10-11 AM (35)
Apr 21, 11-12 PM (98)
Apr 21, 12-1 PM (57)
Apr 21, 1-2 PM (71)
Apr 21, 2-3 PM (60)
Apr 21, 3-4 PM (33)
Apr 21, 4-5 PM (31)
Apr 21, 5-6 PM (27)
Apr 21, 6-7 PM (38)
Apr 21, 7-8 PM (35)
Apr 21, 8-9 PM (37)
Apr 21, 9-10 PM (14)
Apr 21, 10-11 PM (34)
Apr 21, 11-12 AM (12)
Apr 22, 12-1 AM (2)
Apr 22, 1-2 AM (3)
Apr 22, 2-3 AM (3)
Apr 22, 3-4 AM (4)
Apr 22, 4-5 AM (3)
Apr 22, 5-6 AM (17)
Apr 22, 6-7 AM (34)
Apr 22, 7-8 AM (21)
Apr 22, 8-9 AM (37)
Apr 22, 9-10 AM (18)
Apr 22, 10-11 AM (47)
Apr 22, 11-12 PM (45)
Apr 22, 12-1 PM (56)
Apr 22, 1-2 PM (64)
Apr 22, 2-3 PM (44)
Apr 22, 3-4 PM (86)
Apr 22, 4-5 PM (46)
Apr 22, 5-6 PM (17)
Apr 22, 6-7 PM (10)
Apr 22, 7-8 PM (18)
Apr 22, 8-9 PM (15)
Apr 22, 9-10 PM (23)
Apr 22, 10-11 PM (31)
Apr 22, 11-12 AM (17)
Apr 23, 12-1 AM (7)
Apr 23, 1-2 AM (4)
Apr 23, 2-3 AM (4)
Apr 23, 3-4 AM (6)
Apr 23, 4-5 AM (3)
Apr 23, 5-6 AM (8)
Apr 23, 6-7 AM (17)
Apr 23, 7-8 AM (26)
Apr 23, 8-9 AM (33)
Apr 23, 9-10 AM (33)
Apr 23, 10-11 AM (29)
Apr 23, 11-12 PM (30)
Apr 23, 12-1 PM (51)
Apr 23, 1-2 PM (69)
Apr 23, 2-3 PM (65)
Apr 23, 3-4 PM (26)
Apr 23, 4-5 PM (21)
Apr 23, 5-6 PM (7)
Apr 23, 6-7 PM (7)
Apr 23, 7-8 PM (11)
Apr 23, 8-9 PM (14)
Apr 23, 9-10 PM (6)
Apr 23, 10-11 PM (28)
Apr 23, 11-12 AM (18)
Apr 24, 12-1 AM (7)
Apr 24, 1-2 AM (4)
Apr 24, 2-3 AM (7)
Apr 24, 3-4 AM (5)
Apr 24, 4-5 AM (8)
Apr 24, 5-6 AM (13)
Apr 24, 6-7 AM (12)
Apr 24, 7-8 AM (33)
Apr 24, 8-9 AM (40)
Apr 24, 9-10 AM (41)
Apr 24, 10-11 AM (71)
Apr 24, 11-12 PM (57)
Apr 24, 12-1 PM (37)
Apr 24, 1-2 PM (53)
Apr 24, 2-3 PM (34)
Apr 24, 3-4 PM (19)
Apr 24, 4-5 PM (16)
Apr 24, 5-6 PM (38)
Apr 24, 6-7 PM (25)
Apr 24, 7-8 PM (12)
Apr 24, 8-9 PM (41)
Apr 24, 9-10 PM (17)
Apr 24, 10-11 PM (30)
Apr 24, 11-12 AM (16)
Apr 25, 12-1 AM (8)
Apr 25, 1-2 AM (1)
Apr 25, 2-3 AM (10)
Apr 25, 3-4 AM (5)
Apr 25, 4-5 AM (3)
Apr 25, 5-6 AM (13)
Apr 25, 6-7 AM (1)
Apr 25, 7-8 AM (4)
Apr 25, 8-9 AM (24)
Apr 25, 9-10 AM (17)
Apr 25, 10-11 AM (4)
Apr 25, 11-12 PM (4)
Apr 25, 12-1 PM (12)
Apr 25, 1-2 PM (3)
Apr 25, 2-3 PM (10)
Apr 25, 3-4 PM (6)
Apr 25, 4-5 PM (10)
Apr 25, 5-6 PM (8)
3,694 commits this week Apr 18, 2026 - Apr 25, 2026
feat(asteria-player): apply parameters to validators (#56)
Iteration 4.

- aiken/apply-params.sh: chains `aiken blueprint apply` calls to
  bake admin_token + game constants + cross-validator hashes into
  the four asteria validators. Documents each parameter's CBOR
  encoding (computed once with Python cbor2). Re-runnable when
  parameters change.

- aiken/plutus-applied.json: the produced blueprint with all four
  validators parameter-applied:
    pellet:    13d2d459de8ad483d2958859fcf192ac90dfcf00ad92a4e78e870e6e
    deploy:    019a7253f89d99d5ee03ffec5ac77a78f862bfb53e0174af3390110f
    asteria:   70ad11758e5c32c6abb385977c7999b041ca4e9c9bc7775b05941b8d
    spacetime: edf8ee06686bf59f412c7c49ba13ff0270d2735ba879f8baf48f4d68

- src/Asteria/Validators.hs: embed plutus-applied.json (was
  plutus.json, now redundant); rename `unappliedBlueprint` →
  `appliedBlueprint`. The four script accessors (asteriaScript,
  spacetimeScript, pelletScript, deployScript) now return
  parameter-applied bytes ready for use.

Parameter values baked in:
  admin_token       = AssetClass { policy: 00..00 (28 zeros)
                                 , name:   "asteriaAdmin" }
  ship_mint_lovelace_fee = 3_000_000
  max_asteria_mining     = 50
  min_asteria_distance   = 50
  initial_fuel           = 100
  max_speed              = Speed 1 30000
  max_ship_fuel          = 100
  fuel_per_step          = 5

Iteration 5 will replace admin_token's static value with a
bootstrap-time one-shot mint tied to a specific TxOutRef.

Verified: docker container's asteria_player_validators_loaded
event now reports the parameter-applied hashes above.
feat(asteria-player): apply parameters to validators (#56)
Iteration 4.

- aiken/apply-params.sh: chains `aiken blueprint apply` calls to
  bake admin_token + game constants + cross-validator hashes into
  the four asteria validators. Documents each parameter's CBOR
  encoding (computed once with Python cbor2). Re-runnable when
  parameters change.

- aiken/plutus-applied.json: the produced blueprint with all four
  validators parameter-applied:
    pellet:    13d2d459de8ad483d2958859fcf192ac90dfcf00ad92a4e78e870e6e
    deploy:    019a7253f89d99d5ee03ffec5ac77a78f862bfb53e0174af3390110f
    asteria:   70ad11758e5c32c6abb385977c7999b041ca4e9c9bc7775b05941b8d
    spacetime: edf8ee06686bf59f412c7c49ba13ff0270d2735ba879f8baf48f4d68

- src/Asteria/Validators.hs: embed plutus-applied.json (was
  plutus.json, now redundant); rename `unappliedBlueprint` →
  `appliedBlueprint`. The four script accessors (asteriaScript,
  spacetimeScript, pelletScript, deployScript) now return
  parameter-applied bytes ready for use.

Parameter values baked in:
  admin_token       = AssetClass { policy: 00..00 (28 zeros)
                                 , name:   "asteriaAdmin" }
  ship_mint_lovelace_fee = 3_000_000
  max_asteria_mining     = 50
  min_asteria_distance   = 50
  initial_fuel           = 100
  max_speed              = Speed 1 30000
  max_ship_fuel          = 100
  fuel_per_step          = 5

Iteration 5 will replace admin_token's static value with a
bootstrap-time one-shot mint tied to a specific TxOutRef.

Verified: docker container's asteria_player_validators_loaded
event now reports the parameter-applied hashes above.
feat(asteria-player): vendor Aiken validators + Haskell loader (#56)
Iteration 3.

- components/asteria-player/aiken/: vendored from txpipe/asteria's
  onchain/src — the four Aiken validators (asteria, spacetime,
  pellet, deploy) plus their lib/ helpers and aiken.toml /
  aiken.lock for deterministic builds.
- components/asteria-player/aiken/plutus.json: the unapplied
  blueprint produced by `aiken build` from the vendored sources,
  committed for review and so the Haskell layer can embed it
  without an aiken-toolchain build dep at docker-image time.
- src/Asteria/Validators.hs: embedFile-based loader that decodes
  plutus.json at compile time and exposes asteriaScript /
  spacetimeScript / pelletScript / deployScript as Plutus V3
  Script ConwayEra values.
- app/PlayerMain.hs: at startup, hashScript each validator and
  emit asteria_player_validators_loaded_<id> sdk_reachable with
  the four hashes in the details body — proves the load
  machinery works end-to-end inside the running container.

The validators are still parameterized — they're not yet usable
on-chain. Iteration 4 will add an aiken blueprint apply step
(admin token, game constants, cross-validator hashes) and
replace the unapplied bytes; the player code already references
each validator by name so iteration 4 only swaps the JSON file.

Verified:
  - nix build .#asteria-player succeeds.
  - nix build .#docker-image && docker load produces a 158 MB image.
  - docker compose up shows asteria_player_validators_loaded_<id>
    events with the expected four script hashes
    (69090085... / 443d84cd... / 74f43ee1... / 845cbd52...).
feat(asteria-player): vendor Aiken validators + Haskell loader (#56)
Iteration 3.

- components/asteria-player/aiken/: vendored from txpipe/asteria's
  onchain/src — the four Aiken validators (asteria, spacetime,
  pellet, deploy) plus their lib/ helpers and aiken.toml /
  aiken.lock for deterministic builds.
- components/asteria-player/aiken/plutus.json: the unapplied
  blueprint produced by `aiken build` from the vendored sources,
  committed for review and so the Haskell layer can embed it
  without an aiken-toolchain build dep at docker-image time.
- src/Asteria/Validators.hs: embedFile-based loader that decodes
  plutus.json at compile time and exposes asteriaScript /
  spacetimeScript / pelletScript / deployScript as Plutus V3
  Script ConwayEra values.
- app/PlayerMain.hs: at startup, hashScript each validator and
  emit asteria_player_validators_loaded_<id> sdk_reachable with
  the four hashes in the details body — proves the load
  machinery works end-to-end inside the running container.

The validators are still parameterized — they're not yet usable
on-chain. Iteration 4 will add an aiken blueprint apply step
(admin token, game constants, cross-validator hashes) and
replace the unapplied bytes; the player code already references
each validator by name so iteration 4 only swaps the JSON file.

Verified:
  - nix build .#asteria-player succeeds.
  - nix build .#docker-image && docker load produces a 158 MB image.
  - docker compose up shows asteria_player_validators_loaded_<id>
    events with the expected four script hashes
    (69090085... / 443d84cd... / 74f43ee1... / 845cbd52...).
feat(asteria-player): pin cardano-node-clients + N2C provider (#56)
Iteration 2 of the asteria phase-1 gatherer.

- cabal.project: pin cardano-node-clients@f6a31ca via
  source-repository-package along with all of its transitive SRPs
  (chain-follower, rocksdb-kv-transactions, rocksdb-haskell,
  cardano-ledger-read, typed-protocols, quickcheck-state-machine,
  cuddle) and matching ledger / ouroboros constraints.
- flake.nix / nix/project.nix: pin haskell.nix, hackage.nix, iohkNix,
  CHaP to the same revisions as cardano-node-clients; bump the
  compiler to ghc9122; add the fix-libs module for libsodium-vrf,
  secp256k1, libblst, lmdb, liburing.
- src/Asteria/Provider.hs: thin wrapper around
  cardano-node-clients's runNodeClient + mkN2CProvider /
  mkN2CSubmitter; reads CARDANO_NODE_SOCKET_PATH and NETWORK_MAGIC
  from the environment.
- app/PlayerMain.hs: connect via withN2C, query protocol params
  every 5 seconds in a loop, emit
  asteria_player_pp_query_<id> sometimes events.

Verified against the local docker-compose cluster: each player
container connects to its assigned relay, queries pp every 5s, and
the SDK fallback file shows a steady stream of sometimes events.
feat(asteria-player): pin cardano-node-clients + N2C provider (#56)
Iteration 2 of the asteria phase-1 gatherer.

- cabal.project: pin cardano-node-clients@f6a31ca via
  source-repository-package along with all of its transitive SRPs
  (chain-follower, rocksdb-kv-transactions, rocksdb-haskell,
  cardano-ledger-read, typed-protocols, quickcheck-state-machine,
  cuddle) and matching ledger / ouroboros constraints.
- flake.nix / nix/project.nix: pin haskell.nix, hackage.nix, iohkNix,
  CHaP to the same revisions as cardano-node-clients; bump the
  compiler to ghc9122; add the fix-libs module for libsodium-vrf,
  secp256k1, libblst, lmdb, liburing.
- src/Asteria/Provider.hs: thin wrapper around
  cardano-node-clients's runNodeClient + mkN2CProvider /
  mkN2CSubmitter; reads CARDANO_NODE_SOCKET_PATH and NETWORK_MAGIC
  from the environment.
- app/PlayerMain.hs: connect via withN2C, query protocol params
  every 5 seconds in a loop, emit
  asteria_player_pp_query_<id> sometimes events.

Verified against the local docker-compose cluster: each player
container connects to its assigned relay, queries pp every 5s, and
the SDK fallback file shows a steady stream of sometimes events.
feat(asteria-player): iteration 1 wiring proof for #56
Add `components/asteria-player/`, a new container that runs the
asteria game inside the cardano-node-antithesis cluster.

Iteration 1 only proves the wiring end-to-end:

  - Nix-built docker image (`flake.nix` + `nix/{project,docker-image}.nix`)
    mirroring the sidecar pattern.
  - `asteria-bootstrap` (one-shot) and `asteria-player` (long-running)
    Haskell binaries, currently just emitting `sdkReachable` to the
    JSONL fallback file before exiting / sleeping forever.
  - `Asteria.Sdk` Haskell module covering reachable / unreachable /
    sometimes / always; mirrors the existing shell helpers in
    `sidecar/composer/convergence/helper_sdk_lib.sh`.
  - Composer scripts at `composer/asteria/`:
    `parallel_driver_asteria_bootstrap.sh`,
    `parallel_driver_asteria_player.sh`,
    `eventually_asteria_alive.sh`, plus a copy of `helper_sdk_lib.sh`.
    Baked into the image at `/opt/antithesis/test/v1/asteria/` and
    exposed as wrapper bins (`/bin/parallel_driver_asteria_*`).
  - `testnets/cardano_node_master/docker-compose.yaml` extended with
    `asteria-bootstrap` (depends on relay1) plus `asteria-player-1` /
    `asteria-player-2` (depend on bootstrap completing successfully),
    sharing an `asteria-sdk` volume for the JSONL fallback file.

Verified locally with `docker compose up`:

  - asteria-bootstrap exits 0 and writes its sdk_reachable events.
  - asteria-player-1/2 start once bootstrap is green and write
    their own sdk_reachable events.
  - All six assertions (3 shell + 3 Haskell) appear in
    `/sdk/sdk.jsonl` on the shared volume.

Subsequent iterations layer on the cardano-node-clients TxBuild DSL,
the parameter-applied Aiken validators, Antithesis-controlled
randomness, and the actual move_ship / gather_fuel / mine_asteria
game loop.
Initialize speckit + constitution + phase1 asteria gatherer spec
Bootstraps spec-driven workflow for cardano-node-antithesis:

- .specify/ scaffolding installed via nix run /code/spec-kit.
- .specify/memory/constitution.md authored from scratch, capturing
  the composer-first, SDK-instrumented, duration-robust principles
  the repo has been implicitly following since PR #53 + the
  shared/skills/antithesis-tests skill.
- .claude/commands/ speckit command definitions.
- specs/phase1-asteria-gatherer/spec.md — first feature spec:
  txpipe/asteria deployed as the eager-agent workload, one
  gatherer parallel_driver per ship, admin-bootstrap serial driver,
  anytime + finally invariants. Explicit out-of-scope list for
  phase 2+ (mine, quit, spawn contention, prize tokens).

Implements issue #56 (parent #55). No runtime changes yet; this
commit is spec-only.
feat(asteria-player): iteration 1 wiring proof for #56
Add `components/asteria-player/`, a new container that runs the
asteria game inside the cardano-node-antithesis cluster.

Iteration 1 only proves the wiring end-to-end:

  - Nix-built docker image (`flake.nix` + `nix/{project,docker-image}.nix`)
    mirroring the sidecar pattern.
  - `asteria-bootstrap` (one-shot) and `asteria-player` (long-running)
    Haskell binaries, currently just emitting `sdkReachable` to the
    JSONL fallback file before exiting / sleeping forever.
  - `Asteria.Sdk` Haskell module covering reachable / unreachable /
    sometimes / always; mirrors the existing shell helpers in
    `sidecar/composer/convergence/helper_sdk_lib.sh`.
  - Composer scripts at `composer/asteria/`:
    `parallel_driver_asteria_bootstrap.sh`,
    `parallel_driver_asteria_player.sh`,
    `eventually_asteria_alive.sh`, plus a copy of `helper_sdk_lib.sh`.
    Baked into the image at `/opt/antithesis/test/v1/asteria/` and
    exposed as wrapper bins (`/bin/parallel_driver_asteria_*`).
  - `testnets/cardano_node_master/docker-compose.yaml` extended with
    `asteria-bootstrap` (depends on relay1) plus `asteria-player-1` /
    `asteria-player-2` (depend on bootstrap completing successfully),
    sharing an `asteria-sdk` volume for the JSONL fallback file.

Verified locally with `docker compose up`:

  - asteria-bootstrap exits 0 and writes its sdk_reachable events.
  - asteria-player-1/2 start once bootstrap is green and write
    their own sdk_reachable events.
  - All six assertions (3 shell + 3 Haskell) appear in
    `/sdk/sdk.jsonl` on the shared volume.

Subsequent iterations layer on the cardano-node-clients TxBuild DSL,
the parameter-applied Aiken validators, Antithesis-controlled
randomness, and the actual move_ship / gather_fuel / mine_asteria
game loop.
feat(cardano): persist shard count alongside ashard_progress
Guards against a config change to `account_shards` corrupting an
in-flight boundary. Previously, if dolos crashed mid-boundary and the
operator changed `account_shards` between crash and restart, the resume
would re-partition the account key space with the new count, mismatching
the cursor's already-committed shards.

Fix: snapshot the boundary's shard count into state at the first
`EpochEndAccumulate` apply. The persisted total is authoritative for the
duration of the in-flight boundary; the new config value only takes
effect on the next boundary.

Changes:
- New `AShardProgress { committed, total }` struct stored at
  `EpochState.ashard_progress: Option<AShardProgress>` (was
  `Option<u32>`).
- `EpochEndAccumulate` carries `total_shards`. Its apply validates the
  delta's `total_shards` matches any previously persisted total and
  surfaces an error if they diverge (would only happen if a work unit
  was constructed with a stale config view).
- `EpochWrapUp` and `EpochTransition` undo fields adapted to the new
  type.
- `AShardWorkUnit::load` / `commit_state` read the persisted total when
  present and fall back to `config.account_shards()` for fresh
  boundaries.
- `CardanoLogic` caches `effective_account_shards` (= persisted total
  if a boundary is in flight, else config). Refreshed at every
  `pop_work` call so `receive_block` (which has no state access) can
  use the up-to-date value when constructing
  `WorkBuffer::AShardingBoundary`.
- Crash-recovery wording updated to surface a clear warning when the
  persisted total disagrees with current config.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>