Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
d927b28
feat(evolution): compute per-type forward-compatibility ranges (tiered)
pshirshov Sep 13, 2026
e5f6015
feat(evolution): emit forward-compat metadata in all 9 backends; docs
pshirshov Sep 13, 2026
e3a4335
docs: mark forward-compat plan as implemented, record resolved decisions
pshirshov Sep 13, 2026
c82d86e
fix(evolution): make deep schema hashing order- and structure-sensitive
pshirshov Sep 13, 2026
0c4f434
feat(envelope): publish JSON readable-min bound ($rv) + reader Forwar…
pshirshov Sep 14, 2026
e7bba6c
feat(envelope): $rv readable-min bound + ForwardReadPolicy across all…
pshirshov Sep 14, 2026
8e8f1b6
docs(forward-compat): list envelope e2e specs
pshirshov Sep 14, 2026
58309a1
fix(envelope): reject unknown hasMinCompat flag bytes; real TS/Rust f…
pshirshov Sep 14, 2026
8d0c5f4
refactor(codegen): build TS/Rust domain facades and GraphQL SDL with …
pshirshov Sep 14, 2026
840795e
feat(envelope): writer-side ForwardWritePolicy for the UEBA bound; re…
pshirshov Sep 14, 2026
3f3d190
docs(forward-compat): worked wire examples for every envelope flag co…
pshirshov Sep 15, 2026
3e058c8
docs(forward-compat): per-type structure + envelope walk-through in e…
pshirshov Sep 15, 2026
d9c0cdc
feat(envelope): binary envelope metaVersion 2 with both bounds; reade…
pshirshov Sep 15, 2026
3626529
docs(forward-compat): v2 envelope dumps for every fixture type
pshirshov Sep 15, 2026
065140d
test(envelope): cross-language golden bytes in all backends; fix Rust…
pshirshov Sep 15, 2026
b4329f2
docs(spec): v2 conformance reference with canonical bytes
pshirshov Sep 15, 2026
62f56af
docs: record the known gaps of the forward-compat / envelope work
pshirshov Sep 15, 2026
69bbb4c
fix(runtime): uniform fail-fast on a missing min-reader tier; structu…
pshirshov Sep 16, 2026
4aef4b5
refactor(backends): consolidate codec policies and runtime helpers
pshirshov Sep 17, 2026
bb2c782
refactor(translators): share product, service and metadata planning
pshirshov Sep 17, 2026
1ef6040
refactor(conversions): share Java and Kotlin value conversion planning
pshirshov Sep 17, 2026
5089e8c
Share UEBA layout planning and enforce validation in release builds
pshirshov Sep 17, 2026
9f804aa
Increase Kotlin KMP fixture compiler heap
pshirshov Sep 18, 2026
f5c0a03
feat(forward-compat): resolve forward readability per wire format; de…
pshirshov Sep 20, 2026
2b016c2
fix(evolution): make declared `was` renames sound across a version chain
pshirshov Sep 20, 2026
979eaf7
fix(evolution): make declared `was` renames sound for ADT branches an…
pshirshov Sep 20, 2026
65c4513
feat(evolution): resolve type renames for conversions and forward-compat
pshirshov Sep 20, 2026
fc5030e
fix(scheme): render declared type renames; pin rename invariants with…
pshirshov Sep 21, 2026
7d0609d
docs(forward-compat): record the renamed-type top-level bound as a de…
pshirshov Sep 21, 2026
506c097
refactor(translators): resolve the current domain from the subcontext…
pshirshov Sep 21, 2026
e7725bd
refactor(translators): resolve enquiries and evolution from the subco…
pshirshov Sep 21, 2026
8e5204e
ci(macos): stop caching the sbt 2.x disk cache on the Intel runner
pshirshov Sep 21, 2026
48d882f
build: sbt 1.11.7 -> 1.13.0
pshirshov Sep 21, 2026
b0d7b0b
build: bump dependencies and sbt plugins to latest same-major releases
pshirshov Sep 21, 2026
45972d8
build: bump jline 3 -> 4 and json-schema-validator 1 -> 3
pshirshov Sep 21, 2026
7c36771
build: regenerate deps.lock.json for the bumped dependencies
pshirshov Sep 21, 2026
1ca62fd
fix(csharp): rewrite deduplicated method I/O types in obsolete servic…
pshirshov Sep 23, 2026
e187f0a
fix(runtime): follow the conversion chain in C# IsDeprecated
pshirshov Sep 23, 2026
9510633
fix(runtime)!: thread the codec context through the facades' JSON enc…
pshirshov Sep 24, 2026
9c670c5
fix(rust): give the JSON encode path a codec context so `any` payload…
pshirshov Sep 24, 2026
3530405
refactor(rust): emit an explicit JSON encoder, pinned against the ser…
pshirshov Sep 24, 2026
983137d
refactor(rust): reuse RsFieldRepresentation's transitive `any` analysis
pshirshov Sep 24, 2026
fb57054
fix(typescript): mark the generated MCP server's findTool as an override
pshirshov Sep 24, 2026
c9c7203
refactor(rust): emit an explicit JSON decoder, pinned against the ser…
pshirshov Sep 24, 2026
940b47e
fix(rust): route user-typed map keys through their adapter in the exp…
pshirshov Sep 24, 2026
9df09e0
refactor(rust): route JSON through the explicit codecs, and give map-…
pshirshov Sep 24, 2026
5a87a4e
feat(json): explicit Rust JSON codecs, and 64-bit integers as decimal…
pshirshov Sep 24, 2026
a11da1f
test(json): pin the 64-bit reader leniency that backward compatibilit…
pshirshov Sep 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11 changes: 11 additions & 0 deletions .github/workflows/baboon-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,17 @@ jobs:
native-image-job-reports: true
- name: Set up SBT
uses: sbt/setup-sbt@v1
with:
# The action's post step caches the sbt 2.x disk cache
# ($HOME/Library/Caches/sbt), which sbt 1.x never writes -- see
# project/build.properties. Building its key costs a whole-workspace
# hashFiles('**/*.sbt', '**/*.properties'), which by teardown has to walk every
# backend's generated code plus node_modules / cargo target / .dart_tool /
# Swift .build. That exceeds the 120s expression limit on the Intel runner (the
# slowest of the matrix at ~2h40m) and fails the job AFTER build, tests and
# artifact upload have all succeeded. Dependency caching is unaffected:
# graalvm/setup-graalvm above already does it via `cache: 'sbt'`.
disk-cache: false
- name: Set up Dart SDK
uses: dart-lang/setup-dart@v1
- name: Install mudyla
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,6 @@ pubspec.lock

