perf(artifacts)!: name each edge's child through its occurrence (#1493)

Every inventory edge is created from one occurrence and copied that
occurrence's artifact_id and logical_path as child_artifact_id and
logical_path. Edges now keep the occurrence_id reference alone. Edge IDs
still hash the same child and path, read from the occurrence, so they are
unchanged; validation and artifact comparison read the occurrence too.

inspect_artifact output: Transmission.app 1.10 MB to 0.97 MB, Visual
Studio Code.app 15.37 MB to 13.48 MB.

BREAKING CHANGE: Artifact inventory edges no longer include
child_artifact_id or logical_path; read them from the occurrence named by
occurrence_id. The graph commitment changes, so inventory Evidence
recorded by earlier versions no longer validates.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822andClaude Opus 5.5 authored and GitHub committed 2026-10-10 00:58:05 -07:00
1 parent 1f692c83e2
commit a0e5e9db2b
8 files changed
+45 -39

No files matched your search

@@ -249,16 +249,14 @@ export const createArtifactEdges = (
rootArtifactId;
const semantic = {
parent_artifact_id: parentArtifactId,
child_artifact_id: occurrence.artifact_id,
relation:
occurrence.entry_kind === "slice"
? ("slice-of" as const)
: relationFor(occurrence.logical_path),
occurrence_id: occurrence.occurrence_id,
logical_path: occurrence.logical_path,
};
edges.push({
edge_id: artifactEdgeId(semantic),
edge_id: artifactEdgeId(semantic, occurrence),
...semantic,
producer: occurrence.entry_kind === "slice" ? (producer ?? null) : null,
ordinal: edges.length,
+7 -6
View File
@@ -350,19 +350,20 @@ const relationsByPath = (
inventory.occurrences.map((item) => [item.occurrence_id, item]),
);
for (const edge of inventory.edges) {
const path = edge.logical_path ?? ".";
const values = output.get(path);
const occurrence = occurrences.get(edge.occurrence_id);
// An incomplete inventory can omit the occurrence that names the child.
if (occurrence?.artifact_id === null || occurrence === undefined) continue;
const path = occurrence.logical_path;
const values = output.get(path);
const projection: RelationProjection = {
parent_logical_path:
occurrence?.parent_occurrence_id === null ||
occurrence?.parent_occurrence_id === undefined
occurrence.parent_occurrence_id === null
? null
: (occurrences.get(occurrence.parent_occurrence_id)?.logical_path ??
null),
child_artifact_id: edge.child_artifact_id,
child_artifact_id: occurrence.artifact_id,
relation: edge.relation,
logical_path: edge.logical_path,
logical_path: occurrence.logical_path,
producer: edge.producer,
};
if (values === undefined) output.set(path, [projection]);
+4 -3
View File
@@ -151,12 +151,14 @@ export const integrityContradictionSchema = z.object({
limitations: z.array(z.string()).min(1),
});
/** Parent-child derivation relation between two content-addressed artifacts. */
/**
* Parent-child derivation relation between two content-addressed artifacts.
* The child artifact and its logical path are those of the edge's occurrence.
*/
const artifactEdgeSchema = z.object({
edge_id: edgeIdSchema,
ordinal: z.number().int().min(0),
parent_artifact_id: artifactIdSchema,
child_artifact_id: artifactIdSchema,
relation: z.enum([
"contains",
"extracts",
@@ -167,7 +169,6 @@ const artifactEdgeSchema = z.object({
"derived-from",
]),
occurrence_id: occurrenceIdSchema,
logical_path: boundedRelativePathSchema.nullable(),
producer: artifactCommandSchema.nullable(),
});
+17 -10
View File
@@ -71,14 +71,21 @@ export const artifactContradictionId = (input: {
"Artifact",
)}`;
/** Identify an edge by its semantic relationship and exact occurrence. */
/**
* Identify an edge by its semantic relationship and exact occurrence. The
* child artifact and logical path come from that occurrence.
*/
export const artifactEdgeId = (
semantic: Pick<
ArtifactEdge,
| "parent_artifact_id"
| "child_artifact_id"
| "relation"
| "occurrence_id"
| "logical_path"
>,
): string => `edge_${digestCanonicalValue(semantic, "Artifact")}`;
edge: Pick<ArtifactEdge, "parent_artifact_id" | "relation" | "occurrence_id">,
occurrence: Pick<ArtifactOccurrence, "artifact_id" | "logical_path">,
): string =>
`edge_${digestCanonicalValue(
{
parent_artifact_id: edge.parent_artifact_id,
child_artifact_id: occurrence.artifact_id,
relation: edge.relation,
occurrence_id: edge.occurrence_id,
logical_path: occurrence.logical_path,
},
"Artifact",
)}`;
+13 -12
View File
@@ -211,8 +211,14 @@ const validateCompleteInventory = (inventory: InventorySet): void => {
);
for (const occurrence of inventory.occurrences)
validateOccurrence(occurrence, inventory, nodeIds, occurrenceIds);
const occurrencesById = new Map(
inventory.occurrences.map((occurrence) => [
occurrence.occurrence_id,
occurrence,
]),
);
for (const edge of inventory.edges)
validateEdge(edge, nodeIds, occurrenceIds);
validateEdge(edge, nodeIds, occurrencesById);
for (const contradiction of inventory.integrityContradictions) {
if (
!nodeIds.has(contradiction.parent_artifact_id) ||
@@ -273,22 +279,17 @@ const validateOccurrence = (
const validateEdge = (
edge: ArtifactInventoryResult["edges"][number],
nodeIds: ReadonlySet<string>,
occurrenceIds: ReadonlySet<string>,
occurrences: ReadonlyMap<string, ArtifactOccurrence>,
): void => {
const occurrence = occurrences.get(edge.occurrence_id);
if (
!nodeIds.has(edge.parent_artifact_id) ||
!nodeIds.has(edge.child_artifact_id) ||
!occurrenceIds.has(edge.occurrence_id)
occurrence?.artifact_id === null ||
occurrence === undefined ||
!nodeIds.has(occurrence.artifact_id)
)
throw new TypeError("Artifact edge references a missing graph member");
const semantic = {
parent_artifact_id: edge.parent_artifact_id,
child_artifact_id: edge.child_artifact_id,
relation: edge.relation,
occurrence_id: edge.occurrence_id,
logical_path: edge.logical_path,
};
if (edge.edge_id !== artifactEdgeId(semantic))
if (edge.edge_id !== artifactEdgeId(edge, occurrence))
throw new TypeError("Artifact edge ID does not match its identity");
};
@@ -50,10 +50,9 @@ describe("artifact inventory entry order", () => {
const parent = artifactOccurrenceAt(observed, parentPath);
expect(observed.edges).toContainEqual(
expect.objectContaining({
logical_path: path,
occurrence_id: occurrence.occurrence_id,
relation: "contains",
parent_artifact_id: parent.artifact_id,
child_artifact_id: occurrence.artifact_id,
}),
);
}
@@ -75,10 +75,9 @@ describe("expanded ASAR inventory containment", () => {
const child = artifactOccurrenceAt(nested, path);
expect(nested.edges).toContainEqual(
expect.objectContaining({
logical_path: path,
occurrence_id: child.occurrence_id,
relation: "contains",
parent_artifact_id: artifactOccurrenceAt(nested, parent).artifact_id,
child_artifact_id: child.artifact_id,
}),
);
if (path.startsWith("resources/app.asar/"))
@@ -65,7 +65,7 @@ describe("artifact directory inventory", () => {
expect.objectContaining({
relation: "maps-source",
parent_artifact_id: script?.artifact_id,
child_artifact_id: sourceMap?.artifact_id,
occurrence_id: sourceMap?.occurrence_id,
}),
]),
);