Guides

Parent/child chains and .Under

Typed parents, the anchor rule, ancestors, and jumping back with Under.

A child blueprint takes its parent

A review needs to belong to the pull request this test created. Looking up the most recent pull request becomes unreliable once the test adds siblings. A child blueprint receives its parent directly, keeping the relationship clear:

Blueprints.cs
public static partial class Blueprints
{
    public static readonly ChildBlueprint<
        ZeeqSeed,
        PullRequestRecord,
        CodeReviewRecord
    > Review = Blueprint.ChildOf<ZeeqSeed, PullRequestRecord, CodeReviewRecord>(
        (pr, ctx) =>
            new()
            {
                Id = ctx.NewId("review"),
                OrganizationId = pr.OrganizationId,
                PullRequestRecordId = pr.Id,
                RepositoryId = ctx.Ancestor<CodeRepository>().Id, // grandparent
                Status = CodeReviewStatus.Pending,
                CreatedAtUtc = ctx.Now,
            }
    );

    public static readonly ChildBlueprint<
        ZeeqSeed,
        CodeReviewRecord,
        ActiveCodeReviewLock
    > ActiveLock = Blueprint.ChildOf<
        ZeeqSeed,
        CodeReviewRecord,
        ActiveCodeReviewLock
    >(
        (review, ctx) =>
            new()
            {
                OrganizationId = review.OrganizationId,
                RepositoryId = review.RepositoryId,
                PullRequestRecordId = review.PullRequestRecordId,
                CodeReviewRecordId = review.Id,
                AcquiredAtUtc = ctx.Now,
                ExpiresAtUtc = ctx.FromNow(TimeSpan.FromMinutes(5)),
            }
    );
}

The typed parent is the lambda's first argument. Reach further up with ctx.Ancestor<T>(), which walks the recorded parent links and throws a clear error if no ancestor of that type exists.

Every Add re-anchors

The builder always has a current anchor: the seed, at the start, then the output of the latest Add. A child Add attaches to the anchor, so a chain reads top to bottom.

await TestBiome
    .Start(out _)
    .AddOne(Blueprints.Repository, out var repo)
    .AddOne(Blueprints.PullRequest, out var pr) // parent: repo
    .AddOne(Blueprints.Review, out var review) // parent: pr
    .AddOne(Blueprints.ActiveLock, out var activeLock) // parent: review
    .BuildAsync();
The anchor type lets the compiler check the next relationship. After a repository, you can add a pull request but not a review. Generated child graft methods accept only their parent's array anchor, so IntelliSense offers the matching child methods.

.Under: siblings and other branches

A chain follows one branch at a time. To add a sibling or start another branch, return to a parent you already built with .Under(x). It adds no entities; the next Add attaches to x.

Siblings
await TestBiome
    .Start(out _)
    .AddCodeRepository(out var repo)
    .AddPullRequest(out var open)
    .Under(repo) // back to the repository
    .AddPullRequest(out var closed, PullRequests.Closed)
    .AddCodeReview(out var review) // reviews `closed`
    .BuildAsync();

.Under accepts a single entity or an array: .Under(repos) fans out across all of them.

Branches
await TestBiome
    .Start(out _)
    .AddCodeRepository(out var repo)
    .AddPullRequest(out var open) // #1
    .AddCodeReview(out var review) // reviews open
    .Under(repo)
    .AddPullRequest(out var closed, PullRequests.Closed) // #2, sibling of `open`
    .Under(open)
    .AddCodeReview(out var otherReview) // a second review of `open`
    .BuildAsync();

.Under takes Add outputs and the seed rows you listed in SeedEntities (for example .Under(seed.Owner)). A hand-built object, or an untracked part of an aggregate, can't be a parent, and Biome throws ArgumentException naming the offending index.

Anchors and sibling blueprints

Siblings that share a parent are separate branches. The library's ProjectManagement tests attach checklist items and comments to the same cards by jumping back with .Under(cards):

ProjectManagementTests.cs
await biome
    .AddWorkspaces(out var workspaces, 2)
    .AddProjects(out _, 2)
    .AddBoards(out _, 1)
    .AddColumns(out var columns, 2)
    .AddCards(out var cards, 3)
    .AddChecklistItems(out var checklist, 4)
    // Comments attach to cards, so jump back from
    // checklist leaves to the saved card output.
    .Under(cards)
    .AddComments(out var comments, 2)
    .BuildAsync();
Capture an entity with out var x when you intend to .Under(x) later. Keep the starting builder (var biome = TestBiome.Start(...)) when you also want biome.All<T>() and biome.Last<T>() to inspect entities a helper created.

Errors you may see

MessageCause
A singular child AddOne requires exactly one parentThe previous step produced several (or zero) parents. Use the plural Add or .Under(repos[0]).
No ancestor of type X exists…A blueprint walks up to X, but the chain skipped X.
Parent at index N … is not owned by this biome session.Under was given an object Biome didn't build or select as seed.
This biome session is faulted because an earlier Add failedAn earlier Add threw. Fix that error; the session cannot continue.
An Add that fails may have created only part of its step. Biome marks that session faulted instead of allowing more setup on an incomplete graph. Look for the first exception, fix its cause, and start a new session.