# hand-written documentation exempt from the ledger-backend ignore above
!docs/cli-reference.md

# local-only working notes (not part of any ledger)
notes.local.md
2 changes: 2 additions & 0 deletions .jvmopts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-Xmx8g
-Xss8m
77 changes: 69 additions & 8 deletions .mdl/defs/tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,13 +236,23 @@ Run Python tests with regular adt codecs.
```bash
dep action.test-cs-regular

BABOON_BIN="$(realpath "${action.build.binary}")"
TEST_DIR="${action.test-gen-regular-adt.test_dir}"
pushd "$TEST_DIR/py-stub"
python3 -m venv .venv
if [ -f ".venv/Scripts/activate" ]; then source .venv/Scripts/activate; else source .venv/bin/activate; fi
python3 -m pip install -r requirements.txt
python3 -m unittest discover -s BaboonTests/GeneratedTests/testpkg/pkg0
python3 -m unittest discover -s BaboonTests/RuntimeTests
"$BABOON_BIN" --model-dir ReviewModels --lockfile GeneratedReview.lock \
:python --output GeneratedReview/BaboonDefinitions/Generated \
--fixture-output GeneratedReview/BaboonTests/GeneratedFixtures \
--test-output GeneratedReview/BaboonTests/GeneratedTests \
--generate-json-codecs-by-default=true --generate-ueba-codecs-by-default=true \
--py-wrapped-adt-branch-codecs=false
pushd GeneratedReview
python3 -m unittest discover -s ../ReviewTests
popd
popd

ret success:bool=true
Expand All @@ -261,15 +271,34 @@ popd
ret success:bool=true
```

