lib/choir/src/product/revision/store.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const simd = @import("simd");
3 const revision = @import("root.zig");
4 const record = revision.record;
5 const receipt = revision.receipt;
6
7 const Bytes = simd.ScalableTag(u8);
8
9 pub const Event = struct {
10 store_epoch: u64,
11 counter: u64,
12 parent: ?ParentEvent,
13 };
14
15 pub const ParentEvent = struct { store_epoch: u64, counter: u64 };
16
17 pub const Limits = struct {
18 revisions: u32,
19 kinds: u32,
20 builders: u32,
21 compiler_manifests: u32,
22 record_bytes: u32,
23 gate_scratch_bytes: u32,
24 candidate_count: u32,
25 screening_bytes: u32,
26 };
27
28 /// The compiler manifest is owner-interned and arrives beside the record, since
29 /// no record carries the compiler that produced it.
30 pub const GateInput = struct {
31 exact: record.Exact,
32 compiler_manifest: []const u8,
33 dependencies: []const record.Dependency,
34 };
35
36 pub const Gate = struct {
37 identity: record.Version,
38 definition: []const u8,
39 scratch_bytes: u32,
40 run: *const fn (GateInput, []u8) anyerror!record.EntityCounts,
41 };
42
43 pub const KindContract = struct {
44 identity: record.Version,
45 schema: Gate,
46 gates: []const Gate,
47 };
48
49 pub const Dependency = struct {
50 role: []const u8,
51 revision: *const Revision,
52 };
53
54 pub const Request = struct {
55 kind: *const Kind,
56 address: record.Address,
57 inputs: record.Inputs,
58 dependencies: []const Dependency = &.{},
59 parent: ?*const Revision = null,
60 expected_image: ?[]const u8 = null,
61 };
62
63 pub const View = struct {
64 exact: record.Exact,
65 compiler_manifest: []const u8,
66 semantic_record: []const u8,
67 digest: [32]u8,
68 bucket: u64,
69 event: Event,
70 entities: record.EntityCounts,
71 work: receipt.WorkReceiptV1,
72 gate_manifest: []const u8,
73 };
74
75 /// Exact transport metadata has no publication, gate, entity or reuse authority.
76 /// Retaining this handle keeps the store's immutable record allocation alive.
77 pub const Record = opaque {
78 pub fn retain(self: *const Record) !*const Record {
79 try recordData(self).owner.retain();
80 return self;
81 }
82
83 pub fn release(self: *const Record) void {
84 recordData(self).owner.release();
85 }
86
87 pub fn bytes(self: *const Record) []const u8 {
88 return recordData(self).semantic;
89 }
90
91 pub fn view(self: *const Record) record.Exact {
92 return recordData(self).exact;
93 }
94
95 pub fn address(self: *const Record) record.Address {
96 return record.decodeAddress(self.view().address) catch unreachable;
97 }
98
99 pub fn inputs(self: *const Record) record.InputView {
100 return record.decodeInputs(self.view().inputs) catch unreachable;
101 }
102
103 /// The owner-interned bytes this record was admitted under.
104 pub fn compilerManifest(self: *const Record) []const u8 {
105 return recordData(self).compiler_manifest;
106 }
107
108 pub fn eql(self: *const Record, other: *const Record) bool {
109 return sameIdentity(recordData(self), recordData(other));
110 }
111
112 pub fn bucket(self: *const Record) u64 {
113 const value = recordData(self).owner.digest_fn(self.view().image);
114 return std.mem.readInt(u64, value[0..8], .little);
115 }
116 };
117
118 /// This reference owns one retain on the revision's storage allocation.
119 pub const Entity = struct {
120 revision: *const Revision,
121 namespace: record.Namespace,
122 ordinal: u32,
123
124 pub fn release(self: Entity) void {
125 self.revision.release();
126 }
127
128 pub fn eql(self: Entity, other: Entity) bool {
129 return self.namespace == other.namespace and self.ordinal == other.ordinal and
130 self.revision.eql(other.revision);
131 }
132 };
133
134 pub const Revision = opaque {
135 /// The returned reference is borrowed from this sealed revision. One handle
136 /// stands for one record under one compiler, so a revision whose bytes a
137 /// foreign compiler also produced still answers under its own.
138 pub fn metadata(self: *const Revision) *const Record {
139 const value = entry(self);
140 return @ptrCast(value.owner.findRecordUnder(value.semantic, value.compiler_manifest).?);
141 }
142
143 pub fn retain(self: *const Revision) !*const Revision {
144 try entry(self).owner.retain();
145 return self;
146 }
147
148 pub fn release(self: *const Revision) void {
149 entry(self).owner.release();
150 }
151
152 pub fn view(self: *const Revision) View {
153 return entry(self).view();
154 }
155
156 pub fn eql(self: *const Revision, other: *const Revision) bool {
157 return sameIdentity(&entry(self).metadata, &entry(other).metadata);
158 }
159
160 pub fn address(self: *const Revision) record.Address {
161 return record.decodeAddress(entry(self).exact.address) catch unreachable;
162 }
163
164 pub fn inputs(self: *const Revision) record.InputView {
165 return record.decodeInputs(entry(self).exact.inputs) catch unreachable;
166 }
167
168 pub fn requireDependency(
169 self: *const Revision,
170 role: []const u8,
171 input: *const Revision,
172 ) error{UndeclaredProductInput}!void {
173 const value = entry(self);
174 const declared = self.inputs().dependencies;
175 std.debug.assert(value.references.len == declared.count);
176 var dependencies = declared.iterator();
177 for (value.references) |reference| {
178 const dependency = (dependencies.next() catch unreachable).?;
179 if (!std.mem.eql(u8, dependency.role, role)) continue;
180 if (simd.equal(Bytes, reference, input.view().semantic_record)) return;
181 }
182 return error.UndeclaredProductInput;
183 }
184
185 pub fn entity(self: *const Revision, namespace: record.Namespace, ordinal: u32) !Entity {
186 if (ordinal >= entry(self).entities[@backingInt(namespace)]) return error.InvalidEntity;
187 return .{ .revision = try self.retain(), .namespace = namespace, .ordinal = ordinal };
188 }
189
190 pub fn requireGates(self: *const Revision, required: []const record.Version) !void {
191 const kind = entry(self).kind;
192 for (required) |identity| {
193 if (kind.schema.identity.eql(identity)) continue;
194 for (kind.gates) |gate| {
195 if (gate.identity.eql(identity)) break;
196 } else return error.MissingGateEvidence;
197 }
198 }
199 };
200
201 pub const Kind = opaque {};
202
203 pub const Store = opaque {
204 pub fn create(allocator: std.mem.Allocator, limits: Limits) !*Store {
205 return Storage.create(allocator, limits);
206 }
207
208 pub fn release(self: *Store) void {
209 storage(self).release();
210 }
211
212 /// Copies and structurally validates restored metadata under explicit bounds.
213 /// A matching record is interned, but this operation never creates a Revision.
214 /// No record names its compiler, so the caller states which one produced these
215 /// bytes and the store interns it exactly as it interns a draft's.
216 pub fn importRecord(
217 self: *Store,
218 bytes: []const u8,
219 compiler_manifest: []const u8,
220 bounds: record.ClosureBounds,
221 ) !*const Record {
222 return storage(self).importRecord(bytes, compiler_manifest, bounds);
223 }
224
225 pub fn register(self: *Store, contract: KindContract) !*const Kind {
226 return storage(self).register(contract);
227 }
228
229 pub fn freeze(self: *Store) void {
230 std.debug.assert(!storage(self).frozen);
231 storage(self).frozen = true;
232 }
233
234 pub fn begin(self: *Store, request: Request, limits: receipt.Limits) !*Builder {
235 return Draft.create(storage(self), request, limits);
236 }
237
238 /// Candidates are borrowed until store release; lookup grants no reuse authority.
239 pub fn lookup(
240 self: *Store,
241 bucket_hint: u64,
242 output: []*const Revision,
243 ) []const *const Revision {
244 const state = storage(self);
245 const bound = @min(output.len, state.limits.candidate_count);
246 var count: usize = 0;
247 for (state.entries[0..state.count]) |candidate| {
248 if (count == bound) break;
249 if (candidate.bucket != bucket_hint) continue;
250 if (candidate.semantic.len > state.limits.screening_bytes) continue;
251 output[count] = @ptrCast(candidate);
252 count += 1;
253 }
254 return output[0..count];
255 }
256
257 pub fn publicationCount(self: *const Store) u32 {
258 const state: *const Storage = @ptrCast(@alignCast(self));
259 return state.count;
260 }
261 };
262
263 pub const State = enum { open, captured, gated, sealed, aborted };
264
265 pub const Builder = opaque {
266 /// The lease keeps unpublished working bytes alive independently of this builder.
267 pub fn acquireWorkspace(self: *Builder, region: WorkspaceRegion) !WorkspaceLease {
268 const state = draft(self);
269 if (state.state != .open) return error.InvalidBuilderState;
270 if (state.work.view().outcome != .running) return error.TerminalWorkOutcome;
271 return state.workspace.?.acquire(region);
272 }
273
274 /// A live lease binds producer storage to the request that reserved it.
275 pub fn ownsWorkspace(self: *Builder, lease: WorkspaceLease) bool {
276 const state = draft(self);
277 std.debug.assert(state.state == .open);
278 return state.workspace == workspaceData(lease.owner);
279 }
280
281 pub fn inputs(self: *Builder) record.InputView {
282 const state = draft(self);
283 std.debug.assert(state.state == .open);
284 return record.decodeInputs(state.inputs) catch unreachable;
285 }
286
287 /// The owner-interned bytes this draft was admitted under.
288 pub fn compilerManifest(self: *Builder) []const u8 {
289 const state = draft(self);
290 std.debug.assert(state.state == .open);
291 return state.compiler_manifest;
292 }
293
294 pub fn accounting(self: *Builder) *receipt.AccountingV1 {
295 std.debug.assert(draft(self).state == .open);
296 return draft(self).work;
297 }
298
299 pub fn requireDependency(self: *Builder, role: []const u8, input: *const Revision) !void {
300 const state = draft(self);
301 errdefer state.work.fail(.rejected);
302 if (state.state != .open) return error.InvalidBuilderState;
303 if (state.work.view().outcome != .running) return error.TerminalWorkOutcome;
304 for (state.dependencies) |dependency| {
305 if (!std.mem.eql(u8, dependency.role, role)) continue;
306 if (simd.equal(Bytes, dependency.exact, input.view().semantic_record)) return;
307 }
308 return error.UndeclaredProductInput;
309 }
310
311 pub fn capture(self: *Builder, bytes: []const u8, observations: []const record.Fact) !void {
312 errdefer draft(self).work.fail(.rejected);
313 try draft(self).capture(bytes, observations);
314 }
315
316 pub fn seal(self: *Builder, kind: *const Kind) !*const Revision {
317 errdefer draft(self).work.fail(.rejected);
318 return draft(self).seal(kind);
319 }
320
321 /// A mismatch is screened before compiler obligations. Exhaustion is terminal.
322 pub fn admitReuse(self: *Builder, candidate: *const Revision) !?Reuse {
323 errdefer draft(self).work.fail(.rejected);
324 return draft(self).admitReuse(candidate);
325 }
326
327 /// A matching restored record only binds a comparison against fresh cold work.
328 /// It cannot authorize replay; capture must reproduce its complete image.
329 /// Restored bytes do not name their compiler, so the caller states which one
330 /// produced them and a draft admitted under another compiler declines.
331 pub fn screenRestored(
332 self: *Builder,
333 bytes: []const u8,
334 compiler_manifest: []const u8,
335 ) !bool {
336 errdefer draft(self).work.fail(.rejected);
337 return draft(self).screenRestored(bytes, compiler_manifest);
338 }
339
340 pub fn abort(self: *Builder, outcome: receipt.Outcome) ?Failure {
341 const state = draft(self);
342 std.debug.assert(state.state != .sealed);
343 std.debug.assert(state.state != .aborted);
344 state.work.fail(outcome);
345 const failure = Failure.capture(state) catch null;
346 state.state = .aborted;
347 state.destroy();
348 return failure;
349 }
350 };
351
352 pub const WorkspaceRegion = enum { producer, scratch };
353 pub const Workspace = opaque {};
354
355 /// Exclusive mutable byte storage; it never becomes a revision view.
356 pub const WorkspaceLease = struct {
357 owner: *Workspace,
358 region: WorkspaceRegion,
359
360 pub fn bytes(self: WorkspaceLease) []u8 {
361 const state = workspaceData(self.owner);
362 std.debug.assert(state.leased[@backingInt(self.region)]);
363 const boundary = state.bytes.len - state.scratch_bytes;
364 return switch (self.region) {
365 .producer => state.bytes[0..boundary],
366 .scratch => state.bytes[boundary..],
367 };
368 }
369
370 pub fn release(self: WorkspaceLease) void {
371 const state = workspaceData(self.owner);
372 const active = &state.leased[@backingInt(self.region)];
373 std.debug.assert(active.*);
374 active.* = false;
375 state.release();
376 }
377 };
378
379 const WorkspaceData = struct {
380 allocator: std.mem.Allocator,
381 bytes: []u8,
382 scratch_bytes: u32,
383 references: u32 = 1,
384 leased: [2]bool = @splat(false),
385
386 fn acquire(self: *WorkspaceData, region: WorkspaceRegion) !WorkspaceLease {
387 const active = &self.leased[@backingInt(region)];
388 if (active.*) return error.WorkspaceAlreadyLeased;
389 std.debug.assert(self.references < 3);
390 self.references += 1;
391 active.* = true;
392 return .{ .owner = @ptrCast(self), .region = region };
393 }
394
395 fn release(self: *WorkspaceData) void {
396 std.debug.assert(self.references > 0);
397 self.references -= 1;
398 if (self.references != 0) return;
399 std.debug.assert(!self.leased[0]);
400 std.debug.assert(!self.leased[1]);
401 const allocator = self.allocator;
402 releaseUntouched(allocator, self.bytes);
403 allocator.destroy(self);
404 }
405 };
406
407 fn workspaceData(handle: *Workspace) *WorkspaceData {
408 return @ptrCast(@alignCast(handle));
409 }
410
411 pub const Reuse = struct {
412 allocator: std.mem.Allocator,
413 revision: *const Revision,
414 work: receipt.WorkReceiptV1,
415
416 pub fn deinit(self: *Reuse) void {
417 self.revision.release();
418 self.allocator.free(self.work.events);
419 self.* = undefined;
420 }
421 };
422
423 pub const Failure = struct {
424 allocator: std.mem.Allocator,
425 address: []const u8,
426 inputs: []const u8,
427 work: receipt.WorkReceiptV1,
428
429 fn capture(state: *const Draft) !Failure {
430 const allocator = state.owner.allocator;
431 const address = try allocator.dupe(u8, state.address);
432 errdefer allocator.free(address);
433 const inputs = try allocator.dupe(u8, state.inputs);
434 errdefer allocator.free(inputs);
435 var work = state.work.view();
436 work.events = try allocator.dupe(receipt.Event, work.events);
437 return .{ .allocator = allocator, .address = address, .inputs = inputs, .work = work };
438 }
439
440 pub fn deinit(self: *Failure) void {
441 self.allocator.free(self.address);
442 self.allocator.free(self.inputs);
443 self.allocator.free(self.work.events);
444 self.* = undefined;
445 }
446 };
447
448 var next_epoch = std.atomic.Value(u64).init(1);
449
450 const Storage = struct {
451 allocator: std.mem.Allocator,
452 limits: Limits,
453 epoch: u64,
454 references: u64 = 1,
455 entries: []*Entry,
456 count: u32 = 0,
457 records: []*RecordData,
458 record_count: u32 = 0,
459 kinds: []*KindData,
460 kind_count: u32 = 0,
461 manifests: [][]u8,
462 manifest_count: u32 = 0,
463 builder_count: u32 = 0,
464 event_counter: u64 = 0,
465 frozen: bool = false,
466 digest_fn: *const fn ([]const u8) [32]u8 = digest,
467
468 fn create(allocator: std.mem.Allocator, limits: Limits) !*Store {
469 if (limits.revisions == 0 or limits.kinds == 0 or limits.builders == 0 or
470 limits.record_bytes == 0 or limits.candidate_count == 0 or
471 limits.compiler_manifests == 0)
472 {
473 return error.InvalidStoreLimits;
474 }
475 const self = try allocator.create(Storage);
476 errdefer allocator.destroy(self);
477 const entries = try allocator.alloc(*Entry, limits.revisions);
478 errdefer allocator.free(entries);
479 const records = try allocator.alloc(*RecordData, limits.revisions);
480 errdefer allocator.free(records);
481 const kinds = try allocator.alloc(*KindData, limits.kinds);
482 errdefer allocator.free(kinds);
483 const manifests = try allocator.alloc([]u8, limits.compiler_manifests);
484 errdefer allocator.free(manifests);
485 self.* = .{
486 .allocator = allocator,
487 .limits = limits,
488 .epoch = try allocateEpoch(),
489 .entries = entries,
490 .records = records,
491 .kinds = kinds,
492 .manifests = manifests,
493 };
494 return @ptrCast(self);
495 }
496
497 /// Returns this store's copy of the given compiler manifest, admitting a new
498 /// one only while the declared capacity has room. Interned bytes live until
499 /// the store dies, because sealed records reference them and never re-copy.
500 fn internManifest(self: *Storage, bytes: []const u8) ![]const u8 {
501 std.debug.assert(self.manifest_count <= self.manifests.len);
502 if (bytes.len == 0) return error.MissingCompilerManifest;
503 if (bytes.len > self.limits.record_bytes) return error.RecordLimit;
504 for (self.manifests[0..self.manifest_count]) |existing| {
505 if (simd.equal(Bytes, existing, bytes)) return existing;
506 }
507 if (self.manifest_count == self.manifests.len) return error.CompilerManifestLimit;
508 const copy = try self.allocator.dupe(u8, bytes);
509 self.manifests[self.manifest_count] = copy;
510 self.manifest_count += 1;
511 return copy;
512 }
513
514 fn retain(self: *Storage) !void {
515 self.references = std.math.add(u64, self.references, 1) catch {
516 return error.ReferenceOverflow;
517 };
518 }
519
520 fn release(self: *Storage) void {
521 std.debug.assert(self.references > 0);
522 self.references -= 1;
523 if (self.references != 0) return;
524 std.debug.assert(self.builder_count == 0);
525 const allocator = self.allocator;
526 for (self.entries[0..self.count]) |item| item.destroy();
527 for (self.records[0..self.record_count]) |item| item.destroy();
528 for (self.kinds[0..self.kind_count]) |kind| kind.destroy();
529 for (self.manifests[0..self.manifest_count]) |manifest| allocator.free(manifest);
530 allocator.free(self.entries);
531 allocator.free(self.records);
532 allocator.free(self.kinds);
533 allocator.free(self.manifests);
534 allocator.destroy(self);
535 }
536
537 /// Finds equal bytes regardless of compiler, which is what sharing one
538 /// immutable buffer needs. Identity asks `findRecordUnder` instead.
539 fn findRecord(self: *Storage, bytes: []const u8) ?*const RecordData {
540 for (self.records[0..self.record_count]) |item| {
541 if (simd.equal(Bytes, item.semantic, bytes)) return item;
542 }
543 for (self.entries[0..self.count]) |item| {
544 if (simd.equal(Bytes, item.semantic, bytes)) return &item.metadata;
545 }
546 return null;
547 }
548
549 /// Every record this store holds names a manifest this store interned, so
550 /// equal manifests are one address and the screen costs a pointer compare.
551 fn findRecordUnder(
552 self: *Storage,
553 bytes: []const u8,
554 manifest: []const u8,
555 ) ?*const RecordData {
556 for (self.records[0..self.record_count]) |item| {
557 if (item.compiler_manifest.ptr != manifest.ptr) continue;
558 if (simd.equal(Bytes, item.semantic, bytes)) return item;
559 }
560 for (self.entries[0..self.count]) |item| {
561 if (item.metadata.compiler_manifest.ptr != manifest.ptr) continue;
562 if (simd.equal(Bytes, item.semantic, bytes)) return &item.metadata;
563 }
564 return null;
565 }
566
567 fn importRecord(
568 self: *Storage,
569 bytes: []const u8,
570 compiler_manifest: []const u8,
571 bounds: record.ClosureBounds,
572 ) !*const Record {
573 if (bytes.len > self.limits.record_bytes) return error.RecordLimit;
574 if (bytes.len > bounds.bytes) return error.RecordLimit;
575 const interned = try self.internManifest(compiler_manifest);
576 const copy = try self.allocator.dupe(u8, bytes);
577 errdefer self.allocator.free(copy);
578 try record.validateClosure(copy, bounds);
579 if (self.findRecordUnder(copy, interned)) |existing| {
580 try self.retain();
581 self.allocator.free(copy);
582 return @ptrCast(existing);
583 }
584 if (self.record_count == self.records.len) return error.RecordCountLimit;
585 const metadata = try self.allocator.create(RecordData);
586 errdefer self.allocator.destroy(metadata);
587 metadata.* = .{
588 .owner = self,
589 .compiler_manifest = interned,
590 .semantic = copy,
591 .exact = record.decodeExact(copy) catch unreachable,
592 };
593 try self.retain();
594 self.records[self.record_count] = metadata;
595 self.record_count += 1;
596 return @ptrCast(metadata);
597 }
598
599 fn register(self: *Storage, contract: KindContract) !*const Kind {
600 if (self.frozen) return error.RegistrationFrozen;
601 if (contract.schema.scratch_bytes > self.limits.gate_scratch_bytes) {
602 return error.GateScratchLimit;
603 }
604 for (contract.gates) |gate| {
605 if (gate.scratch_bytes > self.limits.gate_scratch_bytes) return error.GateScratchLimit;
606 }
607 const manifest = try encodeManifest(self.allocator, contract);
608 errdefer self.allocator.free(manifest);
609 if (manifest.len > self.limits.record_bytes) return error.RecordLimit;
610 for (self.kinds[0..self.kind_count]) |existing| {
611 if (!existing.identity.eql(contract.identity)) continue;
612 if (!std.mem.eql(u8, existing.manifest, manifest) or
613 !existing.sameCallbacks(contract)) return error.KindContractMismatch;
614 self.allocator.free(manifest);
615 return @ptrCast(existing);
616 }
617 if (self.kind_count == self.kinds.len) return error.KindLimit;
618 const kind = try KindData.create(self, contract, manifest);
619 self.kinds[self.kind_count] = kind;
620 self.kind_count += 1;
621 return @ptrCast(kind);
622 }
623 };
624
625 const KindData = struct {
626 owner: *Storage,
627 identity: record.Version,
628 manifest: []u8,
629 schema: Gate,
630 gates: []Gate,
631
632 fn create(owner: *Storage, contract: KindContract, manifest: []u8) !*KindData {
633 const allocator = owner.allocator;
634 const self = try allocator.create(KindData);
635 errdefer allocator.destroy(self);
636 const name = try allocator.dupe(u8, contract.identity.name);
637 errdefer allocator.free(name);
638 const schema = try cloneGate(allocator, contract.schema);
639 errdefer freeGate(allocator, schema);
640 const gates = try allocator.alloc(Gate, contract.gates.len);
641 errdefer allocator.free(gates);
642 var count: usize = 0;
643 errdefer for (gates[0..count]) |gate| freeGate(allocator, gate);
644 for (contract.gates, gates) |gate, *copy| {
645 copy.* = try cloneGate(allocator, gate);
646 count += 1;
647 }
648 self.* = .{
649 .owner = owner,
650 .identity = .{ .name = name, .version = contract.identity.version },
651 .manifest = manifest,
652 .schema = schema,
653 .gates = gates,
654 };
655 return self;
656 }
657
658 fn sameCallbacks(self: *const KindData, contract: KindContract) bool {
659 if (self.schema.run != contract.schema.run) return false;
660 if (self.gates.len != contract.gates.len) return false;
661 for (self.gates, contract.gates) |first, second| {
662 if (first.run != second.run) return false;
663 }
664 return true;
665 }
666
667 fn destroy(self: *KindData) void {
668 const allocator = self.owner.allocator;
669 allocator.free(self.identity.name);
670 allocator.free(self.manifest);
671 freeGate(allocator, self.schema);
672 for (self.gates) |gate| freeGate(allocator, gate);
673 allocator.free(self.gates);
674 allocator.destroy(self);
675 }
676 };
677
678 const Entry = struct {
679 owner: *Storage,
680 metadata: RecordData,
681 compiler_manifest: []const u8,
682 semantic: []const u8,
683 allocation: ?[]u8,
684 references: []const []const u8,
685 exact: record.Exact,
686 digest_value: [32]u8,
687 bucket: u64,
688 event: Event,
689 entities: record.EntityCounts,
690 kind: *const KindData,
691 work: receipt.WorkReceiptV1,
692
693 fn view(self: *const Entry) View {
694 return .{
695 .exact = self.exact,
696 .compiler_manifest = self.compiler_manifest,
697 .semantic_record = self.semantic,
698 .digest = self.digest_value,
699 .bucket = self.bucket,
700 .event = self.event,
701 .entities = self.entities,
702 .work = self.work,
703 .gate_manifest = self.kind.manifest,
704 };
705 }
706
707 fn destroy(self: *Entry) void {
708 const allocator = self.owner.allocator;
709 if (self.allocation) |bytes| releaseUntouched(allocator, bytes);
710 allocator.free(self.references);
711 allocator.free(self.work.events);
712 allocator.destroy(self);
713 }
714 };
715
716 const Draft = struct {
717 owner: *Storage,
718 kind: *const KindData,
719 compiler_manifest: []const u8,
720 state: State = .open,
721 address: []u8,
722 inputs: []u8,
723 dependencies: []record.Dependency,
724 revisions: []*const Revision,
725 facts: []record.Fact,
726 expected_image: ?[]u8,
727 image: ?[]u8 = null,
728 output: ?[]u8 = null,
729 workspace: ?*WorkspaceData = null,
730 parent: ?ParentEvent,
731 work: *receipt.AccountingV1,
732 entities: record.EntityCounts = @splat(0),
733 screened_candidates: u32 = 0,
734 screened_bytes: u64 = 0,
735 restored: bool = false,
736
737 fn create(owner: *Storage, request: Request, limits: receipt.Limits) !*Builder {
738 if (!owner.frozen) return error.RegistrationOpen;
739 const kind = kindData(request.kind);
740 if (kind.owner != owner) return error.ForeignKind;
741 if (owner.builder_count == owner.limits.builders) return error.BuilderLimit;
742 const input_bound = try inspectRequest(request, owner.limits.record_bytes);
743 const input_capacity = try requestCapacity(request, input_bound);
744 const work = try receipt.AccountingV1.create(
745 owner.allocator,
746 limits,
747 request.inputs.pipeline,
748 );
749 errdefer work.destroy();
750 try transport(work, owner.limits);
751 const output = try reserveOutput(owner, work);
752 errdefer releaseUntouched(owner.allocator, output);
753 const workspace = try reserveWorkspace(owner, work);
754 errdefer workspace.release();
755 const token = try work.begin(.input, .{
756 .identity = .{ .name = "revision-input-v1", .version = 1 },
757 .work = .{ .input_bytes = input_bound, .allocation_capacity = input_capacity },
758 .workspace = input_capacity,
759 .retained_storage = input_capacity,
760 });
761 const self = try copyRequest(owner, kind, request, work);
762 errdefer self.freeRequest();
763 try work.finish(token, .success, .{
764 .work = .{ .input_bytes = self.address.len + self.inputs.len },
765 });
766 try owner.retain();
767 owner.builder_count += 1;
768 self.output = output;
769 self.workspace = workspace;
770 return @ptrCast(self);
771 }
772
773 fn copyRequest(
774 owner: *Storage,
775 kind: *const KindData,
776 request: Request,
777 work: *receipt.AccountingV1,
778 ) !*Draft {
779 const allocator = owner.allocator;
780 const compiler_manifest = try owner.internManifest(request.inputs.compiler_manifest);
781 const self = try allocator.create(Draft);
782 errdefer allocator.destroy(self);
783 const address = try record.encodeAddress(allocator, request.address);
784 errdefer allocator.free(address);
785 const revisions = try allocator.alloc(*const Revision, request.dependencies.len);
786 errdefer allocator.free(revisions);
787 const dependencies = try retainDependencies(allocator, request.dependencies, revisions);
788 errdefer freeDependencies(allocator, dependencies, revisions);
789 const inputs = try record.encodeInputs(
790 allocator,
791 request.inputs,
792 dependencies,
793 kind.manifest,
794 );
795 errdefer allocator.free(inputs);
796 try recordSize(owner.limits.record_bytes, address.len, inputs.len, 0);
797 const facts = try copyFacts(allocator, request.inputs.facts);
798 errdefer freeFacts(allocator, facts);
799 const expected = if (request.expected_image) |bytes|
800 try allocator.dupe(u8, bytes)
801 else
802 null;
803 self.* = .{
804 .owner = owner,
805 .kind = kind,
806 .compiler_manifest = compiler_manifest,
807 .address = address,
808 .inputs = inputs,
809 .dependencies = dependencies,
810 .revisions = revisions,
811 .facts = facts,
812 .expected_image = expected,
813 .parent = if (request.parent) |parent| .{
814 .store_epoch = parent.view().event.store_epoch,
815 .counter = parent.view().event.counter,
816 } else null,
817 .work = work,
818 };
819 return self;
820 }
821
822 fn capture(self: *Draft, bytes: []const u8, observations: []const record.Fact) !void {
823 if (self.state != .open) return error.InvalidBuilderState;
824 try self.work.producersComplete();
825 try recordSize(
826 self.owner.limits.record_bytes,
827 self.address.len,
828 self.inputs.len,
829 bytes.len,
830 );
831 const token = try self.work.begin(.capture, .{
832 .identity = .{ .name = "revision-capture-v1", .version = 1 },
833 .work = .{
834 .input_bytes = bytes.len,
835 .output_bytes = bytes.len,
836 .structural_visits = bytes.len,
837 .allocation_capacity = bytes.len,
838 },
839 .workspace = bytes.len,
840 .retained_storage = bytes.len,
841 });
842 errdefer self.work.finish(token, .rejected, .{}) catch {};
843 try record.validateObservations(self.facts, observations);
844 if (self.expected_image) |expected| {
845 if (!simd.equal(Bytes, bytes, expected)) return error.UnexpectedImage;
846 }
847 self.image = try self.owner.allocator.dupe(u8, bytes);
848 self.state = .captured;
849 try self.work.finish(token, .success, .{ .work = .{
850 .input_bytes = bytes.len,
851 .output_bytes = bytes.len,
852 } });
853 }
854
855 fn seal(self: *Draft, kind: *const Kind) !*const Revision {
856 if (self.state != .captured) return error.InvalidBuilderState;
857 if (kindData(kind) != self.kind) return error.KindContractMismatch;
858 try self.work.producersComplete();
859 self.entities = try self.runGate(self.kind.schema);
860 for (self.kind.gates) |gate| _ = try self.runGate(gate);
861 self.state = .gated;
862 return self.publish();
863 }
864
865 fn runGate(self: *Draft, gate: Gate) !record.EntityCounts {
866 const token = try self.work.begin(.gate, .{
867 .identity = gate.identity,
868 .work = .{
869 .input_bytes = self.inputs.len + self.image.?.len,
870 .structural_visits = self.image.?.len,
871 .allocation_capacity = gate.scratch_bytes,
872 },
873 .workspace = gate.scratch_bytes,
874 });
875 errdefer self.work.finish(token, .rejected, .{}) catch {};
876 const scratch = try self.workspace.?.acquire(.scratch);
877 defer scratch.release();
878 const counts = gate.run(.{
879 .exact = .{ .address = self.address, .inputs = self.inputs, .image = self.image.? },
880 .compiler_manifest = self.compiler_manifest,
881 .dependencies = self.dependencies,
882 }, scratch.bytes()[0..gate.scratch_bytes]) catch |err| {
883 if (err == error.WorkExhausted) self.work.fail(.exhausted);
884 return err;
885 };
886 try self.work.finish(token, .success, .{ .work = .{
887 .input_bytes = self.inputs.len + self.image.?.len,
888 } });
889 return counts;
890 }
891
892 fn publish(self: *Draft) !*const Revision {
893 const owner = self.owner;
894 if (owner.count == owner.entries.len) return error.StoreFull;
895 const event_counter = std.math.add(u64, owner.event_counter, 1) catch {
896 return error.GenerationOverflow;
897 };
898 if (owner.references == std.math.maxInt(u64)) return error.ReferenceOverflow;
899 const length = try record.exactSize(self.address.len, self.inputs.len, self.image.?.len);
900 const references = try capacityMul(self.dependencies.len, @sizeOf([]const u8));
901 const events = try capacityMul(self.work.view().events.len + 1, @sizeOf(receipt.Event));
902 const metadata = try capacityAdd(try capacityAdd(@sizeOf(Entry), references), events);
903 const token = try self.work.begin(.output, .{
904 .identity = .{ .name = "revision-output-v1", .version = 1 },
905 .work = .{
906 .input_bytes = length,
907 .output_bytes = length,
908 .structural_visits = length,
909 .allocation_capacity = metadata,
910 },
911 .workspace = try capacityAdd(length, metadata),
912 .retained_storage = metadata,
913 });
914 var output_open = true;
915 errdefer if (output_open) {
916 self.work.finish(token, .rejected, .{}) catch {};
917 };
918 const published = try self.prepareEntry(event_counter);
919 errdefer published.destroy();
920 try self.work.finish(token, .success, .{ .work = .{
921 .input_bytes = length,
922 .output_bytes = length,
923 } });
924 output_open = false;
925 try self.work.complete();
926 const event_buffer = published.work.events;
927 @memcpy(@constCast(event_buffer), self.work.view().events);
928 published.work = self.work.view();
929 published.work.events = event_buffer;
930 owner.references += 1;
931 owner.entries[owner.count] = published;
932 owner.count += 1;
933 owner.event_counter = event_counter;
934 self.state = .sealed;
935 self.destroy();
936 return @ptrCast(published);
937 }
938
939 fn prepareEntry(self: *Draft, counter: u64) !*Entry {
940 const owner = self.owner;
941 const allocator = owner.allocator;
942 const published = try allocator.create(Entry);
943 errdefer allocator.destroy(published);
944 const references = try allocator.alloc([]const u8, self.dependencies.len);
945 errdefer allocator.free(references);
946 const semantic = try record.encodeExactInto(self.output.?, .{
947 .address = self.address,
948 .inputs = self.inputs,
949 .image = self.image.?,
950 });
951 var work = self.work.view();
952 work.events = try allocator.dupe(receipt.Event, work.events);
953 const digest_value = owner.digest_fn(self.image.?);
954 published.* = .{
955 .owner = owner,
956 .metadata = undefined,
957 .compiler_manifest = self.compiler_manifest,
958 .semantic = semantic,
959 .allocation = self.output,
960 .references = references,
961 .exact = record.decodeExact(semantic) catch unreachable,
962 .digest_value = digest_value,
963 .bucket = std.mem.readInt(u64, digest_value[0..8], .little),
964 .event = .{ .store_epoch = owner.epoch, .counter = counter, .parent = self.parent },
965 .entities = self.entities,
966 .kind = self.kind,
967 .work = work,
968 };
969 self.output = null;
970 internImage(published);
971 self.referenceDependencies(published.exact.inputs, references);
972 published.metadata = .{
973 .owner = owner,
974 .compiler_manifest = self.compiler_manifest,
975 .semantic = published.semantic,
976 .exact = published.exact,
977 };
978 return published;
979 }
980
981 /// Producer bounds may depend on either exposed workspace region's capacity.
982 /// A different reservation needs its own cold trace before it can authorize reuse.
983 fn admitReuse(self: *Draft, candidate: *const Revision) !?Reuse {
984 if (self.restored) return error.RestoredCandidateRequiresExecution;
985 const prior = entry(candidate);
986 if (!try self.screen(prior.semantic.len)) return null;
987 if (!simd.equal(Bytes, prior.compiler_manifest, self.compiler_manifest)) return null;
988 if (!simd.equal(Bytes, prior.kind.manifest, self.kind.manifest)) return null;
989 if (!simd.equal(Bytes, prior.exact.address, self.address)) return null;
990 if (!self.eqlInputs(prior)) return null;
991 if (prior.work.limits.workspace != self.work.view().limits.workspace) return null;
992 if (prior.owner.limits.gate_scratch_bytes != self.owner.limits.gate_scratch_bytes) {
993 return null;
994 }
995 if (self.expected_image) |expected| {
996 if (!simd.equal(Bytes, expected, prior.exact.image)) return null;
997 }
998 try self.work.replay(prior.work, 4);
999 const allocator = self.owner.allocator;
1000 var work = self.work.view();
1001 work.events = try allocator.dupe(receipt.Event, work.events);
1002 errdefer allocator.free(work.events);
1003 const retained = try candidate.retain();
1004 errdefer retained.release();
1005 try self.work.complete();
1006 work.outcome = .success;
1007 const result = Reuse{
1008 .allocator = allocator,
1009 .revision = retained,
1010 .work = work,
1011 };
1012 self.state = .sealed;
1013 self.destroy();
1014 return result;
1015 }
1016
1017 fn eqlInputs(self: *const Draft, prior: *const Entry) bool {
1018 const first = prior.exact.inputs;
1019 const second = self.inputs;
1020 if (first.len != second.len) return false;
1021 const embedded = (record.decodeInputs(first) catch unreachable).dependencies;
1022 const requested = (record.decodeInputs(second) catch unreachable).dependencies;
1023 if (embedded.count != requested.count) return false;
1024 std.debug.assert(prior.references.len == embedded.count);
1025 var prior_dependencies = embedded.iterator();
1026 var draft_dependencies = requested.iterator();
1027 var offset: usize = 0;
1028 for (prior.references) |reference| {
1029 const left = (prior_dependencies.next() catch unreachable).?;
1030 const right = (draft_dependencies.next() catch unreachable).?;
1031 const start = @intFromPtr(left.exact.ptr) - @intFromPtr(first.ptr);
1032 if (start != @intFromPtr(right.exact.ptr) - @intFromPtr(second.ptr)) return false;
1033 if (left.exact.len != right.exact.len) return false;
1034 std.debug.assert(offset <= start);
1035 if (!simd.equal(Bytes, first[offset..start], second[offset..start])) return false;
1036 const retained = self.dependencyRevision(right.role);
1037 if (!simd.equal(Bytes, reference, retained.view().semantic_record)) return false;
1038 offset = start + left.exact.len;
1039 }
1040 return simd.equal(Bytes, first[offset..], second[offset..]);
1041 }
1042
1043 fn referenceDependencies(self: *const Draft, inputs: []const u8, output: [][]const u8) void {
1044 std.debug.assert(output.len == self.dependencies.len);
1045 const declared = (record.decodeInputs(inputs) catch unreachable).dependencies;
1046 std.debug.assert(declared.count == output.len);
1047 var dependencies = declared.iterator();
1048 for (output) |*reference| {
1049 const embedded = (dependencies.next() catch unreachable).?;
1050 const retained = self.dependencyRevision(embedded.role);
1051 reference.* = if (entry(retained).owner == self.owner)
1052 retained.view().semantic_record
1053 else
1054 embedded.exact;
1055 std.debug.assert(reference.len == embedded.exact.len);
1056 }
1057 }
1058
1059 fn dependencyRevision(self: *const Draft, role: []const u8) *const Revision {
1060 for (self.dependencies, self.revisions) |dependency, retained| {
1061 if (std.mem.eql(u8, dependency.role, role)) return retained;
1062 }
1063 unreachable;
1064 }
1065
1066 fn screen(self: *Draft, length: usize) !bool {
1067 if (self.state != .open) return error.InvalidBuilderState;
1068 const work = self.work.view();
1069 if (work.outcome != .running) return error.TerminalWorkOutcome;
1070 if (work.events.len != 4) return error.CompilerWorkAlreadyStarted;
1071 const limits = self.owner.limits;
1072 if (self.screened_candidates == limits.candidate_count) return false;
1073 if (length > limits.screening_bytes - self.screened_bytes) return false;
1074 self.screened_candidates += 1;
1075 self.screened_bytes += length;
1076 try self.work.observeTransport(length);
1077 return true;
1078 }
1079
1080 fn screenRestored(self: *Draft, bytes: []const u8, compiler_manifest: []const u8) !bool {
1081 if (self.restored) return error.RestoredCandidateAlreadyBound;
1082 if (!try self.screen(bytes.len)) return false;
1083 if (!simd.equal(Bytes, self.compiler_manifest, compiler_manifest)) return false;
1084 const restored = record.decodeExact(bytes) catch return false;
1085 if (!simd.equal(Bytes, restored.address, self.address) or
1086 !simd.equal(Bytes, restored.inputs, self.inputs)) return false;
1087 if (self.expected_image) |expected| {
1088 if (!simd.equal(Bytes, expected, restored.image)) return false;
1089 } else {
1090 self.expected_image = try self.owner.allocator.dupe(u8, restored.image);
1091 }
1092 self.restored = true;
1093 return true;
1094 }
1095
1096 fn freeRequest(self: *Draft) void {
1097 const allocator = self.owner.allocator;
1098 allocator.free(self.address);
1099 allocator.free(self.inputs);
1100 freeDependencies(allocator, self.dependencies, self.revisions);
1101 allocator.free(self.revisions);
1102 freeFacts(allocator, self.facts);
1103 if (self.expected_image) |bytes| allocator.free(bytes);
1104 if (self.image) |bytes| allocator.free(bytes);
1105 if (self.output) |bytes| releaseUntouched(allocator, bytes);
1106 if (self.workspace) |workspace| workspace.release();
1107 allocator.destroy(self);
1108 }
1109
1110 fn destroy(self: *Draft) void {
1111 const owner = self.owner;
1112 self.work.destroy();
1113 self.freeRequest();
1114 std.debug.assert(owner.builder_count > 0);
1115 owner.builder_count -= 1;
1116 owner.release();
1117 }
1118 };
1119
1120 fn transport(work: *receipt.AccountingV1, limits: Limits) !void {
1121 const token = try work.begin(.transport, .{
1122 .identity = .{ .name = "revision-screen-v1", .version = 1 },
1123 .work = .{
1124 .input_bytes = limits.screening_bytes,
1125 .allocation_capacity = limits.screening_bytes,
1126 },
1127 .workspace = limits.screening_bytes,
1128 .retained_storage = limits.screening_bytes,
1129 });
1130 try work.finish(token, .success, .{});
1131 }
1132
1133 fn reserveUntouched(allocator: std.mem.Allocator, capacity: usize) error{OutOfMemory}![]u8 {
1134 if (capacity == 0) return allocator.alloc(u8, 0);
1135 const bytes = allocator.rawAlloc(capacity, .of(u8), @returnAddress()) orelse
1136 return error.OutOfMemory;
1137 return bytes[0..capacity];
1138 }
1139
1140 fn releaseUntouched(allocator: std.mem.Allocator, bytes: []u8) void {
1141 if (bytes.len == 0) return allocator.free(bytes);
1142 allocator.rawFree(bytes, .of(u8), @returnAddress());
1143 }
1144
1145 fn reserveOutput(owner: *Storage, work: *receipt.AccountingV1) ![]u8 {
1146 const capacity = owner.limits.record_bytes;
1147 const token = try work.begin(.reservation, .{
1148 .identity = .{ .name = "revision-output-reservation-v1", .version = 1 },
1149 .work = .{ .allocation_capacity = capacity },
1150 .workspace = capacity,
1151 .retained_storage = capacity,
1152 });
1153 errdefer work.finish(token, .rejected, .{}) catch {};
1154 const bytes = try reserveUntouched(owner.allocator, capacity);
1155 errdefer releaseUntouched(owner.allocator, bytes);
1156 try work.finish(token, .success, .{ .work = .{ .allocation_capacity = bytes.len } });
1157 return bytes;
1158 }
1159
1160 fn reserveWorkspace(owner: *Storage, work: *receipt.AccountingV1) !*WorkspaceData {
1161 const capacity = std.math.cast(usize, work.view().limits.workspace) orelse {
1162 work.fail(.exhausted);
1163 return error.WorkOverflow;
1164 };
1165 const scratch = owner.limits.gate_scratch_bytes;
1166 const token = try work.begin(.reservation, .{
1167 .identity = .{ .name = "revision-workspace-reservation-v1", .version = 1 },
1168 .work = .{ .allocation_capacity = capacity },
1169 .workspace = @max(capacity, scratch),
1170 .retained_storage = capacity,
1171 });
1172 errdefer work.finish(token, .rejected, .{}) catch {};
1173 const allocator = owner.allocator;
1174 const state = try allocator.create(WorkspaceData);
1175 errdefer allocator.destroy(state);
1176 const bytes = try reserveUntouched(allocator, capacity);
1177 errdefer releaseUntouched(allocator, bytes);
1178 try work.finish(token, .success, .{ .work = .{ .allocation_capacity = bytes.len } });
1179 state.* = .{ .allocator = allocator, .bytes = bytes, .scratch_bytes = scratch };
1180 return state;
1181 }
1182
1183 fn internImage(published: *Entry) void {
1184 const owner = published.owner;
1185 if (owner.findRecord(published.semantic)) |existing| {
1186 releaseUntouched(owner.allocator, published.allocation.?);
1187 published.semantic = existing.semantic;
1188 published.exact = existing.exact;
1189 published.allocation = null;
1190 }
1191 }
1192
1193 /// A revision's identity is its exact bytes under its owner-interned compiler
1194 /// manifest. Manifests intern per store, so revisions that share one meet at a
1195 /// single address and settle in constant time; across stores the bytes decide.
1196 fn sameIdentity(self: *const RecordData, other: *const RecordData) bool {
1197 if (!simd.equal(Bytes, self.compiler_manifest, other.compiler_manifest)) return false;
1198 return self.exact.eql(other.exact);
1199 }
1200
1201 const RecordData = struct {
1202 owner: *Storage,
1203 compiler_manifest: []const u8,
1204 semantic: []const u8,
1205 exact: record.Exact,
1206
1207 fn destroy(self: *RecordData) void {
1208 const allocator = self.owner.allocator;
1209 allocator.free(self.semantic);
1210 allocator.destroy(self);
1211 }
1212 };
1213
1214 fn recordData(handle: *const Record) *const RecordData {
1215 return @ptrCast(@alignCast(handle));
1216 }
1217
1218 fn retainDependencies(
1219 allocator: std.mem.Allocator,
1220 source: []const Dependency,
1221 revisions: []*const Revision,
1222 ) ![]record.Dependency {
1223 std.debug.assert(source.len == revisions.len);
1224 const result = try allocator.alloc(record.Dependency, source.len);
1225 errdefer allocator.free(result);
1226 var initialized: usize = 0;
1227 errdefer for (result[0..initialized], revisions[0..initialized]) |item, retained| {
1228 allocator.free(item.role);
1229 retained.release();
1230 };
1231 for (source, result, revisions) |dependency, *output, *retained| {
1232 const role = try allocator.dupe(u8, dependency.role);
1233 errdefer allocator.free(role);
1234 retained.* = try dependency.revision.retain();
1235 output.* = .{ .role = role, .exact = retained.*.view().semantic_record };
1236 initialized += 1;
1237 }
1238 return result;
1239 }
1240
1241 fn freeDependencies(
1242 allocator: std.mem.Allocator,
1243 values: []const record.Dependency,
1244 revisions: []const *const Revision,
1245 ) void {
1246 std.debug.assert(values.len == revisions.len);
1247 for (values, revisions) |value, retained| {
1248 allocator.free(value.role);
1249 retained.release();
1250 }
1251 allocator.free(values);
1252 }
1253
1254 fn copyFacts(allocator: std.mem.Allocator, source: []const record.Fact) ![]record.Fact {
1255 const result = try allocator.alloc(record.Fact, source.len);
1256 errdefer allocator.free(result);
1257 var initialized: usize = 0;
1258 errdefer for (result[0..initialized]) |item| {
1259 allocator.free(item.key);
1260 if (item.value) |bytes| allocator.free(bytes);
1261 };
1262 for (source, result) |fact, *output| {
1263 const key = try allocator.dupe(u8, fact.key);
1264 errdefer allocator.free(key);
1265 output.* = .{
1266 .key = key,
1267 .value = if (fact.value) |bytes| try allocator.dupe(u8, bytes) else null,
1268 };
1269 initialized += 1;
1270 }
1271 return result;
1272 }
1273
1274 fn freeFacts(allocator: std.mem.Allocator, values: []const record.Fact) void {
1275 for (values) |value| {
1276 allocator.free(value.key);
1277 if (value.value) |bytes| allocator.free(bytes);
1278 }
1279 allocator.free(values);
1280 }
1281
1282 fn recordSize(limit: u32, address: usize, inputs: usize, image: usize) !void {
1283 if (try record.exactSize(address, inputs, image) > limit) return error.RecordLimit;
1284 }
1285
1286 fn inspectRequest(request: Request, limit: u32) !u64 {
1287 var size: u64 = 256;
1288 inline for (@typeInfo(record.Address).@"struct".field_names) |field| {
1289 try addBound(&size, @field(request.address, field).len, limit);
1290 }
1291 try addBound(&size, request.inputs.options.len, limit);
1292 try addBound(&size, request.inputs.policy.len, limit);
1293 try addBound(&size, kindData(request.kind).manifest.len, limit);
1294 for (request.inputs.versions) |version| try addBound(&size, version.name.len + 8, limit);
1295 for (request.inputs.pipeline) |version| try addBound(&size, version.name.len + 8, limit);
1296 for (request.inputs.facts) |fact| {
1297 try addBound(&size, fact.key.len, limit);
1298 try addBound(&size, if (fact.value) |bytes| bytes.len else 0, limit);
1299 try addBound(&size, 9, limit);
1300 }
1301 for (request.dependencies) |dependency| {
1302 try addBound(&size, dependency.role.len, limit);
1303 try addBound(&size, dependency.revision.view().semantic_record.len, limit);
1304 try addBound(&size, 8, limit);
1305 }
1306 if (request.expected_image) |bytes| {
1307 if (bytes.len > limit) return error.RecordLimit;
1308 }
1309 return size;
1310 }
1311
1312 fn requestCapacity(request: Request, encoded_bound: u64) !u64 {
1313 var size = try capacityMul(encoded_bound, 4);
1314 const tables = [_]struct { count: usize, item_bytes: usize }{
1315 .{ .count = request.inputs.versions.len, .item_bytes = @sizeOf(record.Version) },
1316 .{ .count = request.inputs.facts.len, .item_bytes = 2 * @sizeOf(record.Fact) },
1317 .{ .count = request.dependencies.len, .item_bytes = 2 * @sizeOf(record.Dependency) },
1318 .{ .count = request.dependencies.len, .item_bytes = @sizeOf(*const Revision) },
1319 };
1320 for (tables) |table| {
1321 size = try capacityAdd(size, try capacityMul(table.count, table.item_bytes));
1322 }
1323 size = try capacityAdd(size, @sizeOf(Draft));
1324 if (request.expected_image) |bytes| size = try capacityAdd(size, bytes.len);
1325 return size;
1326 }
1327
1328 fn capacityAdd(first: u64, second: u64) !u64 {
1329 return std.math.add(u64, first, second) catch error.WorkOverflow;
1330 }
1331
1332 fn capacityMul(first: u64, second: u64) !u64 {
1333 return std.math.mul(u64, first, second) catch error.WorkOverflow;
1334 }
1335
1336 fn addBound(size: *u64, additional: usize, limit: u32) !void {
1337 size.* = std.math.add(u64, size.*, additional) catch return error.RecordOverflow;
1338 if (size.* > limit) return error.RecordLimit;
1339 }
1340
1341 fn allocateEpoch() !u64 {
1342 var expected = next_epoch.load(.monotonic);
1343 for (0..1024) |_| {
1344 if (expected == std.math.maxInt(u64)) return error.GenerationOverflow;
1345 if (next_epoch.cmpxchgWeak(expected, expected + 1, .monotonic, .monotonic)) |actual| {
1346 expected = actual;
1347 } else return expected;
1348 }
1349 return error.EpochContention;
1350 }
1351
1352 fn cloneGate(allocator: std.mem.Allocator, gate: Gate) !Gate {
1353 const name = try allocator.dupe(u8, gate.identity.name);
1354 errdefer allocator.free(name);
1355 return .{
1356 .identity = .{ .name = name, .version = gate.identity.version },
1357 .definition = try allocator.dupe(u8, gate.definition),
1358 .scratch_bytes = gate.scratch_bytes,
1359 .run = gate.run,
1360 };
1361 }
1362
1363 fn freeGate(allocator: std.mem.Allocator, gate: Gate) void {
1364 allocator.free(gate.identity.name);
1365 allocator.free(gate.definition);
1366 }
1367
1368 fn encodeManifest(allocator: std.mem.Allocator, contract: KindContract) ![]u8 {
1369 var writer = record.Writer.init(allocator);
1370 defer writer.deinit();
1371 try writer.writeInt(u32, record.schema_version);
1372 try writeVersion(&writer, contract.identity);
1373 try writeGate(&writer, contract.schema);
1374 try writer.writeCount(contract.gates.len);
1375 for (contract.gates, 0..) |gate, index| {
1376 if (gate.identity.eql(contract.schema.identity)) return error.DuplicateGate;
1377 for (contract.gates[0..index]) |previous| {
1378 if (std.mem.eql(u8, gate.identity.name, previous.identity.name)) {
1379 return error.DuplicateGate;
1380 }
1381 }
1382 try writeGate(&writer, gate);
1383 }
1384 return writer.finish();
1385 }
1386
1387 fn writeVersion(writer: *record.Writer, identity: record.Version) !void {
1388 if (identity.version == 0 or identity.name.len == 0) return error.InvalidVersion;
1389 try writer.writeString(identity.name);
1390 try writer.writeInt(u32, identity.version);
1391 }
1392
1393 fn writeGate(writer: *record.Writer, gate: Gate) !void {
1394 if (gate.definition.len == 0) return error.MissingGateContract;
1395 try writeVersion(writer, gate.identity);
1396 try writer.writeBlob(gate.definition);
1397 try writer.writeInt(u32, gate.scratch_bytes);
1398 }
1399
1400 fn digest(bytes: []const u8) [32]u8 {
1401 var value: [32]u8 = undefined;
1402 std.crypto.hash.sha2.Sha256.hash(bytes, &value, .{});
1403 return value;
1404 }
1405
1406 fn storage(handle: *Store) *Storage {
1407 return @ptrCast(@alignCast(handle));
1408 }
1409
1410 fn entry(handle: *const Revision) *const Entry {
1411 return @ptrCast(@alignCast(handle));
1412 }
1413
1414 fn kindData(handle: *const Kind) *const KindData {
1415 return @ptrCast(@alignCast(handle));
1416 }
1417
1418 fn draft(handle: *Builder) *Draft {
1419 return @ptrCast(@alignCast(handle));
1420 }
1421
1422 const test_limits = Limits{
1423 .revisions = 8,
1424 .kinds = 4,
1425 .builders = 4,
1426 .compiler_manifests = 2,
1427 .record_bytes = 16 * 1024,
1428 .gate_scratch_bytes = 1024,
1429 .candidate_count = 8,
1430 .screening_bytes = 16 * 1024,
1431 };
1432
1433 const test_work = receipt.Limits{
1434 .allowance = receipt.WorkVector.uniform(4 * 1024 * 1024),
1435 .workspace = 1024 * 1024,
1436 .events = 32,
1437 };
1438
1439 fn testSchema(input: GateInput, scratch: []u8) !record.EntityCounts {
1440 if (scratch.len < 8) return error.OutOfMemory;
1441 scratch[0] = 0;
1442 if (input.exact.image.len == 0) return error.InvalidImage;
1443 if (input.exact.image[0] != 'v') return error.UnencodableProduct;
1444 var counts: record.EntityCounts = @splat(0);
1445 counts[@backingInt(record.Namespace.root)] = 1;
1446 return counts;
1447 }
1448
1449 fn testContract() KindContract {
1450 return .{
1451 .identity = .{ .name = "test-kind", .version = 1 },
1452 .schema = .{
1453 .identity = .{ .name = "test-schema", .version = 1 },
1454 .definition = "v prefix; one root",
1455 .scratch_bytes = 8,
1456 .run = testSchema,
1457 },
1458 .gates = &.{},
1459 };
1460 }
1461
1462 const test_options = "default=expanded";
1463 const test_manifest = "exact compiled inputs";
1464
1465 fn testRequest(kind: *const Kind) Request {
1466 return .{
1467 .kind = kind,
1468 .address = .{ .producer = "test", .source = "source", .stage = "image", .variant = "" },
1469 .inputs = .{
1470 .compiler_manifest = test_manifest,
1471 .versions = &.{.{ .name = "codec", .version = 1 }},
1472 .pipeline = &.{},
1473 .options = test_options,
1474 .policy = "strict",
1475 },
1476 };
1477 }
1478
1479 fn testPublish(owner: *Store, kind: *const Kind, bytes: []const u8) !*const Revision {
1480 const builder = try owner.begin(testRequest(kind), test_work);
1481 errdefer if (builder.abort(.rejected)) |failure| {
1482 var owned = failure;
1483 owned.deinit();
1484 };
1485 try builder.capture(bytes, &.{});
1486 return builder.seal(kind);
1487 }
1488
1489 const TestSuccessor = struct {
1490 kind: *const Kind,
1491 source: *const Revision,
1492 role: []const u8 = "input",
1493 options: []const u8 = test_options,
1494
1495 fn eqlRequest(left: TestSuccessor, right: TestSuccessor) bool {
1496 return left.kind == right.kind and std.mem.eql(u8, left.role, right.role) and
1497 std.mem.eql(u8, left.options, right.options) and left.source.eql(right.source);
1498 }
1499 };
1500
1501 fn testBeginSuccessor(owner: *Store, successor: TestSuccessor) !*Builder {
1502 var request = testRequest(successor.kind);
1503 request.inputs.options = successor.options;
1504 const dependencies = [_]Dependency{.{ .role = successor.role, .revision = successor.source }};
1505 request.dependencies = &dependencies;
1506 return owner.begin(request, test_work);
1507 }
1508
1509 fn testPublishSuccessor(owner: *Store, successor: TestSuccessor) !*const Revision {
1510 const builder = try testBeginSuccessor(owner, successor);
1511 errdefer if (builder.abort(.rejected)) |failure| {
1512 var owned = failure;
1513 owned.deinit();
1514 };
1515 try builder.capture("valid-successor", &.{});
1516 return builder.seal(successor.kind);
1517 }
1518
1519 const test_closure = record.ClosureBounds{ .bytes = 16 * 1024, .records = 32, .depth = 8 };
1520
1521 test "revision store interns restored records without publication authority" {
1522 const allocator = std.testing.allocator;
1523 const producer = try Store.create(allocator, test_limits);
1524 const kind = try producer.register(testContract());
1525 producer.freeze();
1526 const published = try testPublish(producer, kind, "value");
1527 const owner = try Store.create(allocator, test_limits);
1528 const first = try owner.importRecord(
1529 published.view().semantic_record,
1530 test_manifest,
1531 test_closure,
1532 );
1533 const second = try owner.importRecord(first.bytes(), test_manifest, test_closure);
1534 try std.testing.expect(first == second);
1535 try std.testing.expect(first.eql(published.metadata()));
1536 try std.testing.expectEqual(@as(u32, 0), owner.publicationCount());
1537 var candidates: [1]*const Revision = undefined;
1538 try std.testing.expectEqual(@as(usize, 0), owner.lookup(first.bucket(), &candidates).len);
1539 producer.release();
1540 published.release();
1541 owner.release();
1542 first.release();
1543 try std.testing.expectEqualStrings("value", second.view().image);
1544 second.release();
1545 }
1546
1547 test "revision store exact metadata survives forced bucket and digest collisions" {
1548 const owner = try Store.create(std.testing.allocator, test_limits);
1549 defer owner.release();
1550 storage(owner).digest_fn = collidingDigest;
1551 const kind = try owner.register(testContract());
1552 owner.freeze();
1553 const first = try testPublish(owner, kind, "value-a");
1554 defer first.release();
1555 const second = try testPublish(owner, kind, "value-b");
1556 defer second.release();
1557 try std.testing.expectEqual(first.metadata().bucket(), second.metadata().bucket());
1558 try std.testing.expect(!first.metadata().eql(second.metadata()));
1559 const restored = try owner.importRecord(first.metadata().bytes(), test_manifest, test_closure);
1560 defer restored.release();
1561 try std.testing.expect(restored == first.metadata());
1562 }
1563
1564 fn recordAllocationScenario(allocator: std.mem.Allocator, bytes: []const u8) !void {
1565 const owner = try Store.create(allocator, test_limits);
1566 defer owner.release();
1567 errdefer std.debug.assert(storage(owner).record_count == 0);
1568 const imported = try owner.importRecord(bytes, test_manifest, test_closure);
1569 imported.release();
1570 }
1571
1572 test "revision store restored record insertion rolls back every allocation failure" {
1573 const owner = try Store.create(std.testing.allocator, test_limits);
1574 defer owner.release();
1575 const kind = try owner.register(testContract());
1576 owner.freeze();
1577 const published = try testPublish(owner, kind, "value");
1578 defer published.release();
1579 try std.testing.checkAllAllocationFailures(
1580 std.testing.allocator,
1581 recordAllocationScenario,
1582 .{published.metadata().bytes()},
1583 );
1584 try std.testing.expectError(error.RecordClosureLimit, owner.importRecord(
1585 published.metadata().bytes(),
1586 test_manifest,
1587 .{ .bytes = test_closure.bytes, .records = 0, .depth = 8 },
1588 ));
1589 try std.testing.expectError(
1590 error.UnknownSchema,
1591 owner.importRecord("invalid", test_manifest, test_closure),
1592 );
1593 }
1594
1595 test "revision store retains immutable copies after builder and store owner release" {
1596 const owner = try Store.create(std.testing.allocator, test_limits);
1597 const kind = try owner.register(testContract());
1598 owner.freeze();
1599 var original = [_]u8{ 'v', 'a', 'l', 'u', 'e' };
1600 const first = try testPublish(owner, kind, &original);
1601 defer first.release();
1602 const reference = try first.entity(.root, 0);
1603 defer reference.release();
1604 @memset(&original, 'x');
1605 const second = try testPublish(owner, kind, "value");
1606 defer second.release();
1607 try std.testing.expect(first.eql(second));
1608 try std.testing.expect(first.metadata() == second.metadata());
1609 try std.testing.expect(first.view().event.counter != second.view().event.counter);
1610 try std.testing.expectEqual(
1611 first.view().semantic_record.ptr,
1612 second.view().semantic_record.ptr,
1613 );
1614 owner.release();
1615 try std.testing.expectEqualStrings("value", reference.revision.view().exact.image);
1616 try std.testing.expectEqual(.success, first.view().work.outcome);
1617 try std.testing.expectError(error.InvalidEntity, first.entity(.root, 1));
1618 }
1619
1620 test "revision store sealing after import preserves the canonical metadata handle" {
1621 const producer = try Store.create(std.testing.allocator, test_limits);
1622 defer producer.release();
1623 const producer_kind = try producer.register(testContract());
1624 producer.freeze();
1625 const original = try testPublish(producer, producer_kind, "value");
1626 defer original.release();
1627 const owner = try Store.create(std.testing.allocator, test_limits);
1628 defer owner.release();
1629 const imported = try owner.importRecord(
1630 original.metadata().bytes(),
1631 test_manifest,
1632 test_closure,
1633 );
1634 defer imported.release();
1635 const kind = try owner.register(testContract());
1636 owner.freeze();
1637 const published = try testPublish(owner, kind, "value");
1638 defer published.release();
1639 try std.testing.expect(imported == published.metadata());
1640 try std.testing.expectEqual(@as(u32, 1), owner.publicationCount());
1641 }
1642
1643 test "revision store metadata answers under its own compiler when bytes collide" {
1644 const producer = try Store.create(std.testing.allocator, test_limits);
1645 defer producer.release();
1646 const producer_kind = try producer.register(testContract());
1647 producer.freeze();
1648 const original = try testPublish(producer, producer_kind, "value");
1649 defer original.release();
1650 const owner = try Store.create(std.testing.allocator, test_limits);
1651 defer owner.release();
1652 const kind = try owner.register(testContract());
1653 owner.freeze();
1654 const published = try testPublish(owner, kind, "value");
1655 defer published.release();
1656 const foreign = try owner.importRecord(
1657 original.metadata().bytes(),
1658 "exact compiled inputX",
1659 test_closure,
1660 );
1661 defer foreign.release();
1662 try std.testing.expectEqualSlices(u8, foreign.bytes(), published.metadata().bytes());
1663 try std.testing.expect(foreign != published.metadata());
1664 try std.testing.expect(!foreign.eql(published.metadata()));
1665 try std.testing.expectEqualStrings(test_manifest, published.metadata().compilerManifest());
1666 try std.testing.expectEqualStrings("exact compiled inputX", foreign.compilerManifest());
1667 }
1668
1669 fn collidingDigest(_: []const u8) [32]u8 {
1670 return @splat(7);
1671 }
1672
1673 test "revision store forced digest collisions never authorize unequal image reuse" {
1674 const owner = try Store.create(std.testing.allocator, test_limits);
1675 defer owner.release();
1676 storage(owner).digest_fn = collidingDigest;
1677 const kind = try owner.register(testContract());
1678 owner.freeze();
1679 const first = try testPublish(owner, kind, "value-a");
1680 defer first.release();
1681 const second = try testPublish(owner, kind, "value-b");
1682 defer second.release();
1683 try std.testing.expectEqual(first.view().digest, second.view().digest);
1684 try std.testing.expectEqual(first.view().bucket, second.view().bucket);
1685 try std.testing.expect(!first.eql(second));
1686 var request = testRequest(kind);
1687 request.expected_image = "value-b";
1688 const builder = try owner.begin(request, test_work);
1689 defer if (builder.abort(.rejected)) |failure| {
1690 var owned = failure;
1691 owned.deinit();
1692 };
1693 try std.testing.expectEqual(null, try builder.admitReuse(first));
1694 try std.testing.expectEqual(4, draft(builder).work.view().events.len);
1695 }
1696
1697 test "revision store gate failure keeps the ancestor and installs no partial output" {
1698 const owner = try Store.create(std.testing.allocator, test_limits);
1699 defer owner.release();
1700 const kind = try owner.register(testContract());
1701 owner.freeze();
1702 const ancestor = try testPublish(owner, kind, "valid");
1703 defer ancestor.release();
1704 var request = testRequest(kind);
1705 request.parent = ancestor;
1706 const builder = try owner.begin(request, test_work);
1707 try builder.capture("invalid", &.{});
1708 try std.testing.expectError(error.UnencodableProduct, builder.seal(kind));
1709 var failure = builder.abort(.rejected).?;
1710 defer failure.deinit();
1711 try std.testing.expectEqual(.rejected, failure.work.outcome);
1712 try std.testing.expect(failure.work.charged.input_bytes > 0);
1713 try std.testing.expectEqual(1, owner.publicationCount());
1714 try std.testing.expectEqualStrings("valid", ancestor.view().exact.image);
1715 }
1716
1717 test "revision store freezes exact schema and gate manifests before jobs" {
1718 const owner = try Store.create(std.testing.allocator, test_limits);
1719 defer owner.release();
1720 const kind = try owner.register(testContract());
1721 try std.testing.expectEqual(kind, try owner.register(testContract()));
1722 var changed = testContract();
1723 changed.schema.definition = "different semantics";
1724 try std.testing.expectError(error.KindContractMismatch, owner.register(changed));
1725 try std.testing.expectError(error.RegistrationOpen, owner.begin(testRequest(kind), test_work));
1726 owner.freeze();
1727 try std.testing.expectError(error.RegistrationFrozen, owner.register(testContract()));
1728 }
1729
1730 fn allocationScenario(allocator: std.mem.Allocator) !void {
1731 const owner = try Store.create(allocator, test_limits);
1732 defer owner.release();
1733 const kind = try owner.register(testContract());
1734 owner.freeze();
1735 errdefer std.debug.assert(owner.publicationCount() == 0);
1736 const result = try testPublish(owner, kind, "valid");
1737 defer result.release();
1738 try std.testing.expectEqual(1, owner.publicationCount());
1739 }
1740
1741 test "revision store retains exact cross store dependency closure and checks gate versions" {
1742 const allocator = std.testing.allocator;
1743 const first_owner = try Store.create(allocator, test_limits);
1744 const first_kind = try first_owner.register(testContract());
1745 first_owner.freeze();
1746 const ancestor = try testPublish(first_owner, first_kind, "valid");
1747 const second_owner = try Store.create(allocator, test_limits);
1748 defer second_owner.release();
1749 const second_kind = try second_owner.register(testContract());
1750 second_owner.freeze();
1751 const equal = try testPublish(second_owner, second_kind, "valid");
1752 defer equal.release();
1753 try std.testing.expect(ancestor.eql(equal));
1754 try std.testing.expect(ancestor.view().event.store_epoch != equal.view().event.store_epoch);
1755 var request = testRequest(second_kind);
1756 request.dependencies = &.{.{ .role = "input", .revision = ancestor }};
1757 const builder = try second_owner.begin(request, test_work);
1758 const stored = ancestor.view().semantic_record;
1759 ancestor.release();
1760 first_owner.release();
1761 const borrowed = draft(builder).dependencies[0].exact;
1762 try std.testing.expect(borrowed.ptr == stored.ptr);
1763 try std.testing.expectEqualSlices(u8, equal.view().semantic_record, borrowed);
1764 try builder.requireDependency("input", equal);
1765 try builder.capture("valid-successor", &.{});
1766 const successor = try builder.seal(second_kind);
1767 defer successor.release();
1768 const embedded = entry(successor).references[0];
1769 try std.testing.expect(embedded.ptr != equal.view().semantic_record.ptr);
1770 try successor.requireDependency("input", equal);
1771 try std.testing.expectError(
1772 error.UndeclaredProductInput,
1773 successor.requireDependency("source", equal),
1774 );
1775 try successor.requireGates(&.{.{ .name = "test-schema", .version = 1 }});
1776 try std.testing.expectError(error.MissingGateEvidence, successor.requireGates(&.{
1777 .{ .name = "test-schema", .version = 2 },
1778 }));
1779 try std.testing.expect(std.mem.indexOf(u8, successor.view().exact.inputs, "valid") != null);
1780 }
1781
1782 test "revision store compares sealed dependencies through stored records" {
1783 const owner = try Store.create(std.testing.allocator, test_limits);
1784 defer owner.release();
1785 const kind = try owner.register(testContract());
1786 owner.freeze();
1787 const first = try testPublish(owner, kind, "value-a");
1788 defer first.release();
1789 const second = try testPublish(owner, kind, "value-b");
1790 defer second.release();
1791 const length = first.view().semantic_record.len;
1792 try std.testing.expectEqual(length, second.view().semantic_record.len);
1793 const successor = try testPublishSuccessor(owner, .{ .kind = kind, .source = first });
1794 defer successor.release();
1795 const reference = entry(successor).references[0];
1796 try std.testing.expect(reference.ptr == first.view().semantic_record.ptr);
1797 try std.testing.expectEqual(length, reference.len);
1798 try successor.requireDependency("input", first);
1799 try std.testing.expectError(
1800 error.UndeclaredProductInput,
1801 successor.requireDependency("input", second),
1802 );
1803 try std.testing.expectError(
1804 error.UndeclaredProductInput,
1805 successor.requireDependency("source", first),
1806 );
1807 try std.testing.expectError(
1808 error.UndeclaredProductInput,
1809 first.requireDependency("input", first),
1810 );
1811 }
1812
1813 test "revision store reuse compares inputs as their encoded bytes" {
1814 const allocator = std.testing.allocator;
1815 var limits = test_limits;
1816 limits.revisions = 16;
1817 const owner = try Store.create(allocator, limits);
1818 defer owner.release();
1819 const kind = try owner.register(testContract());
1820 var tail_contract = testContract();
1821 tail_contract.identity.name = "tail-kind";
1822 const tail_kind = try owner.register(tail_contract);
1823 owner.freeze();
1824 const foreign_owner = try Store.create(allocator, test_limits);
1825 defer foreign_owner.release();
1826 const foreign_kind = try foreign_owner.register(testContract());
1827 foreign_owner.freeze();
1828 const first = try testPublish(owner, kind, "value-a");
1829 defer first.release();
1830 const second = try testPublish(owner, kind, "value-b");
1831 defer second.release();
1832 const foreign = try testPublish(foreign_owner, foreign_kind, "value-a");
1833 defer foreign.release();
1834 const successors = [_]TestSuccessor{
1835 .{ .kind = kind, .source = first },
1836 .{ .kind = kind, .source = second },
1837 .{ .kind = kind, .source = foreign },
1838 .{ .kind = kind, .source = first, .role = "inlet" },
1839 .{ .kind = kind, .source = first, .options = "default=collapse" },
1840 .{ .kind = tail_kind, .source = first },
1841 };
1842 var agreements: u32 = 0;
1843 for (successors) |prior_successor| {
1844 const prior = try testPublishSuccessor(owner, prior_successor);
1845 defer prior.release();
1846 for (successors) |request_successor| {
1847 const builder = try testBeginSuccessor(owner, request_successor);
1848 defer if (builder.abort(.rejected)) |failure| {
1849 var owned = failure;
1850 owned.deinit();
1851 };
1852 const prior_inputs = entry(prior).exact.inputs;
1853 const request_inputs = draft(builder).inputs;
1854 try std.testing.expectEqual(prior_inputs.len, request_inputs.len);
1855 const exact = std.mem.eql(u8, prior_inputs, request_inputs);
1856 try std.testing.expectEqual(prior_successor.eqlRequest(request_successor), exact);
1857 try std.testing.expectEqual(exact, draft(builder).eqlInputs(entry(prior)));
1858 agreements += 1;
1859 }
1860 }
1861 try std.testing.expectEqual(36, agreements);
1862 }
1863
1864 test "revision store generation overflow and capacity failure never install output" {
1865 var limits = test_limits;
1866 limits.revisions = 1;
1867 const owner = try Store.create(std.testing.allocator, limits);
1868 defer owner.release();
1869 const kind = try owner.register(testContract());
1870 owner.freeze();
1871 storage(owner).event_counter = std.math.maxInt(u64);
1872 try std.testing.expectError(error.GenerationOverflow, testPublish(owner, kind, "valid"));
1873 try std.testing.expectEqual(0, owner.publicationCount());
1874 storage(owner).event_counter = 0;
1875 const published = try testPublish(owner, kind, "valid");
1876 defer published.release();
1877 try std.testing.expectError(error.StoreFull, testPublish(owner, kind, "valid-successor"));
1878 try std.testing.expectEqual(1, owner.publicationCount());
1879 try std.testing.expectEqual(1, published.view().event.counter);
1880 }
1881
1882 test "revision store interns one copy of each compiler manifest and refuses more" {
1883 const owner = try Store.create(std.testing.allocator, test_limits);
1884 defer owner.release();
1885 const state = storage(owner);
1886 const first = try state.internManifest(test_manifest);
1887 var same: [test_manifest.len]u8 = undefined;
1888 @memcpy(&same, test_manifest);
1889 const repeated = try state.internManifest(&same);
1890 try std.testing.expect(first.ptr == repeated.ptr);
1891 try std.testing.expectEqual(@as(u32, 1), state.manifest_count);
1892 const second = try state.internManifest("other compiled inputs");
1893 try std.testing.expect(first.ptr != second.ptr);
1894 try std.testing.expectEqual(@as(u32, 2), state.manifest_count);
1895 try std.testing.expectError(error.CompilerManifestLimit, state.internManifest("a third"));
1896 try std.testing.expectError(error.MissingCompilerManifest, state.internManifest(""));
1897 }
1898
1899 test "revision store identity separates equal bytes published under different compilers" {
1900 const allocator = std.testing.allocator;
1901 const first_owner = try Store.create(allocator, test_limits);
1902 defer first_owner.release();
1903 const second_owner = try Store.create(allocator, test_limits);
1904 defer second_owner.release();
1905 const first_kind = try first_owner.register(testContract());
1906 const second_kind = try second_owner.register(testContract());
1907 first_owner.freeze();
1908 second_owner.freeze();
1909 const original = try testPublish(first_owner, first_kind, "value");
1910 defer original.release();
1911 const sibling = try testPublish(first_owner, first_kind, "value");
1912 defer sibling.release();
1913 try std.testing.expect(original.eql(original));
1914 try std.testing.expect(original.eql(sibling));
1915
1916 var changed = testRequest(second_kind);
1917 changed.inputs.compiler_manifest = "exact compiled inputX";
1918 const builder = try second_owner.begin(changed, test_work);
1919 try builder.capture("value", &.{});
1920 const foreign = try builder.seal(second_kind);
1921 defer foreign.release();
1922 const left = original.view().exact;
1923 const right = foreign.view().exact;
1924 try std.testing.expectEqualSlices(u8, left.address, right.address);
1925 try std.testing.expectEqualSlices(u8, left.inputs, right.inputs);
1926 try std.testing.expectEqualSlices(u8, left.image, right.image);
1927 try std.testing.expect(!original.eql(foreign));
1928 try std.testing.expectEqualStrings(test_manifest, original.view().compiler_manifest);
1929 try std.testing.expectEqualStrings("exact compiled inputX", foreign.view().compiler_manifest);
1930 }
1931
1932 test "revision store changed compiler codec or gate manifests refuse reuse before producers" {
1933 const owner = try Store.create(std.testing.allocator, test_limits);
1934 defer owner.release();
1935 const first_kind = try owner.register(testContract());
1936 var changed = testContract();
1937 changed.identity.version = 2;
1938 changed.schema.identity.version = 2;
1939 const changed_kind = try owner.register(changed);
1940 owner.freeze();
1941 const original = try testPublish(owner, first_kind, "valid");
1942 defer original.release();
1943 var requests: [3]Request = @splat(testRequest(first_kind));
1944 requests[0].inputs.compiler_manifest = "different compiled inputs";
1945 requests[1].inputs.versions = &.{.{ .name = "codec", .version = 2 }};
1946 requests[2].kind = changed_kind;
1947 for (requests) |request| {
1948 const builder = try owner.begin(request, test_work);
1949 try std.testing.expectEqual(null, try builder.admitReuse(original));
1950 var failure = builder.abort(.rejected).?;
1951 defer failure.deinit();
1952 try std.testing.expectEqual(0, failure.work.executed.counters.pass_runs);
1953 try std.testing.expectEqual(4, failure.work.events.len);
1954 }
1955 }
1956
1957 test "revision store cleans allocation failure at capture gate receipt and insertion" {
1958 try std.testing.checkAllAllocationFailures(std.testing.allocator, allocationScenario, .{});
1959 }
1960
1961 fn dependencyAllocationScenario(allocator: std.mem.Allocator, foreign: bool) !void {
1962 const owner = try Store.create(allocator, test_limits);
1963 defer owner.release();
1964 const kind = try owner.register(testContract());
1965 owner.freeze();
1966 const source_owner = if (foreign) try Store.create(allocator, test_limits) else owner;
1967 defer if (foreign) source_owner.release();
1968 const source_kind = if (foreign) try source_owner.register(testContract()) else kind;
1969 if (foreign) source_owner.freeze();
1970 const source = try testPublish(source_owner, source_kind, "valid");
1971 defer source.release();
1972 const published = owner.publicationCount();
1973 errdefer std.debug.assert(owner.publicationCount() == published);
1974 const successor = try testPublishSuccessor(owner, .{ .kind = kind, .source = source });
1975 defer successor.release();
1976 try successor.requireDependency("input", source);
1977 try std.testing.expectEqual(published + 1, owner.publicationCount());
1978 }
1979
1980 test "revision store cleans allocation failure while retaining dependency revisions" {
1981 for ([_]bool{ false, true }) |foreign| {
1982 try std.testing.checkAllAllocationFailures(
1983 std.testing.allocator,
1984 dependencyAllocationScenario,
1985 .{foreign},
1986 );
1987 }
1988 }
1989
1990 const test_pipeline = [_]record.Version{.{ .name = "producer", .version = 1 }};
1991
1992 fn runTestProducer(builder: *Builder) !void {
1993 const work = builder.accounting();
1994 const pass = try work.begin(.pass, .{
1995 .identity = test_pipeline[0],
1996 .work = .{ .structural_visits = 5, .rewrite_attempts = 3 },
1997 .workspace = 100,
1998 });
1999 errdefer work.finish(pass, .rejected, .{}) catch {};
2000 const analysis = try work.begin(.analysis, .{
2001 .identity = .{ .name = "analysis", .version = 1 },
2002 .work = .{ .structural_visits = 2, .analysis_computations = 1 },
2003 .workspace = 100,
2004 });
2005 try work.finish(analysis, .success, .{ .counters = .{ .analysis_misses = 1 } });
2006 try work.finish(pass, .success, .{ .counters = .{ .pass_runs = 1 } });
2007 }
2008
2009 const WorkObservation = struct {
2010 outcome: receipt.Outcome,
2011 charged: receipt.WorkVector,
2012 executed: receipt.Executed,
2013
2014 fn from(work: receipt.WorkReceiptV1) WorkObservation {
2015 return .{ .outcome = work.outcome, .charged = work.charged, .executed = work.executed };
2016 }
2017 };
2018
2019 const TestCandidate = union(enum) { cold, warm: *const Revision, restored: []const u8 };
2020
2021 fn workAttempt(budget: receipt.Limits, candidate: TestCandidate) !WorkObservation {
2022 const owner = try Store.create(std.testing.allocator, test_limits);
2023 defer owner.release();
2024 const kind = try owner.register(testContract());
2025 owner.freeze();
2026 var request = testRequest(kind);
2027 request.inputs.pipeline = &test_pipeline;
2028 const builder = try owner.begin(request, budget);
2029 if (candidate == .warm) {
2030 var reused = (builder.admitReuse(candidate.warm) catch |err| {
2031 return failedAttempt(builder, err);
2032 }) orelse return failedAttempt(builder, error.UnexpectedCacheMiss);
2033 defer reused.deinit();
2034 return WorkObservation.from(reused.work);
2035 }
2036 if (candidate == .restored) {
2037 try std.testing.expect(try builder.screenRestored(candidate.restored, test_manifest));
2038 }
2039 runTestProducer(builder) catch |err| return failedAttempt(builder, err);
2040 builder.capture("valid", &.{}) catch |err| return failedAttempt(builder, err);
2041 const output = builder.seal(kind) catch |err| return failedAttempt(builder, err);
2042 defer output.release();
2043 return WorkObservation.from(output.view().work);
2044 }
2045
2046 fn failedAttempt(builder: *Builder, err: anyerror) !WorkObservation {
2047 var failure = builder.abort(.rejected) orelse return error.MissingFailureReceipt;
2048 defer failure.deinit();
2049 if (err != error.WorkExhausted) return err;
2050 return WorkObservation.from(failure.work);
2051 }
2052
2053 fn baselineWorkRevision(owner: *Store, kind: *const Kind) !*const Revision {
2054 var request = testRequest(kind);
2055 request.inputs.pipeline = &test_pipeline;
2056 const builder = try owner.begin(request, test_work);
2057 errdefer if (builder.abort(.rejected)) |failure| {
2058 var owned = failure;
2059 owned.deinit();
2060 };
2061 try runTestProducer(builder);
2062 try builder.capture("valid", &.{});
2063 return builder.seal(kind);
2064 }
2065
2066 test "revision store cold warm and restored share every budget boundary" {
2067 const owner = try Store.create(std.testing.allocator, test_limits);
2068 defer owner.release();
2069 const kind = try owner.register(testContract());
2070 owner.freeze();
2071 const baseline = try baselineWorkRevision(owner, kind);
2072 defer baseline.release();
2073 const charge = baseline.view().work.charged;
2074 inline for (@typeInfo(receipt.WorkVector).@"struct".field_names) |field| {
2075 for ([_]i64{ -1, 0, 1 }) |offset| {
2076 var budget = test_work;
2077 budget.allowance = charge;
2078 const value: i64 = @intCast(@field(charge, field));
2079 @field(budget.allowance, field) = @intCast(value + offset);
2080 const cold = try workAttempt(budget, .cold);
2081 const warm = try workAttempt(budget, .{ .warm = baseline });
2082 const restored = try workAttempt(budget, .{
2083 .restored = baseline.view().semantic_record,
2084 });
2085 try std.testing.expectEqual(cold.outcome, warm.outcome);
2086 try std.testing.expectEqualDeep(cold.charged, warm.charged);
2087 try std.testing.expectEqual(cold.outcome, restored.outcome);
2088 try std.testing.expectEqualDeep(cold.charged, restored.charged);
2089 try std.testing.expectEqualDeep(cold.executed.counters, restored.executed.counters);
2090 const expected: receipt.Outcome = if (offset < 0) .exhausted else .success;
2091 try std.testing.expectEqual(expected, cold.outcome);
2092 try std.testing.expectEqual(0, warm.executed.counters.pass_runs);
2093 }
2094 }
2095 }
2096
2097 test "revision store restored bytes bind cold comparison and never authorize replay" {
2098 const owner = try Store.create(std.testing.allocator, test_limits);
2099 defer owner.release();
2100 const kind = try owner.register(testContract());
2101 owner.freeze();
2102 const prior = try baselineWorkRevision(owner, kind);
2103 defer prior.release();
2104 var request = testRequest(kind);
2105 request.inputs.pipeline = &test_pipeline;
2106 const builder = try owner.begin(request, test_work);
2107 try std.testing.expect(!try builder.screenRestored(
2108 prior.view().semantic_record,
2109 "exact compiled inputX",
2110 ));
2111 try std.testing.expect(try builder.screenRestored(prior.view().semantic_record, test_manifest));
2112 try std.testing.expectError(
2113 error.RestoredCandidateRequiresExecution,
2114 builder.admitReuse(prior),
2115 );
2116 var refused = builder.abort(.rejected).?;
2117 defer refused.deinit();
2118 try std.testing.expectEqual(0, refused.work.executed.counters.pass_runs);
2119 const fresh = try owner.begin(request, test_work);
2120 try std.testing.expect(try fresh.screenRestored(prior.view().semantic_record, test_manifest));
2121 try runTestProducer(fresh);
2122 try std.testing.expectError(error.UnexpectedImage, fresh.capture("valid-but-changed", &.{}));
2123 var mismatch = fresh.abort(.rejected).?;
2124 defer mismatch.deinit();
2125 try std.testing.expectEqual(1, mismatch.work.executed.counters.pass_runs);
2126 try std.testing.expectEqual(1, owner.publicationCount());
2127 }
2128
2129 test "revision store screens a bounded number of candidate bytes before cold work" {
2130 var limits = test_limits;
2131 limits.candidate_count = 1;
2132 const owner = try Store.create(std.testing.allocator, limits);
2133 defer owner.release();
2134 const kind = try owner.register(testContract());
2135 owner.freeze();
2136 const prior = try testPublish(owner, kind, "valid");
2137 defer prior.release();
2138 const builder = try owner.begin(testRequest(kind), test_work);
2139 try std.testing.expect(!try builder.screenRestored("corrupt", test_manifest));
2140 try std.testing.expectEqual(null, try builder.admitReuse(prior));
2141 try std.testing.expectEqual(7, builder.accounting().view().events[0].executed.work.input_bytes);
2142 try builder.capture("valid", &.{});
2143 const published = try builder.seal(kind);
2144 defer published.release();
2145 try std.testing.expect(prior.eql(published));
2146 }
2147
2148 test "revision store warm trace preserves analysis occurrence parents and phase order" {
2149 const owner = try Store.create(std.testing.allocator, test_limits);
2150 defer owner.release();
2151 const kind = try owner.register(testContract());
2152 owner.freeze();
2153 const baseline = try baselineWorkRevision(owner, kind);
2154 defer baseline.release();
2155 var request = testRequest(kind);
2156 request.inputs.pipeline = &test_pipeline;
2157 const builder = try owner.begin(request, test_work);
2158 var reused = (try builder.admitReuse(baseline)).?;
2159 defer reused.deinit();
2160 const cold = baseline.view().work;
2161 try std.testing.expectEqual(cold.events.len, reused.work.events.len);
2162 try std.testing.expectEqual(cold.maximum_live_storage, reused.work.maximum_live_storage);
2163 for (cold.events, reused.work.events) |expected, actual| {
2164 try std.testing.expectEqual(expected.phase, actual.phase);
2165 try std.testing.expectEqual(expected.occurrence, actual.occurrence);
2166 try std.testing.expectEqual(expected.parent, actual.parent);
2167 try std.testing.expectEqual(expected.retained_storage, actual.retained_storage);
2168 }
2169 }
2170
2171 test "revision store reserves the same physical output capacity before cold work and warm reuse" {
2172 var observed = std.testing.FailingAllocator.init(std.testing.allocator, .{});
2173 const owner = try Store.create(observed.allocator(), test_limits);
2174 defer owner.release();
2175 const kind = try owner.register(testContract());
2176 owner.freeze();
2177 const interned = try storage(owner).internManifest(test_manifest);
2178 try std.testing.expectEqual(test_manifest.len, interned.len);
2179 const before_cold = observed.allocated_bytes;
2180 const cold = try owner.begin(testRequest(kind), test_work);
2181 var cold_open = true;
2182 defer if (cold_open) {
2183 if (cold.abort(.rejected)) |failure| {
2184 var owned = failure;
2185 owned.deinit();
2186 }
2187 };
2188 const cold_bytes = observed.allocated_bytes - before_cold;
2189 try std.testing.expect(cold_bytes >= test_work.workspace + test_limits.record_bytes);
2190 try cold.capture("valid", &.{});
2191 const published = try cold.seal(kind);
2192 cold_open = false;
2193 defer published.release();
2194 try std.testing.expect(published.view().work.maximum_live_storage >=
2195 test_work.workspace + test_limits.record_bytes);
2196 const before_warm = observed.allocated_bytes;
2197 const warm = try owner.begin(testRequest(kind), test_work);
2198 var warm_open = true;
2199 defer if (warm_open) {
2200 if (warm.abort(.rejected)) |failure| {
2201 var owned = failure;
2202 owned.deinit();
2203 }
2204 };
2205 const warm_bytes = observed.allocated_bytes - before_warm;
2206 try std.testing.expect(warm_bytes >= test_work.workspace + test_limits.record_bytes);
2207 try std.testing.expectEqual(cold_bytes, warm_bytes);
2208 var reused = (try warm.admitReuse(published)) orelse return error.ExpectedWarmCandidate;
2209 warm_open = false;
2210 defer reused.deinit();
2211 try std.testing.expectEqualDeep(published.view().work.charged, reused.work.charged);
2212 }
2213
2214 test "revision store reserves physical workspace before any cold producer or warm replay" {
2215 var observed = std.testing.FailingAllocator.init(std.testing.allocator, .{});
2216 const owner = try Store.create(observed.allocator(), test_limits);
2217 defer owner.release();
2218 const kind = try owner.register(testContract());
2219 owner.freeze();
2220 var prior: ?*const Revision = null;
2221 defer if (prior) |value| value.release();
2222 for ([_]bool{ false, true }) |warm| {
2223 const before = observed.allocated_bytes;
2224 const builder = try owner.begin(testRequest(kind), test_work);
2225 var open = true;
2226 defer if (open) {
2227 if (builder.abort(.rejected)) |failure| {
2228 var owned = failure;
2229 owned.deinit();
2230 }
2231 };
2232 const allocated = observed.allocated_bytes - before;
2233 try std.testing.expect(allocated >= test_work.workspace + test_limits.record_bytes);
2234 if (warm) {
2235 var reused = (try builder.admitReuse(prior.?)).?;
2236 open = false;
2237 reused.deinit();
2238 } else {
2239 try builder.capture("valid", &.{});
2240 const output = try builder.seal(kind);
2241 open = false;
2242 prior = output;
2243 }
2244 }
2245 }
2246
2247 test "revision workspace leases remain separate and alive after builder and store release" {
2248 const owner = try Store.create(std.testing.allocator, test_limits);
2249 var owner_open = true;
2250 defer if (owner_open) owner.release();
2251 const kind = try owner.register(testContract());
2252 owner.freeze();
2253 const builder = try owner.begin(testRequest(kind), test_work);
2254 var open = true;
2255 defer if (open) {
2256 if (builder.abort(.rejected)) |failure| {
2257 var owned = failure;
2258 owned.deinit();
2259 }
2260 };
2261 const producer = try builder.acquireWorkspace(.producer);
2262 defer producer.release();
2263 @memset(producer.bytes(), 0x23);
2264 const scratch = try builder.acquireWorkspace(.scratch);
2265 @memset(scratch.bytes(), 0xb6);
2266 scratch.release();
2267 try std.testing.expectEqual(
2268 test_work.workspace - test_limits.gate_scratch_bytes,
2269 producer.bytes().len,
2270 );
2271 try std.testing.expectError(
2272 error.WorkspaceAlreadyLeased,
2273 builder.acquireWorkspace(.producer),
2274 );
2275 try builder.capture("valid", &.{});
2276 const published = try builder.seal(kind);
2277 open = false;
2278 published.release();
2279 owner.release();
2280 owner_open = false;
2281 for (producer.bytes()) |byte| try std.testing.expectEqual(0x23, byte);
2282 producer.bytes()[0] = 0x42;
2283 try std.testing.expectEqual(0x42, producer.bytes()[0]);
2284 }
2285
2286 const WorkspaceReuseCase = struct {
2287 workspace: u64 = test_work.workspace,
2288 scratch: u32 = test_limits.gate_scratch_bytes,
2289 reuse: bool = false,
2290 };
2291
2292 fn checkWorkspaceCandidate(candidate: *const Revision, request: WorkspaceReuseCase) !void {
2293 var limits = test_limits;
2294 limits.gate_scratch_bytes = request.scratch;
2295 const owner = try Store.create(std.testing.allocator, limits);
2296 defer owner.release();
2297 const kind = try owner.register(testContract());
2298 owner.freeze();
2299 var input = testRequest(kind);
2300 input.inputs.pipeline = &test_pipeline;
2301 var budget = test_work;
2302 budget.workspace = request.workspace;
2303 budget.allowance = receipt.WorkVector.uniform(8 * 1024 * 1024);
2304 const builder = try owner.begin(input, budget);
2305 var open = true;
2306 defer if (open) {
2307 var failure = builder.abort(.rejected).?;
2308 failure.deinit();
2309 };
2310 const charged = builder.accounting().view().charged;
2311 if (try builder.admitReuse(candidate)) |value| {
2312 open = false;
2313 var reused = value;
2314 defer reused.deinit();
2315 try std.testing.expect(request.reuse);
2316 try std.testing.expectEqual(@as(u64, 0), reused.work.executed.counters.pass_runs);
2317 try std.testing.expect(candidate.eql(reused.revision));
2318 return;
2319 }
2320 try std.testing.expect(!request.reuse);
2321 const before = builder.accounting().view();
2322 try std.testing.expectEqual(.running, before.outcome);
2323 try std.testing.expectEqualDeep(charged, before.charged);
2324 try std.testing.expectEqual(@as(usize, 4), before.events.len);
2325 try std.testing.expectEqual(@as(u64, 0), before.executed.counters.pass_runs);
2326 try std.testing.expectEqual(
2327 candidate.view().semantic_record.len,
2328 before.events[0].executed.work.input_bytes,
2329 );
2330 {
2331 const lease = try builder.acquireWorkspace(.producer);
2332 defer lease.release();
2333 try std.testing.expectEqual(request.workspace - request.scratch, lease.bytes().len);
2334 }
2335 try runTestProducer(builder);
2336 try builder.capture("valid", &.{});
2337 const result = try builder.seal(kind);
2338 open = false;
2339 defer result.release();
2340 try std.testing.expect(candidate.eql(result));
2341 try std.testing.expectEqual(@as(u64, 1), result.view().work.executed.counters.pass_runs);
2342 }
2343
2344 test "revision store leaves reserved workspace bytes for producers to write" {
2345 const backing = try std.testing.allocator.alloc(u8, 4 * 1024 * 1024);
2346 defer std.testing.allocator.free(backing);
2347 @memset(backing, 0x5a);
2348 var fixed = std.heap.FixedBufferAllocator.init(backing);
2349 const owner = try Store.create(fixed.allocator(), test_limits);
2350 defer owner.release();
2351 const kind = try owner.register(testContract());
2352 owner.freeze();
2353 const builder = try owner.begin(testRequest(kind), test_work);
2354 defer {
2355 if (builder.abort(.rejected)) |failure| {
2356 var owned = failure;
2357 owned.deinit();
2358 }
2359 }
2360 const producer = try builder.acquireWorkspace(.producer);
2361 defer producer.release();
2362 const scratch = try builder.acquireWorkspace(.scratch);
2363 defer scratch.release();
2364 try std.testing.expect(producer.bytes().len > 0);
2365 try std.testing.expect(std.mem.allEqual(u8, producer.bytes(), 0x5a));
2366 try std.testing.expect(std.mem.allEqual(u8, scratch.bytes(), 0x5a));
2367 }
2368
2369 test "revision store changed workspace regions miss before compiler work without refund" {
2370 const owner = try Store.create(std.testing.allocator, test_limits);
2371 defer owner.release();
2372 const kind = try owner.register(testContract());
2373 owner.freeze();
2374 const baseline = try baselineWorkRevision(owner, kind);
2375 defer baseline.release();
2376 const cases = [_]WorkspaceReuseCase{
2377 .{ .workspace = test_work.workspace - 1 },
2378 .{ .workspace = test_work.workspace + 1 },
2379 .{ .scratch = test_limits.gate_scratch_bytes - 1 },
2380 .{ .scratch = test_limits.gate_scratch_bytes + 1 },
2381 .{ .workspace = test_work.workspace + 1, .scratch = test_limits.gate_scratch_bytes + 1 },
2382 .{ .reuse = true },
2383 };
2384 for (cases) |request| try checkWorkspaceCandidate(baseline, request);
2385 }