// Acceptance for P2.3 (long-lived-container allocator rule) — the repo // OUTLIVES the transient `context.allocator` of any single call, so its // `List` backing stores must grow through the allocator CAPTURED at // construction, not whatever `context.allocator` happens to be at append // time. // // Deterministic proof: build the repo inside a `push Context` scope whose // allocator is a tracked GPA, then let that scope END (context.allocator // reverts to the default). EVERY subsequent repo mutation must still flow // through the captured GPA — observed as a rising `gpa.alloc_count`. If a // growth point wrongly used `context.allocator`, the count would not move // (the default allocator is a different, untracked one). #import "modules/std.sx"; process :: #import "modules/std/process.sx"; #import "../src/domain/platform.sx"; #import "../src/domain/app.sx"; #import "../src/domain/release.sx"; #import "../src/domain/artifact.sx"; #import "../src/domain/channel.sx"; #import "../src/domain/audit.sx"; #import "../src/repo/repo.sx"; main :: () -> i32 { gpa := GPA.init(); // Construct the repo under the tracked allocator, then leave the scope. repo : Repo = .{}; push Context.{ allocator = xx gpa, data = null } { repo = Repo.init(); // own_allocator := this gpa } // We are now back on the default context allocator. Touch all five // owned lists; each first append allocates its backing store, which // MUST come from the captured gpa (not the current context allocator). before := gpa.alloc_count; repo.create_app(App.{ id = "a1", slug = "acme", display_name = "Acme", owner = "u", created_at = 1, updated_at = 1 }); repo.create_release(Release.{ id = "r1", app_id = "a1", version = "1.0.0", build = 1, channel = "stable", notes = "", created_by = "ci", created_at = 1, published_at = 0 }); repo.create_artifact(Artifact.{ id = "art1", app_id = "a1", release_id = "r1", platform = .android_apk, filename = "a.apk", content_type = "x", size_bytes = 1, sha256 = "deadbeef", storage_key = "k", metadata = "", validation_status = .pending }); repo.create_channel(Channel.{ app_id = "a1", name = "stable", current_release_id = "r1" }); repo.create_audit_event(AuditEvent.{ id = "e1", actor = "u", action = "publish", target_type = "release", target_id = "r1", metadata = "", created_at = 1 }); grew := gpa.alloc_count - before; // Five distinct lists each allocated their first backing store through // the captured allocator -> at least five allocations attributed to it. process.assert(grew >= 5, "every owned list must grow through the captured allocator"); // And the data is intact after the construction scope ended. process.assert(repo.apps.len == 1, "app retained"); process.assert(repo.releases.len == 1, "release retained"); process.assert(repo.artifacts.len == 1, "artifact retained"); process.assert(repo.channels.len == 1, "channel retained"); process.assert(repo.audit_events.len == 1, "audit event retained"); ag := repo.find_app_by_slug("acme"); process.assert(ag != null, "app readable after scope end"); print(" repo grew through its captured allocator ({} allocs), data intact\n", grew); print("repo_owns_allocator: ALL CASES PASS\n"); return 0; }