@openparser/lineage 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/LICENSE +201 -0
  3. package/README.md +211 -0
  4. package/dist/activity.d.ts +74 -0
  5. package/dist/activity.d.ts.map +1 -0
  6. package/dist/approval.d.ts +24 -0
  7. package/dist/approval.d.ts.map +1 -0
  8. package/dist/builder.d.ts +48 -0
  9. package/dist/builder.d.ts.map +1 -0
  10. package/dist/common.d.ts +34 -0
  11. package/dist/common.d.ts.map +1 -0
  12. package/dist/confidence.d.ts +61 -0
  13. package/dist/confidence.d.ts.map +1 -0
  14. package/dist/derivation.d.ts +163 -0
  15. package/dist/derivation.d.ts.map +1 -0
  16. package/dist/document.d.ts +412 -0
  17. package/dist/document.d.ts.map +1 -0
  18. package/dist/entity.d.ts +126 -0
  19. package/dist/entity.d.ts.map +1 -0
  20. package/dist/fields.d.ts +76 -0
  21. package/dist/fields.d.ts.map +1 -0
  22. package/dist/index.d.ts +21 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +870 -0
  25. package/dist/labels.d.ts +11 -0
  26. package/dist/labels.d.ts.map +1 -0
  27. package/dist/profile/grounding.d.ts +34 -0
  28. package/dist/profile/grounding.d.ts.map +1 -0
  29. package/dist/profile/index.d.ts +14 -0
  30. package/dist/profile/index.d.ts.map +1 -0
  31. package/dist/profile/index.js +1298 -0
  32. package/dist/profile/labels.d.ts +10 -0
  33. package/dist/profile/labels.d.ts.map +1 -0
  34. package/dist/profile/recognition.d.ts +70 -0
  35. package/dist/profile/recognition.d.ts.map +1 -0
  36. package/dist/profile/transform-validation.d.ts +250 -0
  37. package/dist/profile/transform-validation.d.ts.map +1 -0
  38. package/dist/prov.d.ts +40 -0
  39. package/dist/prov.d.ts.map +1 -0
  40. package/dist/relation.d.ts +27 -0
  41. package/dist/relation.d.ts.map +1 -0
  42. package/dist/traverse.d.ts +12 -0
  43. package/dist/traverse.d.ts.map +1 -0
  44. package/dist/validate.d.ts +14 -0
  45. package/dist/validate.d.ts.map +1 -0
  46. package/lineage.schema.json +625 -0
  47. package/package.json +71 -0
  48. package/src/activity.ts +65 -0
  49. package/src/approval.ts +28 -0
  50. package/src/builder.ts +129 -0
  51. package/src/common.ts +58 -0
  52. package/src/confidence.ts +72 -0
  53. package/src/derivation.ts +81 -0
  54. package/src/document.ts +82 -0
  55. package/src/entity.ts +87 -0
  56. package/src/fields.ts +163 -0
  57. package/src/index.ts +123 -0
  58. package/src/labels.ts +13 -0
  59. package/src/profile/grounding.ts +74 -0
  60. package/src/profile/index.ts +67 -0
  61. package/src/profile/labels.ts +28 -0
  62. package/src/profile/recognition.ts +149 -0
  63. package/src/profile/transform-validation.ts +1379 -0
  64. package/src/prov.ts +205 -0
  65. package/src/relation.ts +32 -0
  66. package/src/traverse.ts +91 -0
  67. package/src/validate.ts +221 -0
@@ -0,0 +1,65 @@
1
+ import { z } from 'zod';
2
+ import { AttributesSchema, JsonValueSchema, LineageIdSchema, TimestampSchema } from './common';
3
+
4
+ /** How an agent relates to an activity (PROV wasAssociatedWith). */
5
+ export const AssociationSchema = z
6
+ .object({
7
+ agent: LineageIdSchema,
8
+ role: z.string().trim().min(1).max(128).optional(),
9
+ attributes: AttributesSchema.optional(),
10
+ })
11
+ .strict();
12
+ export type Association = z.infer<typeof AssociationSchema>;
13
+
14
+ export const ACTIVITY_STATUSES = ['scheduled', 'started', 'ended', 'failed', 'cancelled'] as const;
15
+ export const ActivityStatusSchema = z.enum(ACTIVITY_STATUSES);
16
+ export type ActivityStatus = z.infer<typeof ActivityStatusSchema>;
17
+
18
+ /**
19
+ * Optional description of the concrete implementation that performed an
20
+ * activity (library, model, service, script, binary, …).
21
+ */
22
+ export const ImplementationSchema = z
23
+ .object({
24
+ type: z.string().trim().min(1).max(128).optional(),
25
+ name: z.string().trim().min(1).max(512).optional(),
26
+ version: z.string().trim().min(1).max(128).optional(),
27
+ uri: z.string().trim().min(1).max(4096).optional(),
28
+ attributes: AttributesSchema.optional(),
29
+ })
30
+ .strict();
31
+ export type Implementation = z.infer<typeof ImplementationSchema>;
32
+
33
+ /**
34
+ * A process that used and/or generated entities. `type` is an open string so
35
+ * profiles can introduce domain-specific activity types without a format bump.
36
+ *
37
+ * Map keys in `LineageDocument.activities` are the authoritative activity ids.
38
+ */
39
+ export const ActivitySchema = z
40
+ .object({
41
+ type: z.string().trim().min(1).max(256),
42
+ name: z.string().trim().min(1).max(512).optional(),
43
+ status: ActivityStatusSchema.optional(),
44
+ startedAt: TimestampSchema.optional(),
45
+ endedAt: TimestampSchema.optional(),
46
+ implementation: ImplementationSchema.optional(),
47
+ parameters: z.record(z.string().min(1), JsonValueSchema).optional(),
48
+ associations: z.array(AssociationSchema).max(64).optional(),
49
+ attributes: AttributesSchema.optional(),
50
+ })
51
+ .strict();
52
+ export type Activity = z.infer<typeof ActivitySchema>;
53
+
54
+ /**
55
+ * An agent (person, organization, software, service) that acts or is
56
+ * attributed. Map keys in `LineageDocument.agents` are the authoritative ids.
57
+ */
58
+ export const AgentSchema = z
59
+ .object({
60
+ type: z.string().trim().min(1).max(128).optional(),
61
+ name: z.string().trim().min(1).max(512).optional(),
62
+ attributes: AttributesSchema.optional(),
63
+ })
64
+ .strict();
65
+ export type Agent = z.infer<typeof AgentSchema>;
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ import { AttributesSchema, LineageIdSchema } from './common';
3
+
4
+ /**
5
+ * Who currently stands behind this entity's value.
6
+ *
7
+ * An assertion names an agent and a time; it does not change the entity.
8
+ * OpenParser extraction review projects this from confirmations rather than
9
+ * storing a separate approve event: an actor counts when their newest live
10
+ * confirmation on this path names the value this entity holds. Confirming a
11
+ * matching value adds standing without minting a new entity; confirming a
12
+ * different value mints a new entity, and standing on the predecessor is left
13
+ * behind. That is why a sign-off never describes a value nobody confirmed.
14
+ *
15
+ * Standing is per entity and does not propagate. Confirming a field says
16
+ * nothing about the region it was read from — that region carries its own
17
+ * standing if someone confirms it.
18
+ */
19
+ export const ApprovalAssertionSchema = z
20
+ .object({
21
+ /** Agent in `LineageDocument.agents` that signed off. */
22
+ agent: LineageIdSchema,
23
+ at: z.string().datetime(),
24
+ note: z.string().trim().min(1).max(2_000).optional(),
25
+ attributes: AttributesSchema.optional(),
26
+ })
27
+ .strict();
28
+ export type ApprovalAssertion = z.infer<typeof ApprovalAssertionSchema>;
package/src/builder.ts ADDED
@@ -0,0 +1,129 @@
1
+ import type { Activity, Agent } from './activity';
2
+ import { LINEAGE_FORMAT } from './common';
3
+ import type { ConfidenceAssertion } from './confidence';
4
+ import {
5
+ type Derivation,
6
+ type DerivationInput,
7
+ type Transformation,
8
+ derivationId,
9
+ } from './derivation';
10
+ import type { LineageDocument } from './document';
11
+ import { parseLineageDocument } from './document';
12
+ import type { Entity } from './entity';
13
+ import type { Relation } from './relation';
14
+
15
+ type EntityInput = Entity;
16
+ type ActivityInput = Activity;
17
+ type AgentInput = Agent;
18
+
19
+ /**
20
+ * Ergonomic builder for `lineage@1` documents. Validates on {@link build}.
21
+ */
22
+ export class LineageBuilder {
23
+ private id?: string;
24
+ private profiles?: string[];
25
+ private readonly entities = new Map<string, EntityInput>();
26
+ private readonly activities = new Map<string, ActivityInput>();
27
+ private readonly agents = new Map<string, AgentInput>();
28
+ private readonly derivations: Derivation[] = [];
29
+ private relations?: Relation[];
30
+ private outputs: string[] = [];
31
+ private attributes?: LineageDocument['attributes'];
32
+
33
+ withId(id: string): this {
34
+ this.id = id;
35
+ return this;
36
+ }
37
+
38
+ withProfiles(...profiles: string[]): this {
39
+ this.profiles = [...(this.profiles ?? []), ...profiles];
40
+ return this;
41
+ }
42
+
43
+ withAttributes(attributes: NonNullable<LineageDocument['attributes']>): this {
44
+ this.attributes = attributes;
45
+ return this;
46
+ }
47
+
48
+ entity(id: string, entity: EntityInput): this {
49
+ if (this.entities.has(id)) {
50
+ throw new Error(`entity "${id}" already registered`);
51
+ }
52
+ this.entities.set(id, entity);
53
+ return this;
54
+ }
55
+
56
+ activity(id: string, activity: ActivityInput): this {
57
+ if (this.activities.has(id)) {
58
+ throw new Error(`activity "${id}" already registered`);
59
+ }
60
+ this.activities.set(id, activity);
61
+ return this;
62
+ }
63
+
64
+ agent(id: string, agent: AgentInput): this {
65
+ if (this.agents.has(id)) {
66
+ throw new Error(`agent "${id}" already registered`);
67
+ }
68
+ this.agents.set(id, agent);
69
+ return this;
70
+ }
71
+
72
+ /**
73
+ * Record that `activityId` produced `outputId` from `inputs`.
74
+ */
75
+ derive(args: {
76
+ /** Optional, as in the wire format: the output already identifies it. */
77
+ id?: string;
78
+ output: string;
79
+ activity: string;
80
+ inputs?: Array<DerivationInput | string>;
81
+ transformation?: Transformation;
82
+ confidence?: ConfidenceAssertion;
83
+ attributes?: Derivation['attributes'];
84
+ }): this {
85
+ const id = derivationId(args);
86
+ if (this.derivations.some((d) => derivationId(d) === id)) {
87
+ throw new Error(`derivation "${id}" already registered`);
88
+ }
89
+ const inputs: DerivationInput[] = (args.inputs ?? []).map((input) =>
90
+ typeof input === 'string' ? { entity: input, effect: 'direct' as const } : input
91
+ );
92
+ this.derivations.push({
93
+ ...(args.id ? { id: args.id } : {}),
94
+ output: args.output,
95
+ activity: args.activity,
96
+ inputs,
97
+ ...(args.transformation ? { transformation: args.transformation } : {}),
98
+ ...(args.confidence ? { confidence: args.confidence } : {}),
99
+ ...(args.attributes ? { attributes: args.attributes } : {}),
100
+ });
101
+ return this;
102
+ }
103
+
104
+ relation(relation: Relation): this {
105
+ this.relations = [...(this.relations ?? []), relation];
106
+ return this;
107
+ }
108
+
109
+ withOutputs(...entityIds: string[]): this {
110
+ this.outputs = [...this.outputs, ...entityIds];
111
+ return this;
112
+ }
113
+
114
+ /** Assemble and strictly validate the document. */
115
+ build(): LineageDocument {
116
+ return parseLineageDocument({
117
+ format: LINEAGE_FORMAT,
118
+ ...(this.id ? { id: this.id } : {}),
119
+ ...(this.profiles && this.profiles.length > 0 ? { profiles: this.profiles } : {}),
120
+ entities: Object.fromEntries(this.entities),
121
+ activities: Object.fromEntries(this.activities),
122
+ agents: Object.fromEntries(this.agents),
123
+ derivations: this.derivations,
124
+ ...(this.relations && this.relations.length > 0 ? { relations: this.relations } : {}),
125
+ outputs: this.outputs,
126
+ ...(this.attributes ? { attributes: this.attributes } : {}),
127
+ });
128
+ }
129
+ }
package/src/common.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod';
2
+
3
+ /** Non-empty identifier used as a map key or cross-reference. */
4
+ export const LineageIdSchema = z.string().trim().min(1).max(512);
5
+ export type LineageId = z.infer<typeof LineageIdSchema>;
6
+
7
+ /** JSON-safe attribute bags and free-form parameter objects. */
8
+ export const JsonValueSchema = z.json();
9
+ export type JsonValue = z.infer<typeof JsonValueSchema>;
10
+
11
+ export const AttributesSchema = z.record(z.string().min(1), JsonValueSchema);
12
+ export type Attributes = z.infer<typeof AttributesSchema>;
13
+
14
+ /**
15
+ * Structural equality for JSON values: whether two values say the same thing.
16
+ * Object key order is not part of what a JSON value says, so it is normalised
17
+ * away rather than compared.
18
+ */
19
+ export function jsonValuesEqual(left: JsonValue, right: JsonValue): boolean {
20
+ if (left === right) return true;
21
+ if (left === null || right === null) return false;
22
+ if (typeof left !== 'object' || typeof right !== 'object') return false;
23
+ return canonicalJson(left) === canonicalJson(right);
24
+ }
25
+
26
+ function canonicalJson(value: JsonValue): string {
27
+ if (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null';
28
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
29
+ const entries = Object.entries(value)
30
+ .filter(([, member]) => member !== undefined)
31
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
32
+ .map(([key, member]) => `${JSON.stringify(key)}:${canonicalJson(member as JsonValue)}`);
33
+ return `{${entries.join(',')}}`;
34
+ }
35
+
36
+ /**
37
+ * RFC 6901 JSON Pointer. The empty string addresses the whole document.
38
+ * JSONPath is useful for queries, but pointers give lineage entities one
39
+ * unambiguous location.
40
+ */
41
+ export const JsonPointerSchema = z
42
+ .string()
43
+ .max(2048)
44
+ .regex(/^(?:\/(?:[^~/]|~0|~1)*)*$/, 'path must be an RFC 6901 JSON Pointer');
45
+ export type JsonPointer = z.infer<typeof JsonPointerSchema>;
46
+
47
+ /** Wire format tag for this package. */
48
+ export const LINEAGE_FORMAT = 'lineage@1' as const;
49
+ export const LineageFormatSchema = z.literal(LINEAGE_FORMAT);
50
+ export type LineageFormat = z.infer<typeof LineageFormatSchema>;
51
+
52
+ /**
53
+ * RFC 3339 timestamp string. Offset is required (`Z` or `±HH:mm`). Calendar
54
+ * dates that are not real days are rejected rather than rolled over.
55
+ * Start/end ordering is enforced at the document graph level.
56
+ */
57
+ export const TimestampSchema = z.iso.datetime({ offset: true }).max(64);
58
+ export type Timestamp = z.infer<typeof TimestampSchema>;
@@ -0,0 +1,72 @@
1
+ import { z } from 'zod';
2
+ import { AttributesSchema, LineageIdSchema } from './common';
3
+
4
+ /**
5
+ * Explicit numeric scale for a confidence score.
6
+ *
7
+ * Scores are not forced into 0..1 so original provider scales remain
8
+ * representable without lossy normalization.
9
+ */
10
+ export const ConfidenceScaleSchema = z
11
+ .object({
12
+ min: z.number().finite(),
13
+ max: z.number().finite(),
14
+ })
15
+ .strict()
16
+ .superRefine((scale, ctx) => {
17
+ if (!(scale.max > scale.min)) {
18
+ ctx.addIssue({
19
+ code: 'custom',
20
+ message: 'confidence scale max must be greater than min',
21
+ path: ['max'],
22
+ });
23
+ }
24
+ });
25
+ export type ConfidenceScale = z.infer<typeof ConfidenceScaleSchema>;
26
+
27
+ export const CONFIDENCE_KINDS = ['reported', 'derived', 'assessed'] as const;
28
+ export const ConfidenceKindSchema = z.enum(CONFIDENCE_KINDS);
29
+ export type ConfidenceKind = z.infer<typeof ConfidenceKindSchema>;
30
+
31
+ export const ConfidenceSourceSchema = z
32
+ .object({
33
+ type: z.enum(['entity', 'activity', 'agent']),
34
+ id: LineageIdSchema,
35
+ })
36
+ .strict();
37
+ export type ConfidenceSource = z.infer<typeof ConfidenceSourceSchema>;
38
+
39
+ /**
40
+ * Generic confidence assertion attached to entities, derivations, or other
41
+ * statements. `score` must lie within `scale`; comparison across uncalibrated
42
+ * assertions is not implied.
43
+ */
44
+ export const ConfidenceAssertionSchema = z
45
+ .object({
46
+ score: z.number().finite(),
47
+ scale: ConfidenceScaleSchema,
48
+ kind: ConfidenceKindSchema,
49
+ /** What the score measures (e.g. recognition, classification, quality). */
50
+ scope: z.string().trim().min(1).max(128),
51
+ /** Optional finer grain within scope (e.g. field, page, token). */
52
+ granularity: z.string().trim().min(1).max(128).optional(),
53
+ /** When false, scores must not be treated as calibrated probabilities. */
54
+ calibrated: z.boolean().default(false),
55
+ /** Entities, activities, or agents that substantiate or produced this assertion. */
56
+ sources: z.array(ConfidenceSourceSchema).max(256).optional(),
57
+ method: z.string().trim().min(1).max(256).optional(),
58
+ sampleCount: z.number().int().positive().optional(),
59
+ attributes: AttributesSchema.optional(),
60
+ })
61
+ .strict()
62
+ .superRefine((assertion, ctx) => {
63
+ const { score, scale } = assertion;
64
+ if (score < scale.min || score > scale.max) {
65
+ ctx.addIssue({
66
+ code: 'custom',
67
+ message: `confidence score ${score} is outside scale [${scale.min}, ${scale.max}]`,
68
+ path: ['score'],
69
+ });
70
+ }
71
+ });
72
+ export type ConfidenceAssertion = z.infer<typeof ConfidenceAssertionSchema>;
@@ -0,0 +1,81 @@
1
+ import { z } from 'zod';
2
+ import { AttributesSchema, LineageIdSchema } from './common';
3
+ import { ConfidenceAssertionSchema } from './confidence';
4
+
5
+ export const DERIVATION_EFFECTS = ['direct', 'indirect'] as const;
6
+ export const MAX_DERIVATION_INPUTS = 1_024;
7
+ export const DerivationEffectSchema = z.enum(DERIVATION_EFFECTS);
8
+ export type DerivationEffect = z.infer<typeof DerivationEffectSchema>;
9
+
10
+ /**
11
+ * An input entity reference on a derivation, with optional role and whether
12
+ * the influence on the output is direct or indirect (PROV: Revision /
13
+ * Quotation / PrimarySource style distinctions are profile-level; here we
14
+ * keep the general direct/indirect effect flag).
15
+ *
16
+ * `confidence` measures this input's contribution to this one output, which is
17
+ * not the same statement as the input entity's own confidence. A shared source
18
+ * region is read once but cited by several outputs, and each output may draw on
19
+ * a different, differently legible part of it — so how well one output was
20
+ * recognized in that region belongs on the edge, not on the region.
21
+ */
22
+ export const DerivationInputSchema = z
23
+ .object({
24
+ entity: LineageIdSchema,
25
+ role: z.string().trim().min(1).max(128).optional(),
26
+ effect: DerivationEffectSchema.default('direct'),
27
+ confidence: ConfidenceAssertionSchema.optional(),
28
+ attributes: AttributesSchema.optional(),
29
+ })
30
+ .strict();
31
+ export type DerivationInput = z.infer<typeof DerivationInputSchema>;
32
+
33
+ /**
34
+ * Optional descriptor of how inputs were transformed into the output.
35
+ *
36
+ * `type` is an open string. OpenLineage's column-lineage vocabulary —
37
+ * `identity`, `transformation`, `aggregation`, `join`, `group_by`, `filter`,
38
+ * `sort`, `window`, `conditional` — reads well here, and profiles may use their
39
+ * own namespaced values instead.
40
+ */
41
+ export const TransformationSchema = z
42
+ .object({
43
+ type: z.string().trim().min(1).max(128).optional(),
44
+ description: z.string().trim().min(1).max(4096).optional(),
45
+ expression: z.string().trim().min(1).max(8192).optional(),
46
+ masking: z.boolean().optional(),
47
+ attributes: AttributesSchema.optional(),
48
+ })
49
+ .strict();
50
+ export type Transformation = z.infer<typeof TransformationSchema>;
51
+
52
+ /**
53
+ * Output-centric derivation: one activity produced exactly one output entity
54
+ * from zero or more typed inputs. A derived entity has at most one producer
55
+ * derivation in a document (enforced by graph validation).
56
+ *
57
+ * `output` already identifies the derivation uniquely, so `id` is optional and
58
+ * normally omitted — use {@link derivationId} to resolve one. Producers with
59
+ * their own stable identifiers may still set it.
60
+ */
61
+ export const DerivationSchema = z
62
+ .object({
63
+ id: LineageIdSchema.optional(),
64
+ output: LineageIdSchema,
65
+ activity: LineageIdSchema,
66
+ inputs: z.array(DerivationInputSchema).max(MAX_DERIVATION_INPUTS).default([]),
67
+ transformation: TransformationSchema.optional(),
68
+ confidence: ConfidenceAssertionSchema.optional(),
69
+ attributes: AttributesSchema.optional(),
70
+ })
71
+ .strict();
72
+ export type Derivation = z.infer<typeof DerivationSchema>;
73
+
74
+ /**
75
+ * Stable identifier for a derivation: its explicit `id` when the producer set
76
+ * one, otherwise `derivation:<output>`. Because one derivation produces one
77
+ * output, that fallback is unique within a document.
78
+ */
79
+ export function derivationId(derivation: Pick<Derivation, 'id' | 'output'>): string {
80
+ return derivation.id ?? `derivation:${derivation.output}`;
81
+ }
@@ -0,0 +1,82 @@
1
+ import { z } from 'zod';
2
+ import { ActivitySchema, AgentSchema } from './activity';
3
+ import { AttributesSchema, LineageFormatSchema, LineageIdSchema } from './common';
4
+ import { DerivationSchema } from './derivation';
5
+ import { EntitySchema } from './entity';
6
+ import { RelationSchema } from './relation';
7
+ import { LineageValidationError, validateLineageGraph } from './validate';
8
+
9
+ /**
10
+ * Structural Zod schema for a `lineage@1` document (shape only).
11
+ * Use {@link LineageDocumentSchema} or {@link parseLineageDocument} for
12
+ * shape + strict graph validation.
13
+ */
14
+ export const LineageDocumentShapeSchema = z
15
+ .object({
16
+ format: LineageFormatSchema,
17
+ id: LineageIdSchema.optional(),
18
+ /** Profile URIs or short names declaring extension semantics. */
19
+ profiles: z.array(z.string().trim().min(1).max(1024)).max(64).optional(),
20
+ entities: z.record(LineageIdSchema, EntitySchema).default({}),
21
+ activities: z.record(LineageIdSchema, ActivitySchema).default({}),
22
+ agents: z.record(LineageIdSchema, AgentSchema).default({}),
23
+ derivations: z.array(DerivationSchema).max(100_000).default([]),
24
+ relations: z.array(RelationSchema).max(100_000).default([]),
25
+ /** Entity ids that are primary outputs of this document. */
26
+ outputs: z.array(LineageIdSchema).max(10_000).default([]),
27
+ attributes: AttributesSchema.optional(),
28
+ })
29
+ .strict();
30
+
31
+ export type LineageDocument = z.infer<typeof LineageDocumentShapeSchema>;
32
+
33
+ /**
34
+ * Parse and strictly validate a lineage document (shape + graph invariants).
35
+ */
36
+ export function parseLineageDocument(input: unknown): LineageDocument {
37
+ const doc = LineageDocumentShapeSchema.parse(input);
38
+ validateLineageGraph(doc);
39
+ return doc;
40
+ }
41
+
42
+ /**
43
+ * Safe parse with Zod issues and/or graph validation errors.
44
+ */
45
+ export function safeParseLineageDocument(
46
+ input: unknown
47
+ ):
48
+ | { success: true; data: LineageDocument }
49
+ | { success: false; error: z.ZodError | LineageValidationError } {
50
+ const shape = LineageDocumentShapeSchema.safeParse(input);
51
+ if (!shape.success) {
52
+ return { success: false, error: shape.error };
53
+ }
54
+ try {
55
+ validateLineageGraph(shape.data);
56
+ return { success: true, data: shape.data };
57
+ } catch (error) {
58
+ if (error instanceof LineageValidationError) {
59
+ return { success: false, error };
60
+ }
61
+ throw error;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Zod schema that applies graph validation via `superRefine`. Prefer
67
+ * {@link parseLineageDocument} when you want thrown `LineageValidationError`
68
+ * with structured issue lists.
69
+ */
70
+ export const LineageDocumentSchema = LineageDocumentShapeSchema.superRefine((doc, ctx) => {
71
+ try {
72
+ validateLineageGraph(doc);
73
+ } catch (error) {
74
+ if (error instanceof LineageValidationError) {
75
+ for (const message of error.issues) {
76
+ ctx.addIssue({ code: 'custom', message });
77
+ }
78
+ return;
79
+ }
80
+ throw error;
81
+ }
82
+ });
package/src/entity.ts ADDED
@@ -0,0 +1,87 @@
1
+ import { z } from 'zod';
2
+ import { ApprovalAssertionSchema } from './approval';
3
+ import { AttributesSchema, JsonPointerSchema, JsonValueSchema, LineageIdSchema } from './common';
4
+ import { ConfidenceAssertionSchema } from './confidence';
5
+
6
+ /** How an agent relates to an entity (PROV wasAttributedTo). */
7
+ export const AttributionSchema = z
8
+ .object({
9
+ agent: LineageIdSchema,
10
+ role: z.string().trim().min(1).max(128).optional(),
11
+ attributes: AttributesSchema.optional(),
12
+ })
13
+ .strict();
14
+ export type Attribution = z.infer<typeof AttributionSchema>;
15
+
16
+ /**
17
+ * Content-addressing digest for artifacts or values when a cryptographic
18
+ * fingerprint is available.
19
+ */
20
+ export const DigestSchema = z
21
+ .object({
22
+ algorithm: z.string().trim().min(1).max(64),
23
+ value: z.string().trim().min(1).max(512),
24
+ })
25
+ .strict();
26
+ export type Digest = z.infer<typeof DigestSchema>;
27
+
28
+ /**
29
+ * External location of an artifact (URI, media type, optional integrity).
30
+ */
31
+ export const LocatorSchema = z
32
+ .object({
33
+ uri: z.string().trim().min(1).max(4096),
34
+ mediaType: z.string().trim().min(1).max(256).optional(),
35
+ digest: DigestSchema.optional(),
36
+ attributes: AttributesSchema.optional(),
37
+ })
38
+ .strict();
39
+ export type Locator = z.infer<typeof LocatorSchema>;
40
+
41
+ /**
42
+ * Sub-resource selector within a located artifact (JSON Pointer, XPath,
43
+ * page/region, byte range, etc.). `type` is open for profiles.
44
+ */
45
+ export const SelectorSchema = z
46
+ .object({
47
+ type: z.string().trim().min(1).max(128),
48
+ value: JsonValueSchema,
49
+ attributes: AttributesSchema.optional(),
50
+ })
51
+ .strict();
52
+ export type Selector = z.infer<typeof SelectorSchema>;
53
+
54
+ export const ENTITY_KINDS = ['value', 'artifact', 'evidence', 'decision', 'collection'] as const;
55
+ export const EntityKindSchema = z.enum(ENTITY_KINDS);
56
+ export type EntityKind = z.infer<typeof EntityKindSchema>;
57
+
58
+ /**
59
+ * A node in the derivation DAG: data value, file/blob, supporting evidence,
60
+ * a decision outcome, or a collection of other entities.
61
+ *
62
+ * Map keys in `LineageDocument.entities` are the authoritative entity ids.
63
+ */
64
+ export const EntitySchema = z
65
+ .object({
66
+ kind: EntityKindSchema,
67
+ name: z.string().trim().min(1).max(512).optional(),
68
+ /** Inline JSON value when the entity is a materialized datum. */
69
+ value: JsonValueSchema.optional(),
70
+ /** Unambiguous location within a larger JSON value (RFC 6901). */
71
+ path: JsonPointerSchema.optional(),
72
+ /** Optional JSON Schema (or profile schema object) describing `value`. */
73
+ schema: z.record(z.string(), JsonValueSchema).optional(),
74
+ locator: LocatorSchema.optional(),
75
+ selector: SelectorSchema.optional(),
76
+ digest: DigestSchema.optional(),
77
+ confidence: z.array(ConfidenceAssertionSchema).max(64).optional(),
78
+ /**
79
+ * Who stands behind this value. Standing is per entity and does not
80
+ * propagate. OpenParser extraction review projects this from confirmations.
81
+ */
82
+ approvals: z.array(ApprovalAssertionSchema).max(64).optional(),
83
+ attributions: z.array(AttributionSchema).max(64).optional(),
84
+ attributes: AttributesSchema.optional(),
85
+ })
86
+ .strict();
87
+ export type Entity = z.infer<typeof EntitySchema>;