refactor(reference): centralize source inventory rules
This commit is contained in:
1 parent
5a28a147f1
commit
8dbe92a4a3
3 files changed
+50
-62
No files matched your search
@@ -1,9 +1,11 @@
|
||||
import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js";
|
||||
import {
|
||||
createHistoricalSourceGraph,
|
||||
historicalSourceInventoryHasPartialFacts,
|
||||
historicalSourceLanguages,
|
||||
historicalSourceManifests,
|
||||
historicalSourceParseFailureKey,
|
||||
historicalSourceRelationshipKey,
|
||||
type HistoricalSourceGraph,
|
||||
type HistoricalSourceGraphInput,
|
||||
} from "../domain/referenceSourceGraph.js";
|
||||
@@ -45,18 +47,13 @@ const cancelled = (
|
||||
|
||||
const isAborted = (signal?: AbortSignal): boolean => signal?.aborted === true;
|
||||
|
||||
const relationshipKey = (
|
||||
relationship: HistoricalSourceGraphInput["relationships"][number],
|
||||
): string =>
|
||||
`${relationship.from_path}\u0000${relationship.to}\u0000${relationship.kind}\u0000${relationship.resolution}\u0000${relationship.parse_state}`;
|
||||
|
||||
const deduplicateRelationships = (
|
||||
relationships: HistoricalSourceGraphInput["relationships"],
|
||||
): HistoricalSourceGraphInput["relationships"] => {
|
||||
const seen = new Set<string>();
|
||||
const result: HistoricalSourceGraphInput["relationships"] = [];
|
||||
for (const relationship of relationships) {
|
||||
const key = relationshipKey(relationship);
|
||||
const key = historicalSourceRelationshipKey(relationship);
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
result.push(relationship);
|
||||
@@ -93,37 +90,6 @@ const buildProvenance = (
|
||||
caller: options.caller,
|
||||
});
|
||||
|
||||
const deriveInventoryState = (
|
||||
input: Pick<
|
||||
HistoricalSourceGraphInput,
|
||||
| "entries"
|
||||
| "relationships"
|
||||
| "parse_failures"
|
||||
| "exclusions"
|
||||
| "limitations"
|
||||
>,
|
||||
): "complete" | "partial" | "unknown" => {
|
||||
if (input.limitations.length > 0) return "partial";
|
||||
if (input.exclusions.length > 0) return "partial";
|
||||
if (input.parse_failures.length > 0) return "partial";
|
||||
const partialEntry = input.entries.some(
|
||||
(entry) =>
|
||||
entry.limitations.length > 0 ||
|
||||
(entry.kind === "file" && entry.content_state !== "hashed") ||
|
||||
(entry.kind === "directory" && entry.tree_state !== "enumerated") ||
|
||||
(entry.kind === "symlink" && entry.target_state !== "internal") ||
|
||||
entry.kind === "unknown",
|
||||
);
|
||||
if (partialEntry) return "partial";
|
||||
const partialRelationship = input.relationships.some(
|
||||
({ parse_state, resolution }) =>
|
||||
parse_state !== "parsed" ||
|
||||
["unresolved", "unknown"].includes(resolution),
|
||||
);
|
||||
if (partialRelationship) return "partial";
|
||||
return "complete";
|
||||
};
|
||||
|
||||
const sortExclusions = (
|
||||
exclusions: HistoricalSourceGraphInput["exclusions"],
|
||||
): HistoricalSourceGraphInput["exclusions"] =>
|
||||
@@ -261,16 +227,21 @@ export const importReferenceSource = async (
|
||||
schema: "HistoricalSourceGraph",
|
||||
authority: "historical-reference",
|
||||
root_alias: "$REFERENCE_ROOT",
|
||||
inventory_state: deriveInventoryState({
|
||||
inventory_state: historicalSourceInventoryHasPartialFacts({
|
||||
entries: sortedEntries,
|
||||
relationships: uniqueRelationships,
|
||||
parse_failures: uniqueFailures,
|
||||
exclusions: sortedExclusions,
|
||||
limitations: sortedLimitations,
|
||||
}),
|
||||
})
|
||||
? "partial"
|
||||
: "complete",
|
||||
entries: sortedEntries,
|
||||
relationships: uniqueRelationships.sort((left, right) =>
|
||||
compareUnicodeCodePoints(relationshipKey(left), relationshipKey(right)),
|
||||
compareUnicodeCodePoints(
|
||||
historicalSourceRelationshipKey(left),
|
||||
historicalSourceRelationshipKey(right),
|
||||
),
|
||||
),
|
||||
parse_failures: uniqueFailures,
|
||||
exclusions: sortedExclusions,
|
||||
|
||||
@@ -337,6 +337,10 @@ describe("partial historical source graphs", () => {
|
||||
expect(createHistoricalSourceGraph(partial).inventory_state).toBe(
|
||||
"partial",
|
||||
);
|
||||
expect(
|
||||
createHistoricalSourceGraph({ ...partial, inventory_state: "unknown" })
|
||||
.inventory_state,
|
||||
).toBe("unknown");
|
||||
|
||||
for (const change of [
|
||||
{ entries: partial.entries },
|
||||
|
||||
@@ -237,13 +237,43 @@ export type HistoricalSourceManifest = z.infer<
|
||||
typeof historicalSourceManifestBaseSchema
|
||||
>;
|
||||
|
||||
/** Whether source facts prevent an inventory from being claimed complete. */
|
||||
export const historicalSourceInventoryHasPartialFacts = (
|
||||
input: Pick<
|
||||
HistoricalSourceGraphInput,
|
||||
| "entries"
|
||||
| "relationships"
|
||||
| "parse_failures"
|
||||
| "exclusions"
|
||||
| "limitations"
|
||||
>,
|
||||
): boolean =>
|
||||
input.limitations.length > 0 ||
|
||||
input.exclusions.length > 0 ||
|
||||
input.parse_failures.length > 0 ||
|
||||
input.entries.some(
|
||||
(entry) =>
|
||||
entry.limitations.length > 0 ||
|
||||
(entry.kind === "file" && entry.content_state !== "hashed") ||
|
||||
(entry.kind === "directory" && entry.tree_state !== "enumerated") ||
|
||||
(entry.kind === "symlink" && entry.target_state !== "internal") ||
|
||||
entry.kind === "unknown",
|
||||
) ||
|
||||
input.relationships.some(
|
||||
({ parse_state, resolution }) =>
|
||||
parse_state !== "parsed" ||
|
||||
resolution === "unresolved" ||
|
||||
resolution === "unknown",
|
||||
);
|
||||
|
||||
/** Stable identity used to sort and deduplicate source parse failures. */
|
||||
export const historicalSourceParseFailureKey = (
|
||||
failure: HistoricalSourceGraphInput["parse_failures"][number],
|
||||
): string => `${failure.path}\u0000${failure.parser}\u0000${failure.reason}`;
|
||||
|
||||
const relationshipKey = (
|
||||
relationship: z.infer<typeof sourceRelationshipSchema>,
|
||||
/** Stable identity used to sort and deduplicate source relationships. */
|
||||
export const historicalSourceRelationshipKey = (
|
||||
relationship: HistoricalSourceGraphInput["relationships"][number],
|
||||
): string =>
|
||||
`${relationship.from_path}\u0000${relationship.to}\u0000${relationship.kind}\u0000${relationship.resolution}\u0000${relationship.parse_state}`;
|
||||
|
||||
@@ -397,26 +427,9 @@ const checkCompleteInventory = (
|
||||
graph: GraphStructure,
|
||||
context: z.RefinementCtx,
|
||||
): void => {
|
||||
if (graph.inventory_state !== "complete") return;
|
||||
const partialEntry = graph.entries.some(
|
||||
(entry) =>
|
||||
entry.limitations.length > 0 ||
|
||||
(entry.kind === "file" && entry.content_state !== "hashed") ||
|
||||
(entry.kind === "directory" && entry.tree_state !== "enumerated") ||
|
||||
(entry.kind === "symlink" && entry.target_state !== "internal") ||
|
||||
entry.kind === "unknown",
|
||||
);
|
||||
const partialRelationship = graph.relationships.some(
|
||||
({ parse_state, resolution }) =>
|
||||
parse_state !== "parsed" ||
|
||||
["unresolved", "unknown"].includes(resolution),
|
||||
);
|
||||
if (
|
||||
!partialEntry &&
|
||||
!partialRelationship &&
|
||||
graph.parse_failures.length === 0 &&
|
||||
graph.exclusions.length === 0 &&
|
||||
graph.limitations.length === 0
|
||||
graph.inventory_state !== "complete" ||
|
||||
!historicalSourceInventoryHasPartialFacts(graph)
|
||||
)
|
||||
return;
|
||||
context.addIssue({
|
||||
@@ -445,7 +458,7 @@ function checkGraphInvariants(
|
||||
checkSortedUnique(graph.languages, "languages", context);
|
||||
checkSortedUnique(graph.manifests, "manifests", context);
|
||||
checkSortedUnique(
|
||||
graph.relationships.map(relationshipKey),
|
||||
graph.relationships.map(historicalSourceRelationshipKey),
|
||||
"relationships",
|
||||
context,
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user