# action: test-rust-conversion-regression

Compile and exercise typed Rust evolution on isolated nested-collection models.

```bash
BABOON_BIN="$(realpath "${action.build.binary}")"
bash ./test/rs-conversion-regression/test.sh "$BABOON_BIN"
ret success:bool=true
```

# action: test-typescript-regular

Run TypeScript tests with regular ADT codecs.

```bash
dep action.test-cs-regular

BABOON_BIN="$(realpath "${action.build.binary}")"
TEST_DIR="${action.test-gen-regular-adt.test_dir}"
pushd "$TEST_DIR/ts-stub"
"$BABOON_BIN" --model-dir ReviewModels --lockfile generated-review.lock \
:typescript --output src/generated-review \
--generate-json-codecs-by-default=true --generate-ueba-codecs-by-default=true \
--ts-enum-lowercase-values=true --ts-wrapped-adt-branch-codecs=false
"$BABOON_BIN" --model-dir ReviewModels --lockfile generated-review-records.lock \
:typescript --output src/generated-review-records \
--generate-json-codecs-by-default=true --generate-ueba-codecs-by-default=true \
--ts-enum-lowercase-values=true --ts-maps-as-records=true --ts-wrapped-adt-branch-codecs=false
npm install
npm run build
npm test
Expand Down Expand Up @@ -543,13 +572,23 @@ Run Python tests with wrapped ADT codecs
```bash
dep action.test-cs-wrapped

BABOON_BIN="$(realpath "${action.build.binary}")"
TEST_DIR="${action.test-gen-wrapped-adt.test_dir}"
pushd "$TEST_DIR/py-stub"
python3 -m venv .venv
if [ -f ".venv/Scripts/activate" ]; then source .venv/Scripts/activate; else source .venv/bin/activate; fi
python3 -m pip install -r requirements.txt
python3 -m unittest discover -s BaboonTests/GeneratedTests/testpkg/pkg0
python3 -m unittest discover -s BaboonTests/RuntimeTests
"$BABOON_BIN" --model-dir ReviewModels --lockfile GeneratedReview.lock \
:python --output GeneratedReview/BaboonDefinitions/Generated \
--fixture-output GeneratedReview/BaboonTests/GeneratedFixtures \
--test-output GeneratedReview/BaboonTests/GeneratedTests \
--generate-json-codecs-by-default=true --generate-ueba-codecs-by-default=true \
--py-wrapped-adt-branch-codecs=true
pushd GeneratedReview
python3 -m unittest discover -s ../ReviewTests
popd
popd

ret success:bool=true
Expand All @@ -575,8 +614,17 @@ Run TypeScript tests with wrapped ADT codecs.
```bash
dep action.test-cs-wrapped

BABOON_BIN="$(realpath "${action.build.binary}")"
TEST_DIR="${action.test-gen-wrapped-adt.test_dir}"
pushd "$TEST_DIR/ts-stub"
"$BABOON_BIN" --model-dir ReviewModels --lockfile generated-review.lock \
:typescript --output src/generated-review \
--generate-json-codecs-by-default=true --generate-ueba-codecs-by-default=true \
--ts-enum-lowercase-values=true --ts-wrapped-adt-branch-codecs=true
"$BABOON_BIN" --model-dir ReviewModels --lockfile generated-review-records.lock \
:typescript --output src/generated-review-records \
--generate-json-codecs-by-default=true --generate-ueba-codecs-by-default=true \
--ts-enum-lowercase-values=true --ts-maps-as-records=true --ts-wrapped-adt-branch-codecs=true
npm install
npm test
popd
Expand Down Expand Up @@ -1654,7 +1702,7 @@ validation on every returned inputSchema.
TEST_DIR="${action.test-gen-ts-mcp.test_dir}"
pushd "$TEST_DIR/ts-stub"
npm install
npx vitest run src/mcp.test.ts
npx vitest run src/mcp.test.ts src/dispatch-preparation.test.ts
popd

ret success:bool=true
Expand Down Expand Up @@ -1818,7 +1866,7 @@ to T7 reference) and exercises negative controls.
```bash
TEST_DIR="${action.test-gen-kotlin-mcp.test_dir}"
pushd "$TEST_DIR/kt-stub"
gradle --no-daemon clean test --tests "mcp.McpTests"
gradle --no-daemon clean test --tests "mcp.McpTests" --tests "mcp.McpProtocolOwnershipTest"
popd

ret success:bool=true
Expand Down Expand Up @@ -1929,7 +1977,7 @@ to T7 reference) and exercises negative controls.
```bash
TEST_DIR="${action.test-gen-java-mcp.test_dir}"
pushd "$TEST_DIR/jv-stub"
mvn clean test -Dtest=mcp.McpTests
mvn clean test -Dtest=mcp.McpTests,mcp.McpMetadataOwnershipTest
popd

ret success:bool=true
Expand Down Expand Up @@ -3238,7 +3286,7 @@ pushd "$TEST_DIR/py-stub"
python3 -m venv .venv
if [ -f ".venv/Scripts/activate" ]; then source .venv/Scripts/activate; else source .venv/bin/activate; fi
python3 -m pip install -r requirements.txt
python3 -m unittest BaboonTests.mcp.test_mcp
python3 -m unittest BaboonTests.mcp.test_mcp BaboonTests.mcp.test_dispatch_runtime
popd

ret success:bool=true
Expand Down Expand Up @@ -3502,12 +3550,14 @@ Run complete test suite (orchestrator action).

```bash
dep action.test-sbt-basic
dep action.test-ueba-layout-regression
dep action.test-graphql
dep action.test-openapi
dep action.test-cs-regular
dep action.test-scala-regular
dep action.test-python-regular
dep action.test-rust-regular
dep action.test-rust-conversion-regression
dep action.test-typescript-regular
dep action.test-kotlin-regular
dep action.test-kotlin-kmp-regular
Expand Down Expand Up @@ -3636,6 +3686,16 @@ dep action.test-cs-adt-capture
ret success:bool=true
```

# action: test-ueba-layout-regression

Verify wrapped UEBA branch ordinals across the signed-byte boundary in Scala and Kotlin.

```bash
dep action.build
bash test/ueba-layout-regression/run.sh "${action.build.binary}"
ret success:bool=true
```

# action: test-acceptance

Run cross-language serialization acceptance tests (full cartesian product).
Expand Down Expand Up @@ -4203,8 +4263,8 @@ pushd "$TEST_DIR/dt-stub"
dart pub get
# Analyze only the generated lib and MCP test directory (runtime/ tests reference
# the full model which is not generated in the MCP-only pass).
dart analyze --fatal-warnings lib/ test/mcp/
dart test test/mcp/mcp_tests.dart
dart analyze --fatal-warnings lib/ test/mcp/ test/dispatch_contract_test.dart
dart test test/mcp/mcp_tests.dart test/dispatch_contract_test.dart
popd

ret success:bool=true
Expand Down Expand Up @@ -6143,7 +6203,7 @@ collision, and NoMatchingTool (-32602) against `AbstractMcpMuxer`.
```bash
TEST_DIR="${action.test-gen-rs-mcp-mux.test_dir}"
pushd "$TEST_DIR/rs-stub"
RUSTFLAGS="-D warnings" cargo test --test mcp_mux_tests
RUSTFLAGS="-D warnings" cargo test --test mcp_mux_tests --test runtime_dispatch_tests
popd

ret success:bool=true
Expand Down Expand Up @@ -6188,6 +6248,7 @@ $BABOON_BIN \
--rs-async-services=true

rsync -a ./test/rs-stub-mcp-mux-async-overlay/ "$TEST_DIR/rs-stub/"
cp ./test/rs-stub-mcp-mux-overlay/tests/runtime_dispatch_tests.rs "$TEST_DIR/rs-stub/tests/runtime_dispatch_tests.rs"

ret success:bool=true
ret test_dir:string="$TEST_DIR"
Expand All @@ -6204,7 +6265,7 @@ async-generated `<Service>McpServer`s.
```bash
TEST_DIR="${action.test-gen-rs-mcp-mux-async.test_dir}"
pushd "$TEST_DIR/rs-stub"
RUSTFLAGS="-D warnings" cargo test --test mcp_mux_tests
RUSTFLAGS="-D warnings" cargo test --test mcp_mux_tests --test runtime_dispatch_tests
popd

ret success:bool=true
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ mdl :test-service-acceptance # service-flavour RPC wiring round-trips
- **Local Kotlin OOM workaround:** `mdl :test` runs language test actions in parallel by default. On laptops with <16 GB RAM the Kotlin compiler daemon OOMs under the parallel matrix (documented in `docs/logs/20260428-2350-m16-closeout-log.md`). Run `mdl --seq :build :test` to force serial execution. Slower (~20 min) but completes on memory-constrained machines. CI uses default parallelism.
- **sbt-git (jgit) cannot build inside a linked git worktree** (`NoWorkTreeException: Bare Repository`). When running `mdl :build` or `sbt` from a `git worktree add`-created directory (whose `.git` is a file, not a directory), sbt-git throws this error. Work around by cloning the repo to a real directory (e.g., `git clone <repo> /tmp/baboon-ci-clone`) and running the pipeline from there.
- **Swift 64KB-constant limit (D7/T23):** `baboon_runtime.swift` was split into `baboon_runtime.swift` (pure domain runtime) and `baboon_service_wiring.swift` (service-wiring runtime) to stay within the JVM 64KB bytecode-constant limit hit when the Swift runtime is embedded via `PortableResource.embedSources`. After modifying `baboon-compiler/src/main/resources/baboon-runtime/`, run `sbt clean` before `sbt compile` (the macro caches content per build).
- **Rust / Swift 64KB-constant limit (forward-compat envelope work, 2026-09):** `baboon_codecs_facade.rs` and then `baboon_runtime.swift` crossed the same JVM string-constant limit. Rust: `BaboonTypeMeta` and `mod baboon_type_meta_codec` live in `baboon_type_meta.rs`, re-exported from the facade so every `crate::baboon_codecs_facade::…` path still resolves. Swift: `BaboonVersion`/`BaboonDomainVersion`/`BaboonTypeMeta`/`BaboonTypeMetaCodec` live in `baboon_type_meta.swift` (same module, no imports needed). The compile error to recognise is `UTF8 string too large ... bad String constant of length N` while emitting `BaboonRuntimeResources$`; the fix is always to split the runtime file and add an output entry for the new file in the translator's shared-runtime list.
- **Swift `Package.swift` is hand-written:** `test/sw-stub/Package.swift` lists every generated module as an explicit `.target` (one per domain version, older versions depended upon by newer ones), a `.testTarget` per module under `Tests/BaboonTests/<Module>`, and the `RuntimeTests` dependencies. SwiftPM only *warns* about `Sources/<Dir>` folders that belong to no target, so a fixture domain added to the shared model dir compiles and is tested in the other 8 backends but is silently skipped by the Swift lane until its targets are added (this happened to `fwd-e2e-ok` / `fwd-e2e-chain-ok` in 2026-09). Add the targets in the same commit as the fixture.
- **Swift `@BaboonIndirect` recursive-DTO boxing (D8/T23):** Swift value types (`struct`) cannot have stored properties of their own type (infinite size). Baboon detects recursive DTO fields and wraps them in `@BaboonIndirect` (a property-wrapper that boxes the value behind a `Box<T>` class). Any new test model that includes recursive DTOs in Swift must compile with this boxing in place.
- **Scala MCP requires Either-mode serviceResult (D24/T69):** the Scala MCP dispatch runtime (`AbstractBaboonMcpServer.handle` in `baboon-runtime/scala/BaboonMcpRuntime.scala`) is synchronous and Either-shaped — `handle` matches `Right`/`Left` on the wiring's `invokeJson` result to drive MCP Channel-A/B. It is NOT generic over the configurable `serviceResult` container that `ScServiceWiringTranslator` honors. Combining a non-Either serviceResult (HKT `F[_,_]`, a custom result type, or `--service-result-no-errors=true`) with `--scala-generate-mcp-server=true` is rejected up front with `TranslationIssue.ScalaMcpRequiresEither` (an actionable compiler error) rather than emitting a delegate whose type mismatches the wiring container. The Scala MCP lane (`test-gen-scala-mcp`) therefore stays on `--service-result-type=Either`. Making Scala MCP container-generic is the async-axis (D24) rework, out of scope here.
- **MCP stub model directory isolation (D9/T24):** The MCP stub model lives at `baboon-compiler/src/test/resources/mcp-stub-ok/` — intentionally OUTSIDE the shared `baboon-compiler/src/test/resources/baboon/` model-dir. All `test-gen-*-wiring` and `test-gen-{regular,wrapped}-adt` actions use `--model-dir ./baboon-compiler/src/test/resources/baboon/` and therefore scan every subdirectory. Placing the MCP stub inside the shared dir caused cross-namespace fixture-reference failures in 8 wiring/test lanes (D9). The dedicated `test-gen-<lang>-mcp` actions point at `--model-dir ./baboon-compiler/src/test/resources/mcp-stub-ok/`. A minimal recursive-only `Tree` model (`baboon-compiler/src/test/resources/baboon/recursive-ok/recursive.baboon`) preserves recursive-DTO coverage in the shared matrix without the cross-namespace hazard.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,48 @@ case class VersionLock(
)

case class Locks(
locks: Map[Pkg, List[VersionLock]]
locks: Map[Pkg, List[VersionLock]],
scheme: Int,
)

object Locks {
/** Version of the signature-hashing scheme. Signatures produced by different
* schemes are incomparable: a mismatch means "cannot check drift", never
* "drift detected". History:
* 1 — original deepId hashing (order-erasing: dependency repr lines were
* sorted per field);
* 2 — order- and structure-sensitive deep hashing (dependency reprs kept
* contiguous and ordered, field type-refs included, ADT branch order
* preserved).
*/
final val CurrentScheme: Int = 2
}

object LockCodecs {
implicit lazy val pkgKeyEncoder: KeyEncoder[Pkg] = KeyEncoder.encodeKeyString.contramap(_.toString)
implicit lazy val pkgKeyDecoder: KeyDecoder[Pkg] = KeyDecoder.decodeKeyString.map(s => Pkg(NEList.unsafeFrom(s.split('.').toList)))
implicit lazy val versionCodec: Codec[Version] = Codec.from(Decoder.decodeString.map(s => Version.parse(s)), Encoder.encodeString.contramap(_.v.toString))
implicit lazy val sigidCodec: Codec[SigId] = Codec.from(Decoder.decodeString.map(s => SigId(s)), Encoder.encodeString.contramap(_.value))
implicit lazy val versionLockCodec: Codec[VersionLock] = deriveCodec

private lazy val locksDecoder: Decoder[Locks] = deriveCodec[Locks]
// pre-scheme lockfiles carry no "scheme" key: treat them as scheme 1
private lazy val locksDecoder: Decoder[Locks] = Decoder.instance {
c =>
for {
locks <- c.downField("locks").as[Map[Pkg, List[VersionLock]]]
scheme <- c.downField("scheme").as[Option[Int]]
} yield {
Locks(locks, scheme.getOrElse(1))
}
}
private lazy val locksEncoder: Encoder[Locks] = Encoder.instance { locks =>
val fields = locks.locks.toSeq
.sortBy { case (pkg, _) => pkg.toString }
.map { case (pkg, versions) =>
val sortedVersions = versions.sortBy(_.version)(using Version.ordering).map(versionLockCodec.apply)
pkgKeyEncoder(pkg) -> Json.arr(sortedVersions*)
}
Json.obj("locks" -> Json.obj(fields*))
Json.obj("scheme" -> Json.fromInt(locks.scheme), "locks" -> Json.obj(fields*))
}
implicit lazy val lockscodec: Codec[Locks] = Codec.from(locksDecoder, locksEncoder)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@ class LockfileManagerImpl[F[+_, +_]: Error2: MaybeSuspend2](
enq: BaboonEnquiries,
) extends LockfileManager[F] {
def validateLock(model: BaboonFamily): F[NEList[BaboonIssue], Unit] = {
val currentSigs = Locks(model.domains.map {
case (pkg, lineage) =>
val versions = lineage.versions.toSeq.map {
case (ver, dom) =>
VersionLock(ver, sigOf(dom))
}.toList
(pkg, versions)
}.toMap)
val currentSigs = Locks(
model.domains.map {
case (pkg, lineage) =>
val versions = lineage.versions.toSeq.map {
case (ver, dom) =>
VersionLock(ver, sigOf(dom))
}.toList
(pkg, versions)
}.toMap,
Locks.CurrentScheme,
)

// `evolution.latest` is the only model-derived input the enforcement matrix needs,
// so we project it out and drive the matrix over the projection. This keeps the
Expand Down Expand Up @@ -65,11 +68,18 @@ class LockfileManagerImpl[F[+_, +_]: Error2: MaybeSuspend2](
} yield {
out
}).catchAll(e => F.fail(BaboonIssue.of(IOIssue.CantReadInput(lockfilePath.toString, e))))
_ <- compareSigs(latestOf, currentSigs, existingSigs, options.lockfileEnforcement)
// Reached only when enforcement passed (or was None): force rewrites, create-only leaves untouched.
// Signatures from a different hashing scheme are incomparable: a scheme
// mismatch is a compiler upgrade, not model drift. Skip enforcement for
// that (one) run and rewrite the lockfile with current-scheme signatures
// even under CreateOnly, so the lock resumes protecting from the next run.
schemeMatches = existingSigs.scheme == currentSigs.scheme
_ <- F.when(schemeMatches)(compareSigs(latestOf, currentSigs, existingSigs, options.lockfileEnforcement))
// Reached only when enforcement passed (or was None/skipped): force rewrites,
// create-only leaves untouched unless a scheme migration is required.
_ <- options.lockfileUpdate match {
case LockfileUpdate.Force => writeCurrent(lockfilePath)
case LockfileUpdate.CreateOnly => F.unit
case LockfileUpdate.Force => writeCurrent(lockfilePath)
case LockfileUpdate.CreateOnly if !schemeMatches => writeCurrent(lockfilePath)
case LockfileUpdate.CreateOnly => F.unit
}
} yield {}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class JvmBaboonCompilerTest extends AnyWordSpec with Matchers {
Map.empty,
List.empty,
)
val evolution = BaboonEvolution(pkg, version, Map.empty, Map.empty, Map.empty)
val evolution = BaboonEvolution(pkg, version, Map.empty, Map.empty, Map.empty, Map.empty)
val lineage = BaboonLineage(pkg, NEMap.from(Map(version -> domain)).getOrElse(throw new IllegalStateException("Empty lineage")), evolution)
BaboonFamily(NEMap.from(Map(pkg -> lineage)).getOrElse(throw new IllegalStateException("Empty family")), BaboonFamilyCache.empty)
}
Expand Down
Loading
Loading