lib/closure/src/receipt/rows.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const pretty_json = @import("pretty").json;
3 const closure = @import("../root.zig");
4 const model = @import("model.zig");
5 const schema = @import("../schema/root.zig");
6
7 const limits = closure.limits;
8
9 const JsonWriter = pretty_json.Writer;
10
11 pub fn emit(
12 writer: *std.Io.Writer,
13 input: schema.GenerationInput,
14 selected: *const schema.Claim,
15 context: model.Context,
16 ) std.Io.Writer.Error!void {
17 try generationRow(writer, input, selected, context);
18 try graphRows(writer, input);
19 try artifactRows(writer, input);
20 try evidenceRows(writer, input);
21 try policyRows(writer, input);
22 try claimRows(writer, input, selected.id, context);
23 }
24
25 fn graphRows(writer: *std.Io.Writer, input: schema.GenerationInput) std.Io.Writer.Error!void {
26 for (input.nodes) |*node| try nodeRow(writer, input.id, node);
27 for (input.edges) |*edge| try edgeRow(writer, input.id, edge);
28 }
29
30 fn artifactRows(writer: *std.Io.Writer, input: schema.GenerationInput) std.Io.Writer.Error!void {
31 for (input.artifacts) |*artifact| {
32 try artifactRow(writer, input.id, artifact);
33 }
34 for (input.ranges) |*range| try rangeRow(writer, input.id, range);
35 for (input.digests) |*digest| try digestRow(writer, input.id, digest);
36 }
37
38 fn evidenceRows(writer: *std.Io.Writer, input: schema.GenerationInput) std.Io.Writer.Error!void {
39 for (input.provenance_parents) |*parent| {
40 try provenanceRow(writer, input.id, parent);
41 }
42 for (input.source_records) |*source| {
43 try sourceRow(writer, input.id, source);
44 }
45 for (input.build_records) |*build| {
46 try buildRow(writer, input.id, build);
47 }
48 }
49
50 fn policyRows(writer: *std.Io.Writer, input: schema.GenerationInput) std.Io.Writer.Error!void {
51 for (input.authorities) |*authority| {
52 try authorityRow(writer, input.id, authority);
53 }
54 for (input.lineage_references) |*lineage| {
55 try lineageRow(writer, input.id, lineage);
56 }
57 for (input.service_descriptors) |*service| {
58 try serviceRow(writer, input.id, service);
59 }
60 for (input.residual_roots) |*root| {
61 try residualRootRow(writer, input.id, root);
62 }
63 }
64
65 fn claimRows(
66 writer: *std.Io.Writer,
67 input: schema.GenerationInput,
68 selected: schema.ClaimId,
69 context: model.Context,
70 ) std.Io.Writer.Error!void {
71 for (input.claims) |*claim| {
72 try claimRow(writer, input.id, claim, selected, context);
73 }
74 for (input.claim_nodes) |*node| {
75 try claimNodeRow(writer, input.id, node);
76 }
77 }
78
79 fn generationRow(
80 writer: *std.Io.Writer,
81 input: schema.GenerationInput,
82 selected: *const schema.Claim,
83 context: model.Context,
84 ) std.Io.Writer.Error!void {
85 var stream = JsonWriter.init(writer, .minified);
86 const sink = &stream;
87 try rowStart(sink, "generation", input.id);
88 try numberField(sink, "selected_claim", selected.id);
89 try digestField(sink, "root", &input.root);
90 try digestField(sink, "before_root", &context.before_root);
91 try digestField(sink, "after_root", &context.after_root);
92 try enumField(sink, "verdict", context.verdict);
93 try optionalStringField(sink, "reason", context.reason);
94 try optionalStringField(sink, "rejection", context.rejection);
95 try optionalStringField(sink, "unknown", context.unknown);
96 try objectField(sink, "limits");
97 try writeCounts(sink, model.Counts.fromLimits(context.limits));
98 try objectField(sink, "capacity");
99 try writeCapacity(sink, context.capacity);
100 try objectField(sink, "occupancy");
101 try writeCounts(sink, context.occupancy);
102 try objectField(sink, "high_water");
103 try writeCounts(sink, context.high_water);
104 try numberField(sink, "work", context.work);
105 try numberField(sink, "visited_high_water", context.visited_high_water);
106 try numberField(sink, "queue_high_water", context.queue_high_water);
107 try numberField(sink, "allocation_count", 0);
108 try objectField(sink, "profiles");
109 try writeProfiles(sink, &selected.profiles);
110 try rowEnd(sink);
111 }
112
113 fn nodeRow(
114 writer: *std.Io.Writer,
115 generation: schema.GenerationId,
116 node: *const schema.Node,
117 ) std.Io.Writer.Error!void {
118 var stream = JsonWriter.init(writer, .minified);
119 const sink = &stream;
120 try rowStart(sink, "node", generation);
121 try numberField(sink, "id", node.id);
122 try stringField(sink, "descriptor", node.descriptor.slice());
123 try digestField(sink, "identity", &node.identity);
124 try enumField(sink, "subject_kind", node.subject_kind);
125 try enumField(sink, "material_role", node.material_role);
126 try enumField(sink, "origin", node.origin);
127 try numberField(sink, "phase_bits", node.phases);
128 try phaseField(sink, node.phases);
129 try enumField(sink, "execution_locus", node.execution_locus);
130 try stringField(sink, "owner", node.owner.slice());
131 try enumField(sink, "authority", node.authority);
132 try enumField(sink, "artifact_kind", node.artifact_kind);
133 try rowEnd(sink);
134 }
135
136 fn edgeRow(
137 writer: *std.Io.Writer,
138 generation: schema.GenerationId,
139 edge: *const schema.Edge,
140 ) std.Io.Writer.Error!void {
141 var stream = JsonWriter.init(writer, .minified);
142 const sink = &stream;
143 try rowStart(sink, "edge", generation);
144 try numberField(sink, "id", edge.id);
145 try numberField(sink, "source", edge.source);
146 try numberField(sink, "target", edge.target);
147 try enumField(sink, "kind", edge.kind);
148 try rowEnd(sink);
149 }
150
151 fn artifactRow(
152 writer: *std.Io.Writer,
153 generation: schema.GenerationId,
154 artifact: *const schema.Artifact,
155 ) std.Io.Writer.Error!void {
156 var stream = JsonWriter.init(writer, .minified);
157 const sink = &stream;
158 try rowStart(sink, "artifact", generation);
159 try numberField(sink, "id", artifact.id);
160 try numberField(sink, "node", artifact.node);
161 try numberField(sink, "byte_length", artifact.byte_length);
162 try digestField(sink, "digest", &artifact.digest);
163 try enumField(sink, "witness", artifact.witness);
164 try rowEnd(sink);
165 }
166
167 fn rangeRow(
168 writer: *std.Io.Writer,
169 generation: schema.GenerationId,
170 range: *const schema.ByteRange,
171 ) std.Io.Writer.Error!void {
172 var stream = JsonWriter.init(writer, .minified);
173 const sink = &stream;
174 try rowStart(sink, "range", generation);
175 try numberField(sink, "id", range.id);
176 try numberField(sink, "artifact", range.artifact);
177 try numberField(sink, "offset", range.offset);
178 try numberField(sink, "length", range.length);
179 try digestField(sink, "digest", &range.digest);
180 try booleanField(sink, "executable", range.executable);
181 try enumField(sink, "witness", range.witness);
182 try rowEnd(sink);
183 }
184
185 fn digestRow(
186 writer: *std.Io.Writer,
187 generation: schema.GenerationId,
188 digest: *const schema.DigestRecord,
189 ) std.Io.Writer.Error!void {
190 var stream = JsonWriter.init(writer, .minified);
191 const sink = &stream;
192 try rowStart(sink, "digest", generation);
193 try numberField(sink, "id", digest.id);
194 try numberField(sink, "node", digest.node);
195 try stringField(sink, "purpose", digest.purpose.slice());
196 try digestField(sink, "digest", &digest.digest);
197 try enumField(sink, "witness", digest.witness);
198 try rowEnd(sink);
199 }
200
201 fn provenanceRow(
202 writer: *std.Io.Writer,
203 generation: schema.GenerationId,
204 parent: *const schema.ProvenanceParent,
205 ) std.Io.Writer.Error!void {
206 var stream = JsonWriter.init(writer, .minified);
207 const sink = &stream;
208 try rowStart(sink, "provenance", generation);
209 try numberField(sink, "id", parent.id);
210 try numberField(sink, "child", parent.child);
211 try numberField(sink, "parent", parent.parent);
212 try rowEnd(sink);
213 }
214
215 fn sourceRow(
216 writer: *std.Io.Writer,
217 generation: schema.GenerationId,
218 source: *const schema.SourceRecord,
219 ) std.Io.Writer.Error!void {
220 var stream = JsonWriter.init(writer, .minified);
221 const sink = &stream;
222 try rowStart(sink, "source", generation);
223 try numberField(sink, "id", source.id);
224 try numberField(sink, "node", source.node);
225 try stringField(sink, "path", source.path.slice());
226 try digestField(sink, "digest", &source.digest);
227 try enumField(sink, "witness", source.witness);
228 try rowEnd(sink);
229 }
230
231 fn buildRow(
232 writer: *std.Io.Writer,
233 generation: schema.GenerationId,
234 build: *const schema.BuildRecord,
235 ) std.Io.Writer.Error!void {
236 var stream = JsonWriter.init(writer, .minified);
237 const sink = &stream;
238 try rowStart(sink, "build", generation);
239 try numberField(sink, "id", build.id);
240 try numberField(sink, "node", build.node);
241 try numberField(sink, "tool", build.tool);
242 try stringField(sink, "option", build.option.slice());
243 try digestField(sink, "digest", &build.digest);
244 try enumField(sink, "witness", build.witness);
245 try rowEnd(sink);
246 }
247
248 fn authorityRow(
249 writer: *std.Io.Writer,
250 generation: schema.GenerationId,
251 authority: *const schema.Authority,
252 ) std.Io.Writer.Error!void {
253 var stream = JsonWriter.init(writer, .minified);
254 const sink = &stream;
255 try rowStart(sink, "authority", generation);
256 try numberField(sink, "id", authority.id);
257 try numberField(sink, "source", authority.source);
258 try numberField(sink, "target", authority.target);
259 try enumField(sink, "granted", authority.granted);
260 try rowEnd(sink);
261 }
262
263 fn lineageRow(
264 writer: *std.Io.Writer,
265 generation: schema.GenerationId,
266 lineage: *const schema.LineageReference,
267 ) std.Io.Writer.Error!void {
268 var stream = JsonWriter.init(writer, .minified);
269 const sink = &stream;
270 try rowStart(sink, "lineage", generation);
271 try numberField(sink, "id", lineage.id);
272 try numberField(sink, "node", lineage.node);
273 try stringField(sink, "descriptor", lineage.descriptor.slice());
274 try digestField(sink, "digest", &lineage.digest);
275 try enumField(sink, "witness", lineage.witness);
276 try rowEnd(sink);
277 }
278
279 fn serviceRow(
280 writer: *std.Io.Writer,
281 generation: schema.GenerationId,
282 service: *const schema.ServiceDescriptor,
283 ) std.Io.Writer.Error!void {
284 var stream = JsonWriter.init(writer, .minified);
285 const sink = &stream;
286 try rowStart(sink, "service", generation);
287 try numberField(sink, "id", service.id);
288 try numberField(sink, "node", service.node);
289 try stringField(sink, "provider", service.provider.slice());
290 try stringField(sink, "protocol", service.protocol.slice());
291 try stringField(sink, "endpoint_rule", service.endpoint_rule.slice());
292 try stringField(sink, "trust_anchor", service.trust_anchor.slice());
293 try stringField(sink, "failure_contract", service.failure_contract.slice());
294 try enumField(sink, "requirement", service.requirement);
295 try rowEnd(sink);
296 }
297
298 fn residualRootRow(
299 writer: *std.Io.Writer,
300 generation: schema.GenerationId,
301 root: *const schema.ResidualRoot,
302 ) std.Io.Writer.Error!void {
303 var stream = JsonWriter.init(writer, .minified);
304 const sink = &stream;
305 try rowStart(sink, "root", generation);
306 try numberField(sink, "id", root.id);
307 try numberField(sink, "claim", root.claim);
308 try numberField(sink, "node", root.node);
309 try enumField(sink, "treatment", root.treatment);
310 try rowEnd(sink);
311 }
312
313 fn claimRow(
314 writer: *std.Io.Writer,
315 generation: schema.GenerationId,
316 claim: *const schema.Claim,
317 selected_id: schema.ClaimId,
318 context: model.Context,
319 ) std.Io.Writer.Error!void {
320 var stream = JsonWriter.init(writer, .minified);
321 const sink = &stream;
322 const selected = claim.id == selected_id;
323 try rowStart(sink, "claim", generation);
324 try numberField(sink, "id", claim.id);
325 try stringField(sink, "name", claim.name.slice());
326 try numberField(sink, "artifact", claim.artifact);
327 try digestField(sink, "artifact_digest", &claim.artifact_digest);
328 try objectField(sink, "profiles");
329 try writeProfiles(sink, &claim.profiles);
330 try numberField(sink, "traversed_edge_bits", claim.traversed_edges);
331 try edgeSetField(sink, claim.traversed_edges);
332 try booleanField(sink, "selected", selected);
333 try enumField(
334 sink,
335 "verdict",
336 if (selected) context.verdict else schema.Verdict.not_exercised,
337 );
338 try optionalStringField(sink, "reason", if (selected) context.reason else null);
339 try optionalStringField(sink, "rejection", if (selected) context.rejection else null);
340 try optionalStringField(sink, "unknown", if (selected) context.unknown else null);
341 try rowEnd(sink);
342 }
343
344 fn claimNodeRow(
345 writer: *std.Io.Writer,
346 generation: schema.GenerationId,
347 node: *const schema.ClaimNode,
348 ) std.Io.Writer.Error!void {
349 var stream = JsonWriter.init(writer, .minified);
350 const sink = &stream;
351 try rowStart(sink, "claim-node", generation);
352 try numberField(sink, "id", node.id);
353 try numberField(sink, "claim", node.claim);
354 try numberField(sink, "node", node.node);
355 try enumField(sink, "treatment", node.treatment);
356 try rowEnd(sink);
357 }
358
359 fn rowStart(
360 sink: *JsonWriter,
361 family: []const u8,
362 generation: schema.GenerationId,
363 ) std.Io.Writer.Error!void {
364 var schema_buffer: [64]u8 = undefined;
365 const schema_name = std.fmt.bufPrint(
366 &schema_buffer,
367 "tiny.closure.{s}/v1",
368 .{family},
369 ) catch unreachable;
370 try sink.beginObject();
371 try sink.objectField("schema");
372 try sink.byteString(schema_name);
373 try sink.objectField("vocabulary");
374 try sink.byteString("ClosureManifest/v1");
375 try sink.objectField("generation");
376 try sink.write(generation);
377 }
378
379 fn rowEnd(sink: *JsonWriter) std.Io.Writer.Error!void {
380 try sink.endObject();
381 try sink.newline();
382 }
383
384 fn fieldStart(sink: *JsonWriter, name: []const u8) std.Io.Writer.Error!void {
385 try sink.objectField(name);
386 }
387
388 fn numberField(sink: *JsonWriter, name: []const u8, value: anytype) std.Io.Writer.Error!void {
389 try fieldStart(sink, name);
390 try sink.write(value);
391 }
392
393 fn booleanField(sink: *JsonWriter, name: []const u8, value: bool) std.Io.Writer.Error!void {
394 try fieldStart(sink, name);
395 try sink.write(value);
396 }
397
398 fn stringField(
399 sink: *JsonWriter,
400 name: []const u8,
401 value: []const u8,
402 ) std.Io.Writer.Error!void {
403 try fieldStart(sink, name);
404 try sink.byteString(value);
405 }
406
407 fn optionalStringField(
408 sink: *JsonWriter,
409 name: []const u8,
410 value: ?[]const u8,
411 ) std.Io.Writer.Error!void {
412 try fieldStart(sink, name);
413 if (value) |present| {
414 try sink.byteString(present);
415 } else {
416 try sink.write(null);
417 }
418 }
419
420 fn enumField(sink: *JsonWriter, name: []const u8, value: anytype) std.Io.Writer.Error!void {
421 try stringField(sink, name, @tagName(value));
422 }
423
424 fn digestField(
425 sink: *JsonWriter,
426 name: []const u8,
427 digest: *const schema.Digest,
428 ) std.Io.Writer.Error!void {
429 const encoded = digest.hex();
430 try stringField(sink, name, &encoded);
431 }
432
433 fn objectField(sink: *JsonWriter, name: []const u8) std.Io.Writer.Error!void {
434 try fieldStart(sink, name);
435 }
436
437 fn countMembers(
438 sink: *JsonWriter,
439 counts: model.Counts,
440 ) std.Io.Writer.Error!void {
441 inline for (comptime std.meta.fieldNames(model.Counts)) |name| {
442 try sink.objectField(name);
443 try sink.write(@field(counts, name));
444 }
445 }
446
447 fn writeCounts(sink: *JsonWriter, counts: model.Counts) std.Io.Writer.Error!void {
448 try sink.beginObject();
449 try countMembers(sink, counts);
450 try sink.endObject();
451 }
452
453 fn writeCapacity(
454 sink: *JsonWriter,
455 capacity: limits.Capacity,
456 ) std.Io.Writer.Error!void {
457 try sink.beginObject();
458 try countMembers(sink, model.Counts.fromCapacity(capacity));
459 try capacityMember(sink, "projection_steps_max", capacity.projection_steps_max);
460 try capacityMember(sink, "receipt_rows_max", capacity.receipt_rows_max);
461 try capacityMember(sink, "storage_bytes", capacity.storage_bytes);
462 try sink.endObject();
463 }
464
465 fn capacityMember(
466 sink: *JsonWriter,
467 name: []const u8,
468 value: usize,
469 ) std.Io.Writer.Error!void {
470 try sink.objectField(name);
471 try sink.write(value);
472 }
473
474 fn writeProfiles(
475 sink: *JsonWriter,
476 profiles: *const schema.ProfileSet,
477 ) std.Io.Writer.Error!void {
478 try sink.beginObject();
479 try sink.objectField("executable");
480 try writeProfile(sink, &profiles.executable);
481 try sink.objectField("service_trust");
482 try writeProfile(sink, &profiles.service_trust);
483 try sink.objectField("model_origin");
484 try writeProfile(sink, &profiles.model_origin);
485 try sink.objectField("bootstrap");
486 try writeProfile(sink, &profiles.bootstrap);
487 try sink.endObject();
488 }
489
490 fn writeProfile(
491 sink: *JsonWriter,
492 profile: *const schema.ProfileRef,
493 ) std.Io.Writer.Error!void {
494 try sink.beginObject();
495 try sink.objectField("required");
496 try sink.write(profile.required);
497 try sink.objectField("id");
498 try sink.byteString(profile.id.slice());
499 try sink.objectField("source");
500 try sink.byteString(profile.source.slice());
501 try sink.objectField("body_sha256");
502 const digest = profile.body_sha256.hex();
503 try sink.byteString(&digest);
504 try sink.endObject();
505 }
506
507 fn phaseField(sink: *JsonWriter, phases: schema.PhaseSet) std.Io.Writer.Error!void {
508 try fieldStart(sink, "phases");
509 try sink.beginArray();
510 inline for (std.meta.tags(schema.Phase)) |phase| {
511 if (phases & schema.phaseBit(phase) != 0) {
512 try sink.byteString(@tagName(phase));
513 }
514 }
515 try sink.endArray();
516 }
517
518 fn edgeSetField(sink: *JsonWriter, edges: schema.EdgeSet) std.Io.Writer.Error!void {
519 try fieldStart(sink, "traversed_edges");
520 try sink.beginArray();
521 inline for (std.meta.tags(schema.EdgeKind)) |edge| {
522 if (edges & schema.edgeBit(edge) != 0) {
523 try sink.byteString(@tagName(edge));
524 }
525 }
526 try sink.endArray();
527 }