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 }