lib/closure/src/discover/construct.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const schema = @import("../schema/root.zig");
3
4 pub const NodeInput = struct {
5 id: schema.NodeId,
6 descriptor: schema.Descriptor,
7 identity: schema.Digest,
8 subject_kind: schema.SubjectKind,
9 material_role: schema.MaterialRole,
10 origin: schema.Origin,
11 phases: schema.PhaseSet,
12 execution_locus: schema.ExecutionLocus,
13 owner: schema.Name,
14 authority: schema.AuthorityClass,
15 artifact_kind: schema.ArtifactKind,
16 };
17
18 pub const CategorizedNodeInput = struct {
19 id: schema.NodeId,
20 descriptor: schema.Descriptor,
21 identity: schema.Digest,
22 material_role: schema.MaterialRole,
23 origin: schema.Origin,
24 phases: schema.PhaseSet,
25 execution_locus: schema.ExecutionLocus,
26 owner: schema.Name,
27 authority: schema.AuthorityClass,
28 };
29
30 pub const ArtifactInput = struct {
31 id: schema.RecordId,
32 node: schema.NodeId,
33 byte_length: u64,
34 digest: schema.Digest,
35 witness: schema.WitnessKind,
36 };
37
38 pub const RangeInput = struct {
39 id: schema.RecordId,
40 artifact: schema.RecordId,
41 offset: u64,
42 length: u64,
43 digest: schema.Digest,
44 witness: schema.WitnessKind,
45 };
46
47 pub const SourceInput = struct {
48 id: schema.RecordId,
49 node: schema.NodeId,
50 path: schema.Descriptor,
51 digest: schema.Digest,
52 witness: schema.WitnessKind,
53 };
54
55 pub const BuildInput = struct {
56 id: schema.RecordId,
57 node: schema.NodeId,
58 tool: schema.NodeId,
59 option: schema.Descriptor,
60 digest: schema.Digest,
61 witness: schema.WitnessKind,
62 };
63
64 pub const RelationInput = struct {
65 id: schema.RecordId,
66 source: schema.NodeId,
67 target: schema.NodeId,
68 };
69
70 pub const TypedRelationInput = struct {
71 id: schema.RecordId,
72 source: schema.NodeId,
73 target: schema.NodeId,
74 kind: schema.EdgeKind,
75 };
76
77 pub const EvidenceInput = struct {
78 id: schema.RecordId,
79 node: schema.NodeId,
80 purpose: schema.Name,
81 digest: schema.Digest,
82 witness: schema.WitnessKind,
83 };
84
85 pub const ProvenanceInput = struct {
86 id: schema.RecordId,
87 child: schema.NodeId,
88 parent: schema.NodeId,
89 };
90
91 pub const AuthorityInput = struct {
92 id: schema.RecordId,
93 source: schema.NodeId,
94 target: schema.NodeId,
95 granted: schema.GrantedAuthority,
96 };
97
98 pub const ServiceInput = struct {
99 id: schema.RecordId,
100 node: schema.NodeId,
101 provider: schema.Descriptor,
102 protocol: schema.Descriptor,
103 endpoint_rule: schema.Descriptor,
104 trust_anchor: schema.Descriptor,
105 failure_contract: schema.Descriptor,
106 requirement: schema.RequirementClass,
107 };
108
109 pub const LineageInput = struct {
110 id: schema.RecordId,
111 node: schema.NodeId,
112 descriptor: schema.Descriptor,
113 digest: schema.Digest,
114 witness: schema.WitnessKind,
115 };
116
117 pub fn node(input: NodeInput) schema.Node {
118 return .{
119 .id = input.id,
120 .descriptor = input.descriptor,
121 .identity = input.identity,
122 .subject_kind = input.subject_kind,
123 .material_role = input.material_role,
124 .origin = input.origin,
125 .phases = input.phases,
126 .execution_locus = input.execution_locus,
127 .owner = input.owner,
128 .authority = input.authority,
129 .artifact_kind = input.artifact_kind,
130 };
131 }
132
133 pub fn firmware(input: CategorizedNodeInput) schema.Node {
134 return categorizedNode(input, .firmware, .firmware);
135 }
136
137 pub fn hardware(input: CategorizedNodeInput) schema.Node {
138 return categorizedNode(input, .hardware, .hardware);
139 }
140
141 pub fn trust(input: CategorizedNodeInput) schema.Node {
142 return categorizedNode(input, .trust, .trust);
143 }
144
145 pub fn deliveredFile(input: ArtifactInput) schema.Artifact {
146 return artifact(input);
147 }
148
149 pub fn generatedArtifact(input: ArtifactInput) schema.Artifact {
150 return artifact(input);
151 }
152
153 pub fn executableRange(input: RangeInput) schema.ByteRange {
154 return .{
155 .id = input.id,
156 .artifact = input.artifact,
157 .offset = input.offset,
158 .length = input.length,
159 .digest = input.digest,
160 .executable = true,
161 .witness = input.witness,
162 };
163 }
164
165 pub fn sourceFile(input: SourceInput) schema.SourceRecord {
166 return .{
167 .id = input.id,
168 .node = input.node,
169 .path = input.path,
170 .digest = input.digest,
171 .witness = input.witness,
172 };
173 }
174
175 pub fn buildOption(input: BuildInput) schema.BuildRecord {
176 return .{
177 .id = input.id,
178 .node = input.node,
179 .tool = input.tool,
180 .option = input.option,
181 .digest = input.digest,
182 .witness = input.witness,
183 };
184 }
185
186 pub fn relation(input: TypedRelationInput) schema.Edge {
187 return .{
188 .id = input.id,
189 .source = input.source,
190 .target = input.target,
191 .kind = input.kind,
192 };
193 }
194
195 pub fn sourceImport(input: RelationInput) schema.Edge {
196 return typedRelation(input, .build_influence);
197 }
198
199 pub fn buildImport(input: RelationInput) schema.Edge {
200 return typedRelation(input, .build_influence);
201 }
202
203 pub fn generatedBy(input: RelationInput) schema.Edge {
204 return typedRelation(input, .derivation);
205 }
206
207 pub fn serviceEffect(input: RelationInput) schema.Edge {
208 return typedRelation(input, .service_call);
209 }
210
211 pub fn symbol(input: EvidenceInput) schema.DigestRecord {
212 return evidence(input);
213 }
214
215 pub fn relocation(input: EvidenceInput) schema.DigestRecord {
216 return evidence(input);
217 }
218
219 pub fn license(input: EvidenceInput) schema.DigestRecord {
220 return evidence(input);
221 }
222
223 pub fn provenance(input: ProvenanceInput) schema.ProvenanceParent {
224 return .{
225 .id = input.id,
226 .child = input.child,
227 .parent = input.parent,
228 };
229 }
230
231 pub fn authority(input: AuthorityInput) schema.Authority {
232 return .{
233 .id = input.id,
234 .source = input.source,
235 .target = input.target,
236 .granted = input.granted,
237 };
238 }
239
240 pub fn service(input: ServiceInput) schema.ServiceDescriptor {
241 return .{
242 .id = input.id,
243 .node = input.node,
244 .provider = input.provider,
245 .protocol = input.protocol,
246 .endpoint_rule = input.endpoint_rule,
247 .trust_anchor = input.trust_anchor,
248 .failure_contract = input.failure_contract,
249 .requirement = input.requirement,
250 };
251 }
252
253 pub fn lineage(input: LineageInput) schema.LineageReference {
254 return .{
255 .id = input.id,
256 .node = input.node,
257 .descriptor = input.descriptor,
258 .digest = input.digest,
259 .witness = input.witness,
260 };
261 }
262
263 fn categorizedNode(
264 input: CategorizedNodeInput,
265 subject_kind: schema.SubjectKind,
266 artifact_kind: schema.ArtifactKind,
267 ) schema.Node {
268 return node(.{
269 .id = input.id,
270 .descriptor = input.descriptor,
271 .identity = input.identity,
272 .subject_kind = subject_kind,
273 .material_role = input.material_role,
274 .origin = input.origin,
275 .phases = input.phases,
276 .execution_locus = input.execution_locus,
277 .owner = input.owner,
278 .authority = input.authority,
279 .artifact_kind = artifact_kind,
280 });
281 }
282
283 fn artifact(input: ArtifactInput) schema.Artifact {
284 return .{
285 .id = input.id,
286 .node = input.node,
287 .byte_length = input.byte_length,
288 .digest = input.digest,
289 .witness = input.witness,
290 };
291 }
292
293 fn typedRelation(
294 input: RelationInput,
295 kind: schema.EdgeKind,
296 ) schema.Edge {
297 return relation(.{
298 .id = input.id,
299 .source = input.source,
300 .target = input.target,
301 .kind = kind,
302 });
303 }
304
305 fn evidence(input: EvidenceInput) schema.DigestRecord {
306 return .{
307 .id = input.id,
308 .node = input.node,
309 .purpose = input.purpose,
310 .digest = input.digest,
311 .witness = input.witness,
312 };
313 }
314
315 test "file range source build and relation constructors preserve facts" {
316 const digest = knownDigest(1);
317 const file = deliveredFile(.{
318 .id = 10,
319 .node = 2,
320 .byte_length = 512,
321 .digest = digest,
322 .witness = .owned,
323 });
324 const range = executableRange(.{
325 .id = 11,
326 .artifact = file.id,
327 .offset = 64,
328 .length = 32,
329 .digest = digest,
330 .witness = .differential,
331 });
332 const source = sourceFile(.{
333 .id = 12,
334 .node = 1,
335 .path = try schema.Descriptor.init("src/kernel.zig"),
336 .digest = digest,
337 .witness = .owned,
338 });
339 const build = buildOption(.{
340 .id = 13,
341 .node = 2,
342 .tool = 3,
343 .option = try schema.Descriptor.init("-fno-stack-protector"),
344 .digest = digest,
345 .witness = .owned,
346 });
347 try std.testing.expectEqual(@as(u64, 512), file.byte_length);
348 try std.testing.expect(range.executable);
349 try std.testing.expectEqualStrings("src/kernel.zig", source.path.slice());
350 try std.testing.expectEqual(@as(schema.NodeId, 3), build.tool);
351 try std.testing.expectEqual(
352 schema.EdgeKind.build_influence,
353 sourceImport(.{ .id = 14, .source = 1, .target = 2 }).kind,
354 );
355 }
356
357 test "category and evidence constructors retain explicit classification" {
358 const common = CategorizedNodeInput{
359 .id = 4,
360 .descriptor = try schema.Descriptor.init("platform firmware"),
361 .identity = knownDigest(2),
362 .material_role = .residual_root,
363 .origin = .platform_supplied,
364 .phases = schema.phaseBit(.pre_handoff_boot),
365 .execution_locus = .firmware_management_cpu,
366 .owner = try schema.Name.init("platform"),
367 .authority = .platform_control,
368 };
369 const firmware_node = firmware(common);
370 const hardware_node = hardware(common);
371 const trust_node = trust(common);
372 const purpose = try schema.Name.init("license");
373 const proof = license(.{
374 .id = 15,
375 .node = 4,
376 .purpose = purpose,
377 .digest = knownDigest(3),
378 .witness = .owned,
379 });
380 try std.testing.expectEqual(schema.SubjectKind.firmware, firmware_node.subject_kind);
381 try std.testing.expectEqual(schema.ArtifactKind.hardware, hardware_node.artifact_kind);
382 try std.testing.expectEqual(schema.SubjectKind.trust, trust_node.subject_kind);
383 try std.testing.expectEqualStrings("license", proof.purpose.slice());
384 }
385
386 test "service authority provenance and lineage constructors are policy free" {
387 const relation_input = RelationInput{ .id = 20, .source = 1, .target = 2 };
388 const effect = serviceEffect(relation_input);
389 const grant = authority(.{
390 .id = 21,
391 .source = 1,
392 .target = 2,
393 .granted = .network,
394 });
395 const parent = provenance(.{ .id = 22, .child = 2, .parent = 1 });
396 const service_record = service(.{
397 .id = 23,
398 .node = 2,
399 .provider = try schema.Descriptor.init("private"),
400 .protocol = try schema.Descriptor.init("tls"),
401 .endpoint_rule = try schema.Descriptor.init("fixed"),
402 .trust_anchor = try schema.Descriptor.init("pinned"),
403 .failure_contract = try schema.Descriptor.init("fail closed"),
404 .requirement = .required,
405 });
406 const lineage_record = lineage(.{
407 .id = 24,
408 .node = 2,
409 .descriptor = try schema.Descriptor.init("source generation"),
410 .digest = knownDigest(4),
411 .witness = .differential,
412 });
413 try std.testing.expectEqual(schema.EdgeKind.service_call, effect.kind);
414 try std.testing.expectEqual(schema.GrantedAuthority.network, grant.granted);
415 try std.testing.expectEqual(@as(schema.NodeId, 1), parent.parent);
416 try std.testing.expectEqual(schema.RequirementClass.required, service_record.requirement);
417 try std.testing.expectEqual(schema.WitnessKind.differential, lineage_record.witness);
418 }
419
420 test "generic generated import and evidence constructors preserve inputs" {
421 const subject = node(.{
422 .id = 30,
423 .descriptor = try schema.Descriptor.init("generated binary"),
424 .identity = knownDigest(5),
425 .subject_kind = .binary,
426 .material_role = .platform,
427 .origin = .owned_derivation,
428 .phases = schema.phaseBit(.shipment),
429 .execution_locus = .normal_world_cpu,
430 .owner = try schema.Name.init("os"),
431 .authority = .platform_control,
432 .artifact_kind = .executable,
433 });
434 const generated = generatedArtifact(.{
435 .id = 31,
436 .node = subject.id,
437 .byte_length = 128,
438 .digest = knownDigest(6),
439 .witness = .owned,
440 });
441 const relation_record = relation(.{
442 .id = 32,
443 .source = 1,
444 .target = subject.id,
445 .kind = .shipment,
446 });
447 const import_record = buildImport(.{ .id = 33, .source = 1, .target = 30 });
448 const derivation = generatedBy(.{ .id = 34, .source = 1, .target = 30 });
449 const purpose = try schema.Name.init("symbols");
450 const symbol_record = symbol(.{
451 .id = 35,
452 .node = 30,
453 .purpose = purpose,
454 .digest = knownDigest(7),
455 .witness = .owned,
456 });
457 const relocation_record = relocation(.{
458 .id = 36,
459 .node = 30,
460 .purpose = try schema.Name.init("relocations"),
461 .digest = knownDigest(8),
462 .witness = .differential,
463 });
464 try std.testing.expectEqual(@as(u64, 128), generated.byte_length);
465 try std.testing.expectEqual(schema.EdgeKind.shipment, relation_record.kind);
466 try std.testing.expectEqual(schema.EdgeKind.build_influence, import_record.kind);
467 try std.testing.expectEqual(schema.EdgeKind.derivation, derivation.kind);
468 try std.testing.expectEqualStrings("symbols", symbol_record.purpose.slice());
469 try std.testing.expectEqual(schema.WitnessKind.differential, relocation_record.witness);
470 }
471
472 fn knownDigest(byte: u8) schema.Digest {
473 return .{ .bytes = @splat(byte) };
474 }