@origintrail-official/dkg-okf 10.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 (51) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +55 -0
  3. package/dist/bundle.d.ts +16 -0
  4. package/dist/bundle.d.ts.map +1 -0
  5. package/dist/bundle.js +132 -0
  6. package/dist/bundle.js.map +1 -0
  7. package/dist/constants.d.ts +52 -0
  8. package/dist/constants.d.ts.map +1 -0
  9. package/dist/constants.js +55 -0
  10. package/dist/constants.js.map +1 -0
  11. package/dist/document.d.ts +30 -0
  12. package/dist/document.d.ts.map +1 -0
  13. package/dist/document.js +72 -0
  14. package/dist/document.js.map +1 -0
  15. package/dist/export.d.ts +21 -0
  16. package/dist/export.d.ts.map +1 -0
  17. package/dist/export.js +210 -0
  18. package/dist/export.js.map +1 -0
  19. package/dist/index.d.ts +24 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +23 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/loader.d.ts +20 -0
  24. package/dist/loader.d.ts.map +1 -0
  25. package/dist/loader.js +42 -0
  26. package/dist/loader.js.map +1 -0
  27. package/dist/mapping.d.ts +38 -0
  28. package/dist/mapping.d.ts.map +1 -0
  29. package/dist/mapping.js +253 -0
  30. package/dist/mapping.js.map +1 -0
  31. package/dist/nquads.d.ts +13 -0
  32. package/dist/nquads.d.ts.map +1 -0
  33. package/dist/nquads.js +26 -0
  34. package/dist/nquads.js.map +1 -0
  35. package/dist/paths.d.ts +57 -0
  36. package/dist/paths.d.ts.map +1 -0
  37. package/dist/paths.js +126 -0
  38. package/dist/paths.js.map +1 -0
  39. package/dist/types.d.ts +134 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +10 -0
  42. package/dist/types.js.map +1 -0
  43. package/dist/utils.d.ts +27 -0
  44. package/dist/utils.d.ts.map +1 -0
  45. package/dist/utils.js +53 -0
  46. package/dist/utils.js.map +1 -0
  47. package/dist/validation.d.ts +17 -0
  48. package/dist/validation.d.ts.map +1 -0
  49. package/dist/validation.js +80 -0
  50. package/dist/validation.js.map +1 -0
  51. package/package.json +38 -0
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Deterministic OKF concept → RDF mapping (no LLM, no network).
3
+ *
4
+ * One OKF concept document maps to one Knowledge Asset subject IRI plus a set
5
+ * of content + linkage quads. The mapping reuses the node Markdown extractor's
6
+ * predicate vocabulary (so an OKF import and a native Markdown import converge),
7
+ * with the OKF-specific deltas recorded in `docs/adr/0005-okf-rdf-mapping.md`:
8
+ *
9
+ * - body links are real Markdown links `[text](path)` (OKF §5), NOT the
10
+ * extractor's `[[wikilinks]]`, so we resolve them with a real Markdown AST
11
+ * (`mdast-util-from-markdown`) — which is also what lets us honour the
12
+ * CommonMark rule that a link inside an inline code span is literal text;
13
+ * - OKF concept titles live in frontmatter, so body headings (including `#`
14
+ * H1s like `# Schema`) are genuine sections → `dkg:hasSection`;
15
+ * - `timestamp` is OKF's last-modified time → `schema:dateModified`.
16
+ */
17
+ import { fromMarkdown } from 'mdast-util-from-markdown';
18
+ import { toString as mdToString } from 'mdast-util-to-string';
19
+ import { RDF_TYPE, SCHEMA_NS, SCHEMA_NAME, SCHEMA_DESCRIPTION, SCHEMA_KEYWORDS, SCHEMA_MENTIONS, SCHEMA_DATE_MODIFIED, SCHEMA_URL, SCHEMA_CITATION, SCHEMA_IS_PART_OF, DKG_HAS_SECTION, SECTION_GENID_INFIX, XSD_DATE_TIME, XSD_BOOLEAN, XSD_INTEGER, XSD_DECIMAL, DEFAULT_IRI_BASE, } from './constants.js';
20
+ import { conceptIdToIri, resolveLinkTarget } from './paths.js';
21
+ import { isSafeIri, literalTerm, typedLiteralTerm, pascalCase, camelCase, sanitizeForBlank, } from './utils.js';
22
+ const BARE_URL_RE = /https?:\/\/[^\s)<>"]+/g;
23
+ const INLINE_LINK_RE = /\[[^\]]*\]\(([^)\s]+)\)/g;
24
+ /** OKF `type` value → an rdf:type object IRI (raw, no angle brackets). */
25
+ function typeToIri(value) {
26
+ const s = String(value).trim();
27
+ if (!s)
28
+ return null;
29
+ if (isSafeIri(s))
30
+ return s; // already a full IRI (e.g. `tag:…`, `https://…`)
31
+ const pascal = pascalCase(s);
32
+ return pascal ? SCHEMA_NS + pascal : null;
33
+ }
34
+ function toArray(value) {
35
+ return Array.isArray(value) ? value : [value];
36
+ }
37
+ function dateToLexical(value) {
38
+ return value instanceof Date ? value.toISOString() : String(value);
39
+ }
40
+ /** Producer-defined scalar/array values → object terms (typed where possible). */
41
+ function valueToTerms(value) {
42
+ if (value === null || value === undefined)
43
+ return [];
44
+ if (Array.isArray(value))
45
+ return value.flatMap(valueToTerms);
46
+ if (value instanceof Date)
47
+ return [typedLiteralTerm(value.toISOString(), XSD_DATE_TIME)];
48
+ if (typeof value === 'boolean')
49
+ return [typedLiteralTerm(String(value), XSD_BOOLEAN)];
50
+ if (typeof value === 'number') {
51
+ return [typedLiteralTerm(String(value), Number.isInteger(value) ? XSD_INTEGER : XSD_DECIMAL)];
52
+ }
53
+ if (typeof value === 'string') {
54
+ return [isSafeIri(value) ? value : literalTerm(value)];
55
+ }
56
+ return [literalTerm(JSON.stringify(value))];
57
+ }
58
+ /** Map the YAML frontmatter to quads (SPEC §4.1; see the locked table in ADR 0005). */
59
+ export function frontmatterQuads(iri, frontmatter) {
60
+ const out = [];
61
+ for (const [key, value] of Object.entries(frontmatter)) {
62
+ if (value === null || value === undefined)
63
+ continue;
64
+ switch (key) {
65
+ case 'type': {
66
+ const t = typeToIri(value);
67
+ if (t)
68
+ out.push({ subject: iri, predicate: RDF_TYPE, object: t });
69
+ break;
70
+ }
71
+ case 'title':
72
+ out.push({ subject: iri, predicate: SCHEMA_NAME, object: literalTerm(String(value)) });
73
+ break;
74
+ case 'description':
75
+ out.push({
76
+ subject: iri,
77
+ predicate: SCHEMA_DESCRIPTION,
78
+ object: literalTerm(String(value)),
79
+ });
80
+ break;
81
+ case 'tags':
82
+ for (const tag of toArray(value)) {
83
+ out.push({ subject: iri, predicate: SCHEMA_KEYWORDS, object: literalTerm(String(tag)) });
84
+ }
85
+ break;
86
+ case 'timestamp':
87
+ out.push({
88
+ subject: iri,
89
+ predicate: SCHEMA_DATE_MODIFIED,
90
+ object: typedLiteralTerm(dateToLexical(value), XSD_DATE_TIME),
91
+ });
92
+ break;
93
+ case 'resource': {
94
+ const r = String(value);
95
+ out.push({
96
+ subject: iri,
97
+ predicate: SCHEMA_URL,
98
+ object: isSafeIri(r) ? r : literalTerm(r),
99
+ });
100
+ break;
101
+ }
102
+ default: {
103
+ // Producer-defined keys — preserved, never dropped (SPEC §4.1/§9).
104
+ const predicate = SCHEMA_NS + camelCase(key);
105
+ for (const term of valueToTerms(value)) {
106
+ out.push({ subject: iri, predicate, object: term });
107
+ }
108
+ }
109
+ }
110
+ }
111
+ return out;
112
+ }
113
+ function collect(node, links, codes, texts) {
114
+ if (node.type === 'link')
115
+ links.push(node);
116
+ else if (node.type === 'inlineCode')
117
+ codes.push(node);
118
+ else if (node.type === 'text')
119
+ texts.push(node.value);
120
+ if ('children' in node && Array.isArray(node.children)) {
121
+ for (const child of node.children)
122
+ collect(child, links, codes, texts);
123
+ }
124
+ }
125
+ function extractInlineLinkHrefs(code) {
126
+ const out = [];
127
+ for (const m of code.matchAll(INLINE_LINK_RE))
128
+ out.push(m[1]);
129
+ return out;
130
+ }
131
+ /** Parse a concept body with a real Markdown AST. */
132
+ export function parseBody(body) {
133
+ const tree = fromMarkdown(body);
134
+ const headings = [];
135
+ const bodyLinks = [];
136
+ const codeSpanHrefs = [];
137
+ const citations = [];
138
+ let currentSection = '';
139
+ for (const node of tree.children) {
140
+ if (node.type === 'heading') {
141
+ const text = mdToString(node);
142
+ headings.push(text);
143
+ currentSection = text.trim().toLowerCase();
144
+ continue;
145
+ }
146
+ const inCitations = currentSection === 'citations';
147
+ const links = [];
148
+ const codes = [];
149
+ const texts = [];
150
+ collect(node, links, codes, texts);
151
+ if (inCitations) {
152
+ for (const l of links) {
153
+ const label = mdToString(l).trim();
154
+ citations.push(label ? { url: l.url, label } : { url: l.url });
155
+ }
156
+ for (const t of texts) {
157
+ for (const m of t.matchAll(BARE_URL_RE))
158
+ citations.push({ url: m[0] });
159
+ }
160
+ }
161
+ else {
162
+ for (const l of links)
163
+ bodyLinks.push(l.url);
164
+ for (const c of codes)
165
+ codeSpanHrefs.push(...extractInlineLinkHrefs(c.value));
166
+ }
167
+ }
168
+ return { headings, bodyLinks, codeSpanHrefs, citations };
169
+ }
170
+ /**
171
+ * Map a single concept to its Knowledge Asset quads + structured link/citation
172
+ * diagnostics. `conceptExists` decides whether a resolved link target is in the
173
+ * bundle (a candidate that is not present is a broken link — warned, never fatal).
174
+ */
175
+ export function mapConcept(doc, iri, conceptExists, opts = {}) {
176
+ const iriBase = opts.iriBase ?? DEFAULT_IRI_BASE;
177
+ const quads = [...frontmatterQuads(iri, doc.frontmatter)];
178
+ const parsed = parseBody(doc.body);
179
+ // Sections: every body heading (OKF titles live in frontmatter, so H1s count).
180
+ // Section nodes are skolemized into deterministic concept-scoped IRIs rather
181
+ // than emitted as RDF blank nodes: the daemon rejects blank-node *objects*
182
+ // ("RDF object must be a quoted literal term or absolute IRI"), so a blank
183
+ // `hasSection` object fails the first write on a strict node. The IRI uses the
184
+ // node's own `.well-known/genid/` scheme, so the stored graph is identical.
185
+ parsed.headings.forEach((text, i) => {
186
+ const sectionIri = `${iri}${SECTION_GENID_INFIX}okfsec_${sanitizeForBlank(doc.conceptId)}_${i}`;
187
+ quads.push({ subject: iri, predicate: DKG_HAS_SECTION, object: sectionIri });
188
+ quads.push({ subject: sectionIri, predicate: SCHEMA_NAME, object: literalTerm(text) });
189
+ });
190
+ const resolvedLinks = [];
191
+ const brokenLinks = [];
192
+ const codeSpanLinks = [];
193
+ const edgeTargets = new Set();
194
+ const addEdge = (target) => {
195
+ if (edgeTargets.has(target))
196
+ return;
197
+ edgeTargets.add(target);
198
+ quads.push({
199
+ subject: iri,
200
+ predicate: SCHEMA_MENTIONS,
201
+ object: conceptIdToIri(target, iriBase),
202
+ });
203
+ };
204
+ for (const href of parsed.bodyLinks) {
205
+ const candidate = resolveLinkTarget(href, doc.conceptId);
206
+ if (candidate && conceptExists(candidate)) {
207
+ resolvedLinks.push({ raw: href, targetConceptId: candidate, inCodeSpan: false });
208
+ addEdge(candidate);
209
+ }
210
+ else if (candidate) {
211
+ // Resolved to a bundle path that doesn't exist → broken (SPEC §5.3/§9).
212
+ brokenLinks.push({ raw: href, targetConceptId: null, inCodeSpan: false });
213
+ }
214
+ // candidate === null → external URL / anchor / escapes root: not a concept edge.
215
+ }
216
+ for (const href of parsed.codeSpanHrefs) {
217
+ const candidate = resolveLinkTarget(href, doc.conceptId);
218
+ const present = !!candidate && conceptExists(candidate);
219
+ const link = {
220
+ raw: href,
221
+ targetConceptId: present ? candidate : null,
222
+ inCodeSpan: true,
223
+ };
224
+ codeSpanLinks.push(link);
225
+ if (opts.includeCodeSpanLinks && present && candidate) {
226
+ resolvedLinks.push(link);
227
+ addEdge(candidate);
228
+ }
229
+ }
230
+ const citations = [];
231
+ const seenCitation = new Set();
232
+ for (const c of parsed.citations) {
233
+ if (seenCitation.has(c.url))
234
+ continue;
235
+ seenCitation.add(c.url);
236
+ citations.push(c);
237
+ quads.push({
238
+ subject: iri,
239
+ predicate: SCHEMA_CITATION,
240
+ object: isSafeIri(c.url) ? c.url : literalTerm(c.url),
241
+ });
242
+ }
243
+ if (opts.emitFolderHierarchy && doc.segments.length > 1) {
244
+ const parentId = doc.segments.slice(0, -1).join('/');
245
+ quads.push({
246
+ subject: iri,
247
+ predicate: SCHEMA_IS_PART_OF,
248
+ object: conceptIdToIri(parentId, iriBase),
249
+ });
250
+ }
251
+ return { conceptId: doc.conceptId, iri, quads, resolvedLinks, brokenLinks, codeSpanLinks, citations };
252
+ }
253
+ //# sourceMappingURL=mapping.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mapping.js","sourceRoot":"","sources":["../src/mapping.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Deterministic N-Quads serialization for golden tests and `export`.
3
+ *
4
+ * Quad object terms already use the node's quad encoding (raw IRIs without
5
+ * angle brackets, literals as `"…"` / `"…"^^<dt>`, blanks as `_:…`). This
6
+ * renders them as canonical N-Quads: IRIs wrapped in `<…>`, literals/blanks
7
+ * passed through, then deduplicated and lexically sorted so the same graph
8
+ * always serialises to byte-identical output.
9
+ */
10
+ import type { Quad } from './types.js';
11
+ /** Render quads to canonical (deduped + sorted) N-Quads. */
12
+ export declare function quadsToNQuads(quads: Quad[]): string;
13
+ //# sourceMappingURL=nquads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nquads.d.ts","sourceRoot":"","sources":["../src/nquads.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAQvC,4DAA4D;AAC5D,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAOnD"}
package/dist/nquads.js ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Deterministic N-Quads serialization for golden tests and `export`.
3
+ *
4
+ * Quad object terms already use the node's quad encoding (raw IRIs without
5
+ * angle brackets, literals as `"…"` / `"…"^^<dt>`, blanks as `_:…`). This
6
+ * renders them as canonical N-Quads: IRIs wrapped in `<…>`, literals/blanks
7
+ * passed through, then deduplicated and lexically sorted so the same graph
8
+ * always serialises to byte-identical output.
9
+ */
10
+ function termToNQuads(term) {
11
+ if (term.startsWith('_:'))
12
+ return term; // blank node
13
+ if (term.startsWith('"'))
14
+ return term; // literal (possibly `"…"^^<dt>` / `"…"@lang`)
15
+ return `<${term}>`; // IRI
16
+ }
17
+ /** Render quads to canonical (deduped + sorted) N-Quads. */
18
+ export function quadsToNQuads(quads) {
19
+ const lines = quads.map((q) => {
20
+ const graph = q.graph ? ` ${termToNQuads(q.graph)}` : '';
21
+ return `${termToNQuads(q.subject)} <${q.predicate}> ${termToNQuads(q.object)}${graph} .`;
22
+ });
23
+ const unique = [...new Set(lines)].sort();
24
+ return unique.length > 0 ? unique.join('\n') + '\n' : '';
25
+ }
26
+ //# sourceMappingURL=nquads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nquads.js","sourceRoot":"","sources":["../src/nquads.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,SAAS,YAAY,CAAC,IAAY;IAChC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,aAAa;IACrD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,8CAA8C;IACrF,OAAO,IAAI,IAAI,GAAG,CAAC,CAAC,MAAM;AAC5B,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC5B,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,SAAS,KAAK,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,KAAK,IAAI,CAAC;IAC3F,CAAC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1C,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Concept-ID ↔ path resolution and OKF cross-link resolution.
3
+ *
4
+ * The segment-validation regex is kept byte-for-byte in agreement with the
5
+ * OKF reference agent's `okf/src/reference_agent/bundle/paths.py`:
6
+ *
7
+ * _SEGMENT_RE = re.compile(r"[A-Za-z0-9_][A-Za-z0-9_.\-]*")
8
+ *
9
+ * matched with `fullmatch`. A path segment must start with an alphanumeric or
10
+ * underscore, then may contain alphanumerics, underscore, dot or hyphen.
11
+ */
12
+ /** Reserved filenames that are NOT concepts (SPEC §3.1, §6, §7). */
13
+ export declare const RESERVED_FILENAMES: Set<string>;
14
+ export declare function isValidSegment(segment: string): boolean;
15
+ /** POSIX basename of a bundle-relative path. */
16
+ export declare function basename(path: string): string;
17
+ /** True for reserved `index.md` / `log.md` at any depth (SPEC §3.1). */
18
+ export declare function isReservedFile(path: string): boolean;
19
+ /** True for a non-reserved `.md` file (i.e. a concept document, SPEC §4). */
20
+ export declare function isConceptFile(path: string): boolean;
21
+ /**
22
+ * Bundle-relative path → concept ID (path with `.md` removed, SPEC §2).
23
+ * `tables/blocks.md` → `tables/blocks`.
24
+ */
25
+ export declare function pathToConceptId(path: string): string;
26
+ /** Concept ID → deterministic subject IRI. Same bundle ⇒ same IRI. */
27
+ export declare function conceptIdToIri(conceptId: string, iriBase?: string): string;
28
+ /**
29
+ * Node-side Knowledge Asset / assertion name for a concept.
30
+ *
31
+ * DKG asset/assertion names cannot contain `/`, but OKF concept IDs are
32
+ * path-based (`tables/blocks`). The encoding must be **injective** — a naive
33
+ * `/`→`__` collapses `a/b` and the literal concept `a__b` onto the same name.
34
+ * So escape the escape character first (`_`→`_5f`), then `/`→`_2f` (the chars'
35
+ * hex codes). `a/b`→`a_2fb`, `a__b`→`a_5f_5fb` — distinct. The RDF subject IRI
36
+ * is unaffected; it keeps the original `/`-bearing concept ID.
37
+ */
38
+ export declare function conceptIdToKaName(conceptId: string): string;
39
+ /**
40
+ * Resolve a Markdown link `href` written inside the concept `fromConceptId`
41
+ * into a candidate target concept ID, per SPEC §5. Handles:
42
+ * - absolute (bundle-relative): `/tables/customers.md`
43
+ * - relative: `./other.md`, `../tables/x.md`
44
+ * - bare-sibling: `x.md`
45
+ * - extension-less variants: `x`, `../tables/x`
46
+ * - `#anchor` / `?query` suffixes are stripped first
47
+ *
48
+ * Returns `null` for: external URLs (scheme-prefixed), pure anchors, paths that
49
+ * escape the bundle root, directory links, or any candidate whose segments fail
50
+ * the `paths.py` validation regex (so it could not be a concept ID anyway).
51
+ *
52
+ * Note: this returns a *candidate* — whether the target actually exists in the
53
+ * bundle is decided by the caller against the Pass-1 concept map. A candidate
54
+ * that does not exist is a broken link, which is NOT an error (SPEC §5.3/§9).
55
+ */
56
+ export declare function resolveLinkTarget(href: string, fromConceptId: string): string | null;
57
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,oEAAoE;AACpE,eAAO,MAAM,kBAAkB,aAAkC,CAAC;AAQlE,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,gDAAgD;AAChD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG7C;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIpD;AAED,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,MAAyB,GAAG,MAAM,CAE5F;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0CpF"}
package/dist/paths.js ADDED
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Concept-ID ↔ path resolution and OKF cross-link resolution.
3
+ *
4
+ * The segment-validation regex is kept byte-for-byte in agreement with the
5
+ * OKF reference agent's `okf/src/reference_agent/bundle/paths.py`:
6
+ *
7
+ * _SEGMENT_RE = re.compile(r"[A-Za-z0-9_][A-Za-z0-9_.\-]*")
8
+ *
9
+ * matched with `fullmatch`. A path segment must start with an alphanumeric or
10
+ * underscore, then may contain alphanumerics, underscore, dot or hyphen.
11
+ */
12
+ import { DEFAULT_IRI_BASE } from './constants.js';
13
+ /** Reserved filenames that are NOT concepts (SPEC §3.1, §6, §7). */
14
+ export const RESERVED_FILENAMES = new Set(['index.md', 'log.md']);
15
+ /** Mirrors `paths.py` `_SEGMENT_RE` used with `fullmatch`. */
16
+ const SEGMENT_RE = /^[A-Za-z0-9_][A-Za-z0-9_.\-]*$/;
17
+ /** A scheme-prefixed URL (http:, https:, mailto:, urn:, …) — never a concept link. */
18
+ const SCHEME_RE = /^[A-Za-z][A-Za-z0-9+.-]*:/;
19
+ export function isValidSegment(segment) {
20
+ return SEGMENT_RE.test(segment);
21
+ }
22
+ /** POSIX basename of a bundle-relative path. */
23
+ export function basename(path) {
24
+ const parts = path.split('/');
25
+ return parts[parts.length - 1] ?? '';
26
+ }
27
+ /** True for reserved `index.md` / `log.md` at any depth (SPEC §3.1). */
28
+ export function isReservedFile(path) {
29
+ return RESERVED_FILENAMES.has(basename(path));
30
+ }
31
+ /** True for a non-reserved `.md` file (i.e. a concept document, SPEC §4). */
32
+ export function isConceptFile(path) {
33
+ return path.endsWith('.md') && !isReservedFile(path);
34
+ }
35
+ /**
36
+ * Bundle-relative path → concept ID (path with `.md` removed, SPEC §2).
37
+ * `tables/blocks.md` → `tables/blocks`.
38
+ */
39
+ export function pathToConceptId(path) {
40
+ const noExt = path.endsWith('.md') ? path.slice(0, -3) : path;
41
+ // Normalise any backslashes a Windows caller might pass; bundles are POSIX.
42
+ return noExt.split(/[\\/]/).filter((s) => s.length > 0).join('/');
43
+ }
44
+ /** Concept ID → deterministic subject IRI. Same bundle ⇒ same IRI. */
45
+ export function conceptIdToIri(conceptId, iriBase = DEFAULT_IRI_BASE) {
46
+ return `${iriBase}${conceptId}`;
47
+ }
48
+ /**
49
+ * Node-side Knowledge Asset / assertion name for a concept.
50
+ *
51
+ * DKG asset/assertion names cannot contain `/`, but OKF concept IDs are
52
+ * path-based (`tables/blocks`). The encoding must be **injective** — a naive
53
+ * `/`→`__` collapses `a/b` and the literal concept `a__b` onto the same name.
54
+ * So escape the escape character first (`_`→`_5f`), then `/`→`_2f` (the chars'
55
+ * hex codes). `a/b`→`a_2fb`, `a__b`→`a_5f_5fb` — distinct. The RDF subject IRI
56
+ * is unaffected; it keeps the original `/`-bearing concept ID.
57
+ */
58
+ export function conceptIdToKaName(conceptId) {
59
+ return conceptId.replace(/_/g, '_5f').replace(/\//g, '_2f');
60
+ }
61
+ /**
62
+ * Resolve a Markdown link `href` written inside the concept `fromConceptId`
63
+ * into a candidate target concept ID, per SPEC §5. Handles:
64
+ * - absolute (bundle-relative): `/tables/customers.md`
65
+ * - relative: `./other.md`, `../tables/x.md`
66
+ * - bare-sibling: `x.md`
67
+ * - extension-less variants: `x`, `../tables/x`
68
+ * - `#anchor` / `?query` suffixes are stripped first
69
+ *
70
+ * Returns `null` for: external URLs (scheme-prefixed), pure anchors, paths that
71
+ * escape the bundle root, directory links, or any candidate whose segments fail
72
+ * the `paths.py` validation regex (so it could not be a concept ID anyway).
73
+ *
74
+ * Note: this returns a *candidate* — whether the target actually exists in the
75
+ * bundle is decided by the caller against the Pass-1 concept map. A candidate
76
+ * that does not exist is a broken link, which is NOT an error (SPEC §5.3/§9).
77
+ */
78
+ export function resolveLinkTarget(href, fromConceptId) {
79
+ // Strip anchor / query.
80
+ let target = href.split('#')[0].split('?')[0].trim();
81
+ if (target.length === 0)
82
+ return null;
83
+ // External URL (http:, mailto:, …) — not a concept link.
84
+ if (SCHEME_RE.test(target))
85
+ return null;
86
+ // A trailing slash denotes a directory, not a concept document.
87
+ if (target.endsWith('/'))
88
+ return null;
89
+ let stack;
90
+ if (target.startsWith('/')) {
91
+ // Absolute, bundle-relative.
92
+ stack = [];
93
+ target = target.slice(1);
94
+ }
95
+ else {
96
+ // Relative to the linking concept's directory.
97
+ stack = fromConceptId.split('/').slice(0, -1);
98
+ }
99
+ for (const part of target.split('/')) {
100
+ if (part === '' || part === '.')
101
+ continue;
102
+ if (part === '..') {
103
+ if (stack.length === 0)
104
+ return null; // escapes the bundle root
105
+ stack.pop();
106
+ continue;
107
+ }
108
+ stack.push(part);
109
+ }
110
+ if (stack.length === 0)
111
+ return null;
112
+ // Drop a trailing `.md` extension (extension-less links are left as-is).
113
+ const last = stack[stack.length - 1];
114
+ if (last.endsWith('.md')) {
115
+ stack[stack.length - 1] = last.slice(0, -3);
116
+ }
117
+ if (stack[stack.length - 1].length === 0)
118
+ return null; // directory link
119
+ // Every segment must be a valid concept-ID segment, else it can't be a concept.
120
+ for (const seg of stack) {
121
+ if (!isValidSegment(seg))
122
+ return null;
123
+ }
124
+ return stack.join('/');
125
+ }
126
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Public types for the OKF → DKG mapper.
3
+ *
4
+ * The RDF output is a deterministic array of `Quad`s. We reuse the node's
5
+ * canonical `Quad` shape (`{ subject, predicate, object, graph? }`) so the
6
+ * mapper's output drops straight into the importer/`/api/assertion/write`
7
+ * path without translation.
8
+ */
9
+ /** Canonical quad shape, structurally identical to `dkg-core`'s extraction `Quad`. */
10
+ export interface Quad {
11
+ subject: string;
12
+ predicate: string;
13
+ object: string;
14
+ graph?: string;
15
+ }
16
+ /** A parsed OKF concept document (frontmatter + body), per SPEC §4. */
17
+ export interface OkfDocument {
18
+ /** Bundle-relative concept ID (path with `.md` removed), e.g. `tables/blocks`. */
19
+ conceptId: string;
20
+ /** Path segments of the concept ID, e.g. `['tables', 'blocks']`. */
21
+ segments: string[];
22
+ /** Parsed YAML frontmatter (empty object if none). */
23
+ frontmatter: Record<string, unknown>;
24
+ /** Markdown body (everything after the closing `---`). */
25
+ body: string;
26
+ }
27
+ /** A resolved or unresolved cross-link discovered in a concept body (SPEC §5). */
28
+ export interface OkfLink {
29
+ /** Raw link target as written in the Markdown, e.g. `../tables/blocks.md`. */
30
+ raw: string;
31
+ /** Resolved target concept ID if it exists in the bundle, else `null`. */
32
+ targetConceptId: string | null;
33
+ /** True when the link sat inside an inline code span (SPEC/CommonMark edge case). */
34
+ inCodeSpan: boolean;
35
+ }
36
+ /** A citation captured from a `# Citations` section (SPEC §8). */
37
+ export interface OkfCitation {
38
+ /** The cited URL (external in the wild). */
39
+ url: string;
40
+ /** Optional human label (numbered `[n] [label](url)` style). */
41
+ label?: string;
42
+ }
43
+ /**
44
+ * A deterministic, opt-in rule that types a cross-concept edge by the OKF
45
+ * `type` of its two endpoints — no LLM, no prose parsing. e.g. a link from a
46
+ * `BigQuery Dataset` to a `BigQuery Table` is containment (`schema:hasPart`),
47
+ * while `BigQuery Table` → `BigQuery Table` stays an untyped reference
48
+ * (`schema:mentions`). Both endpoint types come straight from frontmatter, so
49
+ * this is byte-stable. Default behaviour (no rules) keeps every edge as
50
+ * `schema:mentions`, preserving the "links are untyped per SPEC §5.3" guarantee.
51
+ */
52
+ export interface TypeRelation {
53
+ /** Source concept's OKF `type` (exact string, e.g. `BigQuery Dataset`). */
54
+ from: string;
55
+ /** Target concept's OKF `type`. */
56
+ to: string;
57
+ /** Predicate IRI to use for edges matching this (from,to) type pair. */
58
+ predicate: string;
59
+ }
60
+ /** Mapping options. All deterministic; no network, no LLM. */
61
+ export interface OkfMappingOptions {
62
+ /** IRI namespace for concept subjects. Default `urn:okf:`. */
63
+ iriBase?: string;
64
+ /**
65
+ * Opt-in type-pair edge typing. Empty/undefined ⇒ every cross-concept edge is
66
+ * `schema:mentions` (the faithful, zero-interpretation default).
67
+ */
68
+ typeRelations?: TypeRelation[];
69
+ /**
70
+ * Whether a concept link written inside an inline code span counts as an edge.
71
+ * Default `false` — CommonMark treats code-span content as literal text, so it
72
+ * is NOT a link. See ADR 0005 / CONTEXT.md "Flagged ambiguities".
73
+ */
74
+ includeCodeSpanLinks?: boolean;
75
+ /**
76
+ * Whether to emit `schema:isPartOf` triples reflecting the folder hierarchy.
77
+ * Default `false` — directories are not concepts, so minting them as nodes
78
+ * muddies the concept graph. See ADR 0005.
79
+ */
80
+ emitFolderHierarchy?: boolean;
81
+ }
82
+ /** Per-concept mapping result. */
83
+ export interface ConceptMapping {
84
+ conceptId: string;
85
+ /** Deterministic subject IRI for this concept's Knowledge Asset. */
86
+ iri: string;
87
+ /** Content + linkage triples for this concept. */
88
+ quads: Quad[];
89
+ /** Resolved concept→concept edges (subset of links). */
90
+ resolvedLinks: OkfLink[];
91
+ /** Links whose target is not in the bundle (warned, never fatal — SPEC §5.3/§9). */
92
+ brokenLinks: OkfLink[];
93
+ /** Links skipped because they sat in a code span and the option is off. */
94
+ codeSpanLinks: OkfLink[];
95
+ /** Citations captured (distinct from concept edges). */
96
+ citations: OkfCitation[];
97
+ }
98
+ /** A non-fatal diagnostic surfaced during a bundle import. */
99
+ export interface OkfWarning {
100
+ conceptId?: string;
101
+ code: 'broken-link' | 'code-span-link' | 'missing-type' | 'reserved-skip' | 'parse' | 'invalid-path';
102
+ message: string;
103
+ }
104
+ /** Result of importing a whole bundle. */
105
+ export interface BundleImport {
106
+ /** OKF version declared in the root `index.md`, if any (SPEC §11). */
107
+ okfVersion: string | null;
108
+ /** Concept ID → subject IRI map (Pass 1). */
109
+ iriByConceptId: Record<string, string>;
110
+ /** Per-concept mappings (Pass 2). */
111
+ concepts: ConceptMapping[];
112
+ /** Reserved files (`index.md` / `log.md`) skipped — never minted as KAs. */
113
+ reservedSkipped: string[];
114
+ /** All quads across all concepts, in concept order. */
115
+ quads: Quad[];
116
+ /** Non-fatal diagnostics. */
117
+ warnings: OkfWarning[];
118
+ }
119
+ /** A single file fed to the in-memory mapper (path is bundle-relative, POSIX). */
120
+ export interface BundleFile {
121
+ /** Bundle-relative POSIX path including extension, e.g. `tables/blocks.md`. */
122
+ path: string;
123
+ /** UTF-8 file contents. */
124
+ content: string;
125
+ }
126
+ /** §9 conformance report. */
127
+ export interface ConformanceReport {
128
+ conformant: boolean;
129
+ /** Hard violations — only §9 rules 1–2 (parseable frontmatter + non-empty `type`) make a bundle non-conformant; reserved-file structure issues are warnings. */
130
+ errors: string[];
131
+ /** Things consumers MUST tolerate (§9) — surfaced as info, never failing. */
132
+ warnings: string[];
133
+ }
134
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}