@hraness/kb 0.17.1

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 (200) hide show
  1. package/DISCLOSURE +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +545 -0
  4. package/dist/agent-context.js +35 -0
  5. package/dist/agent-guide-audit.js +18 -0
  6. package/dist/attachments.js +19 -0
  7. package/dist/authoring.js +33 -0
  8. package/dist/benchmark.js +19 -0
  9. package/dist/capture.js +49 -0
  10. package/dist/cli.js +3646 -0
  11. package/dist/clip/acquire.js +57 -0
  12. package/dist/clip/args.js +21 -0
  13. package/dist/clip/bounded-byte-buffer.js +9 -0
  14. package/dist/clip/browser-profiles.js +17 -0
  15. package/dist/clip/bundle-reader.js +11 -0
  16. package/dist/clip/cli.js +27 -0
  17. package/dist/clip/cookies.js +21 -0
  18. package/dist/clip/defuddle-worker.js +84 -0
  19. package/dist/clip/doctor.js +32 -0
  20. package/dist/clip/jobs.js +784 -0
  21. package/dist/clip/network-proxy.js +9 -0
  22. package/dist/clip/network.js +28 -0
  23. package/dist/clip/persist.js +30 -0
  24. package/dist/clip/refresh.js +11 -0
  25. package/dist/clip/terminal.js +11 -0
  26. package/dist/evaluation-builder.js +12238 -0
  27. package/dist/evaluation-kb.js +26 -0
  28. package/dist/evaluation.js +35 -0
  29. package/dist/git.js +39 -0
  30. package/dist/graph.js +39 -0
  31. package/dist/index-01jj6rbv.js +1103 -0
  32. package/dist/index-06c9ctr6.js +738 -0
  33. package/dist/index-0kavxzqj.js +2480 -0
  34. package/dist/index-1gwbassd.js +743 -0
  35. package/dist/index-1n418kb9.js +827 -0
  36. package/dist/index-1vrd1rmn.js +56 -0
  37. package/dist/index-1xxnjn0d.js +171 -0
  38. package/dist/index-3rm7cz6h.js +373 -0
  39. package/dist/index-3v2z4f0q.js +328 -0
  40. package/dist/index-48pz4jpc.js +392 -0
  41. package/dist/index-4j3tt0c3.js +234 -0
  42. package/dist/index-5n05se68.js +307 -0
  43. package/dist/index-5vwpzb5a.js +764 -0
  44. package/dist/index-6g2pv9d2.js +370 -0
  45. package/dist/index-7fzc32gf.js +712 -0
  46. package/dist/index-84x0vjjp.js +432 -0
  47. package/dist/index-adx6khj5.js +488 -0
  48. package/dist/index-b88v3vtm.js +805 -0
  49. package/dist/index-cv6fh7z5.js +462 -0
  50. package/dist/index-cxfrakt7.js +1067 -0
  51. package/dist/index-d13v9ckt.js +150 -0
  52. package/dist/index-dycjbhw2.js +1943 -0
  53. package/dist/index-dyqwejk5.js +531 -0
  54. package/dist/index-e5fbsywq.js +658 -0
  55. package/dist/index-f984hw45.js +927 -0
  56. package/dist/index-gh719d91.js +63 -0
  57. package/dist/index-hgve9rh2.js +2046 -0
  58. package/dist/index-hya40gb2.js +474 -0
  59. package/dist/index-j4zgmzjr.js +152 -0
  60. package/dist/index-jsmvyyvf.js +1166 -0
  61. package/dist/index-kh0pwvdd.js +1974 -0
  62. package/dist/index-mqx4nd6v.js +119 -0
  63. package/dist/index-mxxxytys.js +1254 -0
  64. package/dist/index-n5dd7r0v.js +711 -0
  65. package/dist/index-npg9z1a4.js +431 -0
  66. package/dist/index-pj501bh1.js +140 -0
  67. package/dist/index-qry4vhxk.js +1041 -0
  68. package/dist/index-s2gw5aw9.js +199 -0
  69. package/dist/index-vvdj6kca.js +70 -0
  70. package/dist/index-w2zc0vwa.js +571 -0
  71. package/dist/index-x3fthpsc.js +585 -0
  72. package/dist/index-zr53sf63.js +44 -0
  73. package/dist/index-zxdy5pby.js +2728 -0
  74. package/dist/index-zzhgcwyt.js +966 -0
  75. package/dist/index.js +453 -0
  76. package/dist/navigation.js +13 -0
  77. package/dist/pdf.js +85 -0
  78. package/dist/percolate.js +24 -0
  79. package/dist/portfolio.js +77 -0
  80. package/dist/query.js +29 -0
  81. package/dist/repository-memory.js +60 -0
  82. package/dist/sdk.js +36 -0
  83. package/dist/search-rules.js +34 -0
  84. package/dist/search.js +22 -0
  85. package/dist/semantic.js +39 -0
  86. package/dist/source-inbox.js +17 -0
  87. package/dist/untrusted-content.js +32 -0
  88. package/dist/url-intelligence.js +74 -0
  89. package/dist/workflow.js +23 -0
  90. package/dist/workflows/decision-context.js +20 -0
  91. package/dist/workflows/explain-change.js +8 -0
  92. package/dist/workflows/index.js +28 -0
  93. package/dist/workflows/plan-radar.js +10 -0
  94. package/package.json +409 -0
  95. package/skills/kb/AGENTS.md +19 -0
  96. package/skills/kb/SKILL.md +82 -0
  97. package/skills/kb/agents/openai.yaml +4 -0
  98. package/skills/kb/references/pdf-review.md +51 -0
  99. package/skills/kb/references/percolate.md +144 -0
  100. package/skills/kb/references/plan-structure.md +105 -0
  101. package/skills/kb/references/plan.md +115 -0
  102. package/skills/kb/references/query.md +231 -0
  103. package/skills/kb/references/refresh.md +133 -0
  104. package/skills/kb/references/save-pdf.md +125 -0
  105. package/skills/kb/references/save-url.md +178 -0
  106. package/skills/kb/references/url-authentication.md +75 -0
  107. package/skills/kb/references/url-platforms.md +25 -0
  108. package/src/agent-context.ts +1241 -0
  109. package/src/agent-guide-audit.ts +750 -0
  110. package/src/attachments.ts +723 -0
  111. package/src/authoring.ts +1725 -0
  112. package/src/benchmark.ts +362 -0
  113. package/src/capture.ts +68 -0
  114. package/src/cli.ts +3660 -0
  115. package/src/clip/acquire.ts +1273 -0
  116. package/src/clip/archive-today.ts +402 -0
  117. package/src/clip/args.ts +423 -0
  118. package/src/clip/assets.ts +262 -0
  119. package/src/clip/bounded-byte-buffer.ts +75 -0
  120. package/src/clip/browser-profiles.ts +372 -0
  121. package/src/clip/bundle-reader.ts +608 -0
  122. package/src/clip/capture.ts +1054 -0
  123. package/src/clip/cli.ts +181 -0
  124. package/src/clip/cookies.ts +512 -0
  125. package/src/clip/doctor.ts +1110 -0
  126. package/src/clip/extract.ts +868 -0
  127. package/src/clip/jobs.ts +959 -0
  128. package/src/clip/lib.ts +861 -0
  129. package/src/clip/media.ts +1474 -0
  130. package/src/clip/metadata-search-tool/Cargo.lock +2498 -0
  131. package/src/clip/metadata-search-tool/Cargo.toml +18 -0
  132. package/src/clip/metadata-search-tool/runner.ts +685 -0
  133. package/src/clip/metadata-search-tool/src/main.rs +389 -0
  134. package/src/clip/metadata-search.ts +687 -0
  135. package/src/clip/network-proxy.ts +627 -0
  136. package/src/clip/network.ts +950 -0
  137. package/src/clip/package-root.ts +54 -0
  138. package/src/clip/persist.ts +1472 -0
  139. package/src/clip/platforms.ts +1824 -0
  140. package/src/clip/refresh.ts +197 -0
  141. package/src/clip/structured.ts +432 -0
  142. package/src/clip/terminal.ts +197 -0
  143. package/src/clip/url-intelligence.ts +581 -0
  144. package/src/clip/url-metadata-backfill.ts +616 -0
  145. package/src/clip/url-metadata-cli.ts +251 -0
  146. package/src/clip/url-metadata.ts +1397 -0
  147. package/src/evaluation-analysis-v2.ts +1440 -0
  148. package/src/evaluation-builder.ts +2539 -0
  149. package/src/evaluation-corpus-authoring.ts +2052 -0
  150. package/src/evaluation-evidence.ts +1147 -0
  151. package/src/evaluation-execution-v2.ts +426 -0
  152. package/src/evaluation-implementation.ts +131 -0
  153. package/src/evaluation-kb-closure.ts +1430 -0
  154. package/src/evaluation-kb-runner-v2.ts +735 -0
  155. package/src/evaluation-kb-v2.ts +2567 -0
  156. package/src/evaluation-kb.ts +1018 -0
  157. package/src/evaluation-measurement-v2.ts +1462 -0
  158. package/src/evaluation-packing-v2.ts +452 -0
  159. package/src/evaluation-redaction.ts +28 -0
  160. package/src/evaluation-v2.ts +3857 -0
  161. package/src/evaluation.ts +1220 -0
  162. package/src/git.ts +1276 -0
  163. package/src/graph.ts +1487 -0
  164. package/src/index.ts +20 -0
  165. package/src/init.ts +123 -0
  166. package/src/navigation.ts +277 -0
  167. package/src/note-lock.ts +529 -0
  168. package/src/pdf/args.ts +202 -0
  169. package/src/pdf/capture.ts +298 -0
  170. package/src/pdf/cli.ts +159 -0
  171. package/src/pdf/extract.ts +303 -0
  172. package/src/pdf/layout.ts +618 -0
  173. package/src/pdf/markdown.ts +154 -0
  174. package/src/pdf/model.ts +234 -0
  175. package/src/pdf/ocr.ts +190 -0
  176. package/src/pdf/persist.ts +314 -0
  177. package/src/pdf/source.ts +135 -0
  178. package/src/pdf/tools.ts +146 -0
  179. package/src/pdf.ts +92 -0
  180. package/src/percolate.ts +877 -0
  181. package/src/portfolio-audit.ts +574 -0
  182. package/src/portfolio-identity.ts +162 -0
  183. package/src/portfolio-registry.ts +473 -0
  184. package/src/portfolio.ts +765 -0
  185. package/src/query.ts +646 -0
  186. package/src/repository-memory.ts +1150 -0
  187. package/src/sdk.ts +1452 -0
  188. package/src/search-rules.ts +712 -0
  189. package/src/search.ts +710 -0
  190. package/src/semantic-runtime.ts +1139 -0
  191. package/src/semantic.ts +2480 -0
  192. package/src/source-inbox.ts +232 -0
  193. package/src/untrusted-content.ts +401 -0
  194. package/src/url-intelligence.ts +104 -0
  195. package/src/vault.ts +636 -0
  196. package/src/workflow.ts +653 -0
  197. package/src/workflows/decision-context.ts +81 -0
  198. package/src/workflows/explain-change.ts +73 -0
  199. package/src/workflows/index.ts +3 -0
  200. package/src/workflows/plan-radar.ts +101 -0
@@ -0,0 +1,1397 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import {
4
+ closeSync,
5
+ constants,
6
+ fchmodSync,
7
+ fstatSync,
8
+ ftruncateSync,
9
+ fsyncSync,
10
+ lstatSync,
11
+ openSync,
12
+ readFileSync,
13
+ readSync,
14
+ readdirSync,
15
+ realpathSync,
16
+ renameSync,
17
+ unlinkSync,
18
+ writeFileSync,
19
+ writeSync,
20
+ type BigIntStats,
21
+ } from "node:fs";
22
+ import { hostname } from "node:os";
23
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
24
+
25
+ import { parseDocument } from "yaml";
26
+
27
+ import { isPrivateHostname } from "./network.js";
28
+ import { sanitizeArtifactUrl } from "./persist.js";
29
+ import { parseArchiveTodayMementoUrl } from "./url-intelligence.js";
30
+
31
+ export const URL_METADATA_FILENAME = "url-metadata.json";
32
+ export const URL_METADATA_SCHEMA_VERSION = 1 as const;
33
+ export const METADATA_SEARCH_ENGINE_ID = "metadata-search-engine-rs" as const;
34
+ export const METADATA_SEARCH_ENGINE_VERSION = "0.1.3" as const;
35
+ export const METADATA_SEARCH_ENGINE_REVISION = "f40a00ea67a857ee996e1caba1ebab3ee7a14a47" as const;
36
+
37
+ const MAX_ARTICLE_DIRECTORIES = 4_096;
38
+ const MAX_DIRECTORY_ENTRIES = 256;
39
+ const MAX_MARKDOWN_BYTES = 2 * 1024 * 1024;
40
+ const MAX_SIDECAR_BYTES = 1024 * 1024;
41
+ const MAX_CANDIDATES = 20;
42
+ const MAX_ARCHIVES = 32;
43
+ const MAX_ATTEMPTS = 16;
44
+ const MAX_WARNINGS = 32;
45
+ const MAX_URL_BYTES = 16 * 1024;
46
+ const MAX_TITLE_BYTES = 2 * 1024;
47
+ const MAX_SNIPPET_BYTES = 8 * 1024;
48
+ const MAX_MESSAGE_BYTES = 2 * 1024;
49
+ const MAX_ENGINE_COUNT = 8;
50
+ const MAX_LOCK_BYTES = 4 * 1024;
51
+ const MALFORMED_LOCK_RECOVERY_AGE_MILLISECONDS = 30_000;
52
+ const LOCK_EXCLUSIVE = 0x02;
53
+ const LOCK_NONBLOCKING = 0x04;
54
+ const F_SET_FILE_DESCRIPTOR = 2;
55
+ const FILE_DESCRIPTOR_CLOSE_ON_EXEC = 1;
56
+ const INTERRUPTED_SYSTEM_CALL = 4;
57
+ const engineNamePattern = /^[a-z0-9][a-z0-9._-]{0,63}$/u;
58
+ const lockTokenPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
59
+
60
+ type NativeFlockFunction = (descriptor: number, operation: number) => number;
61
+ type NativeFcntlFunction = (descriptor: number, command: number, argument: number) => number;
62
+ type NativePointer = number;
63
+ type BunFfiRuntime = {
64
+ readonly dlopen: (
65
+ path: string,
66
+ symbols: Readonly<Record<string, {
67
+ readonly args: readonly string[];
68
+ readonly returns: string;
69
+ }>>,
70
+ ) => {
71
+ readonly symbols: Readonly<Record<string, (...arguments_: readonly number[]) => number>>;
72
+ };
73
+ readonly read: {
74
+ readonly i32: (pointer: NativePointer) => number;
75
+ };
76
+ };
77
+
78
+ let nativeFlock: NativeFlockFunction | undefined;
79
+ let nativeFcntl: NativeFcntlFunction | undefined;
80
+ let nativeErrnoLocation: (() => NativePointer) | undefined;
81
+ let nativeFfiRead: BunFfiRuntime["read"] | undefined;
82
+ const nativeLockLibraries: unknown[] = [];
83
+
84
+ export type SavedUrlRecord = {
85
+ readonly articleId: string;
86
+ readonly directory: string;
87
+ readonly markdownPath: string;
88
+ readonly sidecarPath: string;
89
+ readonly subjectUrl: string;
90
+ };
91
+
92
+ export type UrlMetadataCandidate = {
93
+ readonly title: string;
94
+ readonly url: string;
95
+ readonly snippet: string | null;
96
+ readonly engines: readonly string[];
97
+ readonly score: number;
98
+ };
99
+
100
+ export type UrlMetadataArchive = {
101
+ readonly url: string;
102
+ readonly capturedAt: string;
103
+ readonly discovery: "newest" | "timemap" | "metadata-search";
104
+ };
105
+
106
+ export type UrlMetadataAttempt = {
107
+ readonly provider: "metadata-search-engine-rs" | "archive-today";
108
+ readonly outcome: "succeeded" | "partial" | "not-found" | "failed" | "skipped";
109
+ readonly message: string;
110
+ };
111
+
112
+ export type UrlMetadataSelectedField = {
113
+ readonly value: string;
114
+ readonly sourceUrl: string;
115
+ readonly provider: typeof METADATA_SEARCH_ENGINE_ID;
116
+ };
117
+
118
+ export type UrlMetadataDocument = {
119
+ readonly schemaVersion: typeof URL_METADATA_SCHEMA_VERSION;
120
+ readonly kind: "url-metadata";
121
+ readonly subjectUrl: string;
122
+ readonly generatedAt: string;
123
+ readonly status: "matched" | "not-found" | "partial" | "unavailable";
124
+ readonly provider: {
125
+ readonly id: typeof METADATA_SEARCH_ENGINE_ID;
126
+ readonly version: typeof METADATA_SEARCH_ENGINE_VERSION;
127
+ readonly revision: typeof METADATA_SEARCH_ENGINE_REVISION;
128
+ readonly enginesQueried: readonly string[];
129
+ readonly enginesFailed: readonly string[];
130
+ };
131
+ readonly attempts: readonly UrlMetadataAttempt[];
132
+ readonly candidates: readonly UrlMetadataCandidate[];
133
+ readonly selected: {
134
+ readonly title: UrlMetadataSelectedField | null;
135
+ readonly description: UrlMetadataSelectedField | null;
136
+ };
137
+ readonly archives: readonly UrlMetadataArchive[];
138
+ readonly warnings: readonly string[];
139
+ };
140
+
141
+ export type CreateUrlMetadataDocumentInput = {
142
+ readonly subjectUrl: string;
143
+ readonly generatedAt: string;
144
+ readonly enginesQueried: readonly string[];
145
+ readonly enginesFailed: readonly string[];
146
+ readonly attempts: readonly UrlMetadataAttempt[];
147
+ readonly candidates: readonly UrlMetadataCandidate[];
148
+ readonly archives?: readonly UrlMetadataArchive[];
149
+ readonly warnings?: readonly string[];
150
+ };
151
+
152
+ type FileIdentity = {
153
+ readonly device: bigint;
154
+ readonly inode: bigint;
155
+ readonly size: bigint;
156
+ readonly mode: bigint;
157
+ readonly links: bigint;
158
+ readonly modifiedNanoseconds: bigint;
159
+ readonly changedNanoseconds: bigint;
160
+ };
161
+
162
+ type DirectoryIdentity = {
163
+ readonly device: bigint;
164
+ readonly inode: bigint;
165
+ readonly mode: bigint;
166
+ };
167
+
168
+ type SavedUrlRecordIdentity = {
169
+ readonly directory: DirectoryIdentity;
170
+ readonly markdown: FileIdentity;
171
+ };
172
+
173
+ type UrlMetadataWriteLockOwner = {
174
+ readonly schemaVersion: 1;
175
+ readonly kind: "url-metadata-write-lock";
176
+ readonly host: string;
177
+ readonly pid: number;
178
+ readonly processIdentity: string;
179
+ readonly token: string;
180
+ readonly acquiredAt: string;
181
+ };
182
+
183
+ type HeldUrlMetadataWriteLock = {
184
+ readonly descriptor: number;
185
+ readonly identity: FileIdentity;
186
+ readonly owner: UrlMetadataWriteLockOwner;
187
+ readonly path: string;
188
+ };
189
+
190
+ const savedUrlRecordIdentities = new WeakMap<SavedUrlRecord, SavedUrlRecordIdentity>();
191
+
192
+ function requiredNativeSymbol(
193
+ symbols: Readonly<Record<string, (...arguments_: readonly number[]) => number>>,
194
+ name: string,
195
+ ): (...arguments_: readonly number[]) => number {
196
+ const symbol = symbols[name];
197
+ if (symbol === undefined) throw new Error(`URL metadata locking could not bind ${name}.`);
198
+ return symbol;
199
+ }
200
+
201
+ function linuxLibcCandidates(processMaps: string, architecture = process.arch): readonly string[] {
202
+ const candidates: string[] = [];
203
+ const append = (candidate: string): void => {
204
+ if (!candidates.includes(candidate)) candidates.push(candidate);
205
+ };
206
+ for (const line of processMaps.split("\n")) {
207
+ const pathStart = line.indexOf("/");
208
+ if (pathStart < 0 || line.endsWith(" (deleted)")) continue;
209
+ const path = line.slice(pathStart).replace(
210
+ /\\([0-7]{3})/gu,
211
+ (_, octal: string) => String.fromCodePoint(Number.parseInt(octal, 8)),
212
+ );
213
+ if (
214
+ /\/(?:libc(?:-[^/]+)?\.so(?:\.[0-9]+)*|libc\.musl-[^/]+\.so(?:\.[0-9]+)*|ld-musl-[^/]+\.so(?:\.[0-9]+)*)$/u
215
+ .test(path)
216
+ ) append(path);
217
+ }
218
+ append("libc.so.6");
219
+ if (architecture === "x64") append("/lib/ld-musl-x86_64.so.1");
220
+ if (architecture === "arm64") append("/lib/ld-musl-aarch64.so.1");
221
+ return candidates;
222
+ }
223
+
224
+ function initializeNativeLocking(): void {
225
+ if (nativeFlock !== undefined && nativeFcntl !== undefined && nativeErrnoLocation !== undefined) return;
226
+ const ffi = (globalThis as typeof globalThis & {
227
+ readonly Bun?: { readonly FFI?: BunFfiRuntime };
228
+ }).Bun?.FFI;
229
+ if (ffi === undefined) {
230
+ throw new Error("URL metadata writes require Bun's native file-locking runtime.");
231
+ }
232
+ nativeFfiRead = ffi.read;
233
+ if (process.platform === "darwin") {
234
+ const library = ffi.dlopen("/usr/lib/libSystem.B.dylib", {
235
+ __error: { args: [], returns: "ptr" },
236
+ fcntl: { args: ["i32", "i32", "i32"], returns: "i32" },
237
+ flock: { args: ["i32", "i32"], returns: "i32" },
238
+ });
239
+ nativeLockLibraries.push(library);
240
+ const errnoLocation = requiredNativeSymbol(library.symbols, "__error");
241
+ const fcntl = requiredNativeSymbol(library.symbols, "fcntl");
242
+ const flock = requiredNativeSymbol(library.symbols, "flock");
243
+ nativeErrnoLocation = () => errnoLocation();
244
+ nativeFcntl = (descriptor, command, argument) => fcntl(descriptor, command, argument);
245
+ nativeFlock = (descriptor, operation) => flock(descriptor, operation);
246
+ return;
247
+ }
248
+ if (process.platform === "linux") {
249
+ let processMaps = "";
250
+ try {
251
+ processMaps = readFileSync("/proc/self/maps", "utf8");
252
+ } catch {
253
+ // The fallback library names below still cover glibc and common musl layouts.
254
+ }
255
+ const failures: string[] = [];
256
+ for (const candidate of linuxLibcCandidates(processMaps)) {
257
+ try {
258
+ const library = ffi.dlopen(candidate, {
259
+ __errno_location: { args: [], returns: "ptr" },
260
+ fcntl: { args: ["i32", "i32", "i32"], returns: "i32" },
261
+ flock: { args: ["i32", "i32"], returns: "i32" },
262
+ });
263
+ nativeLockLibraries.push(library);
264
+ const errnoLocation = requiredNativeSymbol(library.symbols, "__errno_location");
265
+ const fcntl = requiredNativeSymbol(library.symbols, "fcntl");
266
+ const flock = requiredNativeSymbol(library.symbols, "flock");
267
+ nativeErrnoLocation = () => errnoLocation();
268
+ nativeFcntl = (descriptor, command, argument) => fcntl(descriptor, command, argument);
269
+ nativeFlock = (descriptor, operation) => flock(descriptor, operation);
270
+ return;
271
+ } catch (error) {
272
+ failures.push(`${candidate}: ${error instanceof Error ? error.message : String(error)}`);
273
+ }
274
+ }
275
+ throw new Error(`URL metadata locking could not load the host libc (${failures.join("; ")}).`);
276
+ }
277
+ throw new Error(`URL metadata locking is unsupported on ${process.platform}.`);
278
+ }
279
+
280
+ function currentNativeErrno(): number {
281
+ initializeNativeLocking();
282
+ const pointer = nativeErrnoLocation?.();
283
+ if (pointer === undefined || pointer === null) throw new Error("URL metadata locking could not read errno.");
284
+ if (nativeFfiRead === undefined) throw new Error("URL metadata locking lost its native read binding.");
285
+ return nativeFfiRead.i32(pointer);
286
+ }
287
+
288
+ function tryExclusiveFileLock(descriptor: number): boolean {
289
+ initializeNativeLocking();
290
+ for (;;) {
291
+ if (nativeFlock?.(descriptor, LOCK_EXCLUSIVE | LOCK_NONBLOCKING) === 0) return true;
292
+ const errno = currentNativeErrno();
293
+ if (errno === INTERRUPTED_SYSTEM_CALL) continue;
294
+ if ((process.platform === "darwin" && errno === 35) || (process.platform === "linux" && errno === 11)) {
295
+ return false;
296
+ }
297
+ throw new Error(`URL metadata flock failed with errno ${errno}.`);
298
+ }
299
+ }
300
+
301
+ function setDescriptorCloseOnExec(descriptor: number): void {
302
+ initializeNativeLocking();
303
+ if (nativeFcntl?.(descriptor, F_SET_FILE_DESCRIPTOR, FILE_DESCRIPTOR_CLOSE_ON_EXEC) !== 0) {
304
+ throw new Error(`URL metadata fcntl failed with errno ${currentNativeErrno()}.`);
305
+ }
306
+ }
307
+
308
+ function compareText(left: string, right: string): number {
309
+ return left < right ? -1 : left > right ? 1 : 0;
310
+ }
311
+
312
+ function errorFromUnknown(value: unknown, message: string): Error {
313
+ return value instanceof Error ? value : new Error(message, { cause: value });
314
+ }
315
+
316
+ function throwCleanupErrorWithoutOperationError(operationError: unknown, cleanupError: unknown): void {
317
+ if (operationError === undefined && cleanupError !== undefined) {
318
+ throw errorFromUnknown(cleanupError, "URL metadata write cleanup failed.");
319
+ }
320
+ }
321
+
322
+ function utf8Length(value: string): number {
323
+ return new TextEncoder().encode(value).byteLength;
324
+ }
325
+
326
+ function record(value: unknown, label: string): Readonly<Record<string, unknown>> {
327
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
328
+ throw new TypeError(`${label} must be an object.`);
329
+ }
330
+ return value as Readonly<Record<string, unknown>>;
331
+ }
332
+
333
+ function exactKeys(
334
+ value: Readonly<Record<string, unknown>>,
335
+ keys: readonly string[],
336
+ label: string,
337
+ ): void {
338
+ const expected = new Set(keys);
339
+ for (const key of Object.keys(value)) {
340
+ if (!expected.has(key)) throw new TypeError(`${label} contains unknown key ${JSON.stringify(key)}.`);
341
+ }
342
+ for (const key of keys) {
343
+ if (!Object.hasOwn(value, key)) throw new TypeError(`${label} is missing ${JSON.stringify(key)}.`);
344
+ }
345
+ }
346
+
347
+ function boundedText(value: unknown, label: string, maximumBytes: number): string {
348
+ if (typeof value !== "string" || value === "" || utf8Length(value) > maximumBytes) {
349
+ throw new TypeError(`${label} must be a non-empty string of at most ${maximumBytes} UTF-8 bytes.`);
350
+ }
351
+ if (/\p{Cc}|\p{Cs}/u.test(value)) throw new TypeError(`${label} contains unsafe control characters.`);
352
+ return value;
353
+ }
354
+
355
+ function boundedTextList(
356
+ value: unknown,
357
+ label: string,
358
+ maximumCount: number,
359
+ maximumBytes: number,
360
+ ): readonly string[] {
361
+ if (!Array.isArray(value) || value.length > maximumCount) {
362
+ throw new TypeError(`${label} must be an array with at most ${maximumCount} entries.`);
363
+ }
364
+ const parsed = value.map((item, index) => boundedText(item, `${label}[${index}]`, maximumBytes));
365
+ if (new Set(parsed).size !== parsed.length) throw new TypeError(`${label} contains duplicate entries.`);
366
+ return Object.freeze(parsed);
367
+ }
368
+
369
+ function engineList(value: unknown, label: string): readonly string[] {
370
+ const engines = boundedTextList(value, label, MAX_ENGINE_COUNT, 64);
371
+ for (const engine of engines) {
372
+ if (!engineNamePattern.test(engine)) {
373
+ throw new TypeError(`${label} contains an invalid engine identifier.`);
374
+ }
375
+ }
376
+ return engines;
377
+ }
378
+
379
+ function normalizedPublicUrl(value: unknown, label: string): string {
380
+ const text = boundedText(value, label, MAX_URL_BYTES);
381
+ let url: URL;
382
+ try {
383
+ url = new URL(text);
384
+ } catch (error) {
385
+ throw new TypeError(`${label} must be an absolute HTTP(S) URL.`, { cause: error });
386
+ }
387
+ if ((url.protocol !== "https:" && url.protocol !== "http:") || url.username !== "" || url.password !== "") {
388
+ throw new TypeError(`${label} must be a credential-free HTTP(S) URL.`);
389
+ }
390
+ if (isPrivateHostname(url.hostname)) {
391
+ throw new TypeError(`${label} must not target a private network.`);
392
+ }
393
+ url.hash = "";
394
+ const normalized = url.href;
395
+ if (sanitizeArtifactUrl(normalized) !== normalized) {
396
+ throw new TypeError(`${label} must not contain credential-shaped path or query data.`);
397
+ }
398
+ return normalized;
399
+ }
400
+
401
+ function isoTimestamp(value: unknown, label: string): string {
402
+ const text = boundedText(value, label, 128);
403
+ const parsed = new Date(text);
404
+ if (!Number.isFinite(parsed.getTime()) || parsed.toISOString() !== text) {
405
+ throw new TypeError(`${label} must be a canonical ISO timestamp.`);
406
+ }
407
+ return text;
408
+ }
409
+
410
+ function parseUrlMetadataWriteLockOwner(value: unknown): UrlMetadataWriteLockOwner {
411
+ const input = record(value, "URL metadata write lock owner");
412
+ exactKeys(input, [
413
+ "schemaVersion",
414
+ "kind",
415
+ "host",
416
+ "pid",
417
+ "processIdentity",
418
+ "token",
419
+ "acquiredAt",
420
+ ], "URL metadata write lock owner");
421
+ if (input.schemaVersion !== 1 || input.kind !== "url-metadata-write-lock") {
422
+ throw new TypeError("URL metadata write lock owner has an unsupported identity.");
423
+ }
424
+ if (typeof input.pid !== "number" || !Number.isSafeInteger(input.pid) || input.pid <= 0) {
425
+ throw new TypeError("URL metadata write lock owner.pid must be a positive safe integer.");
426
+ }
427
+ const token = boundedText(input.token, "URL metadata write lock owner.token", 64);
428
+ if (!lockTokenPattern.test(token)) throw new TypeError("URL metadata write lock owner.token is invalid.");
429
+ return Object.freeze({
430
+ schemaVersion: 1,
431
+ kind: "url-metadata-write-lock",
432
+ host: boundedText(input.host, "URL metadata write lock owner.host", 255),
433
+ pid: input.pid,
434
+ processIdentity: boundedText(input.processIdentity, "URL metadata write lock owner.processIdentity", 512),
435
+ token,
436
+ acquiredAt: isoTimestamp(input.acquiredAt, "URL metadata write lock owner.acquiredAt"),
437
+ });
438
+ }
439
+
440
+ function operatingSystemProcessIdentity(pid: number): string | null {
441
+ if (!Number.isSafeInteger(pid) || pid <= 0) return null;
442
+ if (process.platform === "linux") {
443
+ try {
444
+ const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
445
+ const commandEnd = stat.lastIndexOf(")");
446
+ if (commandEnd < 0) return null;
447
+ const fields = stat.slice(commandEnd + 1).trim().split(/\s+/u);
448
+ const state = fields[0];
449
+ const startTicks = fields[19];
450
+ if (state === "Z" || startTicks === undefined || !/^\d+$/u.test(startTicks)) return null;
451
+ const bootId = readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim();
452
+ if (!/^[0-9a-f-]{36}$/u.test(bootId)) return null;
453
+ return `linux:${bootId}:${startTicks}`;
454
+ } catch {
455
+ return null;
456
+ }
457
+ }
458
+ if (process.platform === "darwin") {
459
+ try {
460
+ const started = execFileSync("/bin/ps", ["-o", "lstart=", "-p", String(pid)], {
461
+ encoding: "utf8",
462
+ maxBuffer: 4_096,
463
+ stdio: ["ignore", "pipe", "ignore"],
464
+ timeout: 1_000,
465
+ }).trim();
466
+ return started === "" ? null : `darwin:${started.replace(/\s+/gu, " ")}`;
467
+ } catch {
468
+ return null;
469
+ }
470
+ }
471
+ return null;
472
+ }
473
+
474
+ function currentWriteLockOwner(): UrlMetadataWriteLockOwner {
475
+ const processIdentity = operatingSystemProcessIdentity(process.pid);
476
+ if (processIdentity === null) {
477
+ throw new Error("URL metadata locking could not verify the current process identity.");
478
+ }
479
+ return parseUrlMetadataWriteLockOwner({
480
+ schemaVersion: 1,
481
+ kind: "url-metadata-write-lock",
482
+ host: hostname(),
483
+ pid: process.pid,
484
+ processIdentity,
485
+ token: randomUUID(),
486
+ acquiredAt: new Date().toISOString(),
487
+ });
488
+ }
489
+
490
+ function parseCandidate(value: unknown, label: string): UrlMetadataCandidate {
491
+ const input = record(value, label);
492
+ exactKeys(input, ["title", "url", "snippet", "engines", "score"], label);
493
+ const score = input.score;
494
+ if (typeof score !== "number" || !Number.isFinite(score) || score < 0) {
495
+ throw new TypeError(`${label}.score must be a finite non-negative number.`);
496
+ }
497
+ const snippet = input.snippet === null
498
+ ? null
499
+ : boundedText(input.snippet, `${label}.snippet`, MAX_SNIPPET_BYTES);
500
+ return Object.freeze({
501
+ title: boundedText(input.title, `${label}.title`, MAX_TITLE_BYTES),
502
+ url: normalizedPublicUrl(input.url, `${label}.url`),
503
+ snippet,
504
+ engines: boundedTextList(input.engines, `${label}.engines`, MAX_ENGINE_COUNT, 64),
505
+ score,
506
+ });
507
+ }
508
+
509
+ function parseAttempt(value: unknown, label: string): UrlMetadataAttempt {
510
+ const input = record(value, label);
511
+ exactKeys(input, ["provider", "outcome", "message"], label);
512
+ if (input.provider !== "metadata-search-engine-rs" && input.provider !== "archive-today") {
513
+ throw new TypeError(`${label}.provider is unsupported.`);
514
+ }
515
+ if (
516
+ input.outcome !== "succeeded"
517
+ && input.outcome !== "partial"
518
+ && input.outcome !== "not-found"
519
+ && input.outcome !== "failed"
520
+ && input.outcome !== "skipped"
521
+ ) throw new TypeError(`${label}.outcome is unsupported.`);
522
+ return Object.freeze({
523
+ provider: input.provider,
524
+ outcome: input.outcome,
525
+ message: boundedText(input.message, `${label}.message`, MAX_MESSAGE_BYTES),
526
+ });
527
+ }
528
+
529
+ function parseArchive(
530
+ value: unknown,
531
+ label: string,
532
+ subjectUrl: string,
533
+ generatedAt: string,
534
+ ): UrlMetadataArchive {
535
+ const input = record(value, label);
536
+ exactKeys(input, ["url", "capturedAt", "discovery"], label);
537
+ if (
538
+ input.discovery !== "newest"
539
+ && input.discovery !== "timemap"
540
+ && input.discovery !== "metadata-search"
541
+ ) throw new TypeError(`${label}.discovery is unsupported.`);
542
+ const url = normalizedPublicUrl(input.url, `${label}.url`);
543
+ const capturedAt = isoTimestamp(input.capturedAt, `${label}.capturedAt`);
544
+ let memento;
545
+ try {
546
+ memento = parseArchiveTodayMementoUrl(url, {
547
+ originalUrl: subjectUrl,
548
+ now: new Date(generatedAt),
549
+ });
550
+ } catch (error) {
551
+ throw new TypeError(`${label} must be an exact timestamped Archive.today-family snapshot.`, { cause: error });
552
+ }
553
+ if (memento.capturedAt !== capturedAt) {
554
+ throw new TypeError(`${label}.capturedAt must match its snapshot timestamp.`);
555
+ }
556
+ return Object.freeze({
557
+ url: memento.url,
558
+ capturedAt,
559
+ discovery: input.discovery,
560
+ });
561
+ }
562
+
563
+ function parseSelectedField(
564
+ value: unknown,
565
+ label: string,
566
+ maximumBytes: number,
567
+ ): UrlMetadataSelectedField | null {
568
+ if (value === null) return null;
569
+ const input = record(value, label);
570
+ exactKeys(input, ["value", "sourceUrl", "provider"], label);
571
+ if (input.provider !== METADATA_SEARCH_ENGINE_ID) throw new TypeError(`${label}.provider is unsupported.`);
572
+ return Object.freeze({
573
+ value: boundedText(input.value, `${label}.value`, maximumBytes),
574
+ sourceUrl: normalizedPublicUrl(input.sourceUrl, `${label}.sourceUrl`),
575
+ provider: METADATA_SEARCH_ENGINE_ID,
576
+ });
577
+ }
578
+
579
+ function metadataStatus(options: {
580
+ readonly hasCandidate: boolean;
581
+ readonly hasArchive: boolean;
582
+ readonly queriedEngines: number;
583
+ readonly failedEngines: number;
584
+ readonly partial: boolean;
585
+ }): UrlMetadataDocument["status"] {
586
+ if (options.hasCandidate) return options.partial ? "partial" : "matched";
587
+ if (options.hasArchive) return "partial";
588
+ if (options.queriedEngines === 0 || options.failedEngines === options.queriedEngines) return "unavailable";
589
+ return options.partial ? "partial" : "not-found";
590
+ }
591
+
592
+ export function parseUrlMetadataDocument(value: unknown): UrlMetadataDocument {
593
+ const input = record(value, "URL metadata document");
594
+ exactKeys(input, [
595
+ "schemaVersion",
596
+ "kind",
597
+ "subjectUrl",
598
+ "generatedAt",
599
+ "status",
600
+ "provider",
601
+ "attempts",
602
+ "candidates",
603
+ "selected",
604
+ "archives",
605
+ "warnings",
606
+ ], "URL metadata document");
607
+ if (input.schemaVersion !== URL_METADATA_SCHEMA_VERSION || input.kind !== "url-metadata") {
608
+ throw new TypeError("URL metadata document has an unsupported identity.");
609
+ }
610
+ if (
611
+ input.status !== "matched"
612
+ && input.status !== "not-found"
613
+ && input.status !== "partial"
614
+ && input.status !== "unavailable"
615
+ ) throw new TypeError("URL metadata document has an unsupported status.");
616
+
617
+ const provider = record(input.provider, "URL metadata document.provider");
618
+ exactKeys(provider, ["id", "version", "revision", "enginesQueried", "enginesFailed"], "URL metadata document.provider");
619
+ if (
620
+ provider.id !== METADATA_SEARCH_ENGINE_ID
621
+ || provider.version !== METADATA_SEARCH_ENGINE_VERSION
622
+ || provider.revision !== METADATA_SEARCH_ENGINE_REVISION
623
+ ) throw new TypeError("URL metadata document has an unsupported provider identity.");
624
+
625
+ if (!Array.isArray(input.attempts) || input.attempts.length > MAX_ATTEMPTS) {
626
+ throw new TypeError(`URL metadata document.attempts must have at most ${MAX_ATTEMPTS} entries.`);
627
+ }
628
+ if (!Array.isArray(input.candidates) || input.candidates.length > MAX_CANDIDATES) {
629
+ throw new TypeError(`URL metadata document.candidates must have at most ${MAX_CANDIDATES} entries.`);
630
+ }
631
+ if (!Array.isArray(input.archives) || input.archives.length > MAX_ARCHIVES) {
632
+ throw new TypeError(`URL metadata document.archives must have at most ${MAX_ARCHIVES} entries.`);
633
+ }
634
+ const selected = record(input.selected, "URL metadata document.selected");
635
+ exactKeys(selected, ["title", "description"], "URL metadata document.selected");
636
+
637
+ const subjectUrl = normalizedPublicUrl(input.subjectUrl, "URL metadata document.subjectUrl");
638
+ const generatedAt = isoTimestamp(input.generatedAt, "URL metadata document.generatedAt");
639
+ const enginesQueried = engineList(provider.enginesQueried, "URL metadata document.provider.enginesQueried");
640
+ const enginesFailed = engineList(provider.enginesFailed, "URL metadata document.provider.enginesFailed");
641
+ const queried = new Set(enginesQueried);
642
+ for (const engine of enginesFailed) {
643
+ if (!queried.has(engine)) {
644
+ throw new TypeError("URL metadata document.provider.enginesFailed contains an unqueried engine.");
645
+ }
646
+ }
647
+ const failed = new Set(enginesFailed);
648
+ const attempts = Object.freeze(input.attempts.map((item, index) =>
649
+ parseAttempt(item, `URL metadata document.attempts[${index}]`)));
650
+ const candidates = Object.freeze(input.candidates.map((item, index) =>
651
+ parseCandidate(item, `URL metadata document.candidates[${index}]`)));
652
+ const candidateUrls = new Set<string>();
653
+ for (const candidate of candidates) {
654
+ if (candidate.url !== subjectUrl) {
655
+ throw new TypeError("URL metadata document candidate must exactly match subjectUrl.");
656
+ }
657
+ if (candidateUrls.has(candidate.url)) {
658
+ throw new TypeError("URL metadata document candidates contain a duplicate source URL.");
659
+ }
660
+ candidateUrls.add(candidate.url);
661
+ for (const engine of candidate.engines) {
662
+ if (!queried.has(engine) || failed.has(engine)) {
663
+ throw new TypeError("URL metadata document candidate references an unavailable or unqueried engine.");
664
+ }
665
+ }
666
+ }
667
+ const title = parseSelectedField(selected.title, "URL metadata document.selected.title", MAX_TITLE_BYTES);
668
+ const description = parseSelectedField(selected.description, "URL metadata document.selected.description", MAX_SNIPPET_BYTES);
669
+ const best = candidates[0] ?? null;
670
+ if (
671
+ (best === null && (title !== null || description !== null))
672
+ || (best !== null && (title?.value !== best.title || title.sourceUrl !== subjectUrl))
673
+ || (best?.snippet === null && description !== null)
674
+ || (best?.snippet !== null && best !== null
675
+ && (description?.value !== best.snippet || description.sourceUrl !== subjectUrl))
676
+ ) {
677
+ throw new TypeError("URL metadata document selected fields do not match the best exact candidate.");
678
+ }
679
+ const archives = Object.freeze(input.archives.map((item, index) =>
680
+ parseArchive(item, `URL metadata document.archives[${index}]`, subjectUrl, generatedAt)));
681
+ const archiveCaptures = new Set<string>();
682
+ for (const archive of archives) {
683
+ if (archiveCaptures.has(archive.capturedAt)) {
684
+ throw new TypeError("URL metadata document archives contain a duplicate capture timestamp.");
685
+ }
686
+ archiveCaptures.add(archive.capturedAt);
687
+ }
688
+ const partial = enginesFailed.length > 0
689
+ || attempts.some(({ outcome }) => outcome === "failed" || outcome === "partial");
690
+ const expectedStatus = metadataStatus({
691
+ hasCandidate: best !== null,
692
+ hasArchive: archives.length > 0,
693
+ queriedEngines: enginesQueried.length,
694
+ failedEngines: enginesFailed.length,
695
+ partial,
696
+ });
697
+ if (input.status !== expectedStatus) {
698
+ throw new TypeError(`URL metadata document status must be ${expectedStatus}.`);
699
+ }
700
+
701
+ return Object.freeze({
702
+ schemaVersion: URL_METADATA_SCHEMA_VERSION,
703
+ kind: "url-metadata",
704
+ subjectUrl,
705
+ generatedAt,
706
+ status: expectedStatus,
707
+ provider: Object.freeze({
708
+ id: METADATA_SEARCH_ENGINE_ID,
709
+ version: METADATA_SEARCH_ENGINE_VERSION,
710
+ revision: METADATA_SEARCH_ENGINE_REVISION,
711
+ enginesQueried,
712
+ enginesFailed,
713
+ }),
714
+ attempts,
715
+ candidates,
716
+ selected: Object.freeze({
717
+ title,
718
+ description,
719
+ }),
720
+ archives,
721
+ warnings: boundedTextList(input.warnings, "URL metadata document.warnings", MAX_WARNINGS, MAX_MESSAGE_BYTES),
722
+ });
723
+ }
724
+
725
+ function stableCandidateOrder(left: UrlMetadataCandidate, right: UrlMetadataCandidate): number {
726
+ return right.score - left.score
727
+ || right.engines.length - left.engines.length
728
+ || compareText(left.title, right.title)
729
+ || compareText(left.url, right.url);
730
+ }
731
+
732
+ export function createUrlMetadataDocument(input: CreateUrlMetadataDocumentInput): UrlMetadataDocument {
733
+ const candidates = [...input.candidates].sort(stableCandidateOrder).slice(0, MAX_CANDIDATES);
734
+ const best = candidates[0] ?? null;
735
+ const archives = [...(input.archives ?? [])]
736
+ .sort((left, right) => compareText(right.capturedAt, left.capturedAt) || compareText(left.url, right.url))
737
+ .slice(0, MAX_ARCHIVES);
738
+ const partial = input.enginesFailed.length > 0
739
+ || input.attempts.some(({ outcome }) => outcome === "failed" || outcome === "partial");
740
+ const status = metadataStatus({
741
+ hasCandidate: best !== null,
742
+ hasArchive: archives.length > 0,
743
+ queriedEngines: input.enginesQueried.length,
744
+ failedEngines: input.enginesFailed.length,
745
+ partial,
746
+ });
747
+ return parseUrlMetadataDocument({
748
+ schemaVersion: URL_METADATA_SCHEMA_VERSION,
749
+ kind: "url-metadata",
750
+ subjectUrl: input.subjectUrl,
751
+ generatedAt: input.generatedAt,
752
+ status,
753
+ provider: {
754
+ id: METADATA_SEARCH_ENGINE_ID,
755
+ version: METADATA_SEARCH_ENGINE_VERSION,
756
+ revision: METADATA_SEARCH_ENGINE_REVISION,
757
+ enginesQueried: [...input.enginesQueried],
758
+ enginesFailed: [...input.enginesFailed],
759
+ },
760
+ attempts: [...input.attempts],
761
+ candidates,
762
+ selected: {
763
+ title: best === null ? null : {
764
+ value: best.title,
765
+ sourceUrl: best.url,
766
+ provider: METADATA_SEARCH_ENGINE_ID,
767
+ },
768
+ description: best?.snippet === null || best?.snippet === undefined ? null : {
769
+ value: best.snippet,
770
+ sourceUrl: best.url,
771
+ provider: METADATA_SEARCH_ENGINE_ID,
772
+ },
773
+ },
774
+ archives,
775
+ warnings: [...(input.warnings ?? [])],
776
+ });
777
+ }
778
+
779
+ export function renderUrlMetadataDocument(document: UrlMetadataDocument): string {
780
+ return `${JSON.stringify(parseUrlMetadataDocument(document), null, 2)}\n`;
781
+ }
782
+
783
+ function frontmatterObject(markdown: string, path: string): Readonly<Record<string, unknown>> {
784
+ const match = /^---(?:\r?\n)([\s\S]*?)(?:\r?\n)---(?:\r?\n|$)/u.exec(markdown);
785
+ if (match === null) return {};
786
+ const parsed = parseDocument(match[1] ?? "", { uniqueKeys: true });
787
+ if (parsed.errors.length > 0) throw new Error(`Invalid YAML frontmatter in ${path}: ${parsed.errors[0]?.message ?? "unknown error"}`);
788
+ const value: unknown = parsed.toJS({ maxAliasCount: 32 });
789
+ if (value === null) return {};
790
+ return record(value, `Frontmatter in ${path}`);
791
+ }
792
+
793
+ function absoluteSource(value: unknown, label: string, allowLocalPdf: boolean): string | null {
794
+ if (value === undefined) return null;
795
+ if (allowLocalPdf && value === "source.pdf") return null;
796
+ if (typeof value !== "string") {
797
+ throw new TypeError(`${label} must be an absolute HTTP(S) URL${allowLocalPdf ? " or source.pdf" : ""}.`);
798
+ }
799
+ let parsed: URL;
800
+ try {
801
+ parsed = new URL(value);
802
+ } catch (error) {
803
+ throw new TypeError(`${label} must be an absolute HTTP(S) URL${allowLocalPdf ? " or source.pdf" : ""}.`, { cause: error });
804
+ }
805
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
806
+ throw new TypeError(`${label} must use HTTP or HTTPS.`);
807
+ }
808
+ return normalizedPublicUrl(value, label);
809
+ }
810
+
811
+ function confined(root: string, path: string, label: string): void {
812
+ const pathFromRoot = relative(root, path);
813
+ if (pathFromRoot === "" || pathFromRoot === ".." || pathFromRoot.startsWith(`..${sep}`) || pathFromRoot.includes(`\0`)) {
814
+ throw new Error(`${label} escapes the articles root.`);
815
+ }
816
+ }
817
+
818
+ function identity(metadata: BigIntStats): FileIdentity {
819
+ return Object.freeze({
820
+ device: metadata.dev,
821
+ inode: metadata.ino,
822
+ size: metadata.size,
823
+ mode: metadata.mode,
824
+ links: metadata.nlink,
825
+ modifiedNanoseconds: metadata.mtimeNs,
826
+ changedNanoseconds: metadata.ctimeNs,
827
+ });
828
+ }
829
+
830
+ function sameIdentity(left: FileIdentity, right: FileIdentity): boolean {
831
+ return left.device === right.device
832
+ && left.inode === right.inode
833
+ && left.size === right.size
834
+ && left.mode === right.mode
835
+ && left.links === right.links
836
+ && left.modifiedNanoseconds === right.modifiedNanoseconds
837
+ && left.changedNanoseconds === right.changedNanoseconds;
838
+ }
839
+
840
+ function sameInode(left: FileIdentity, right: FileIdentity): boolean {
841
+ return left.device === right.device && left.inode === right.inode;
842
+ }
843
+
844
+ function validateOwnedLockFile(metadata: BigIntStats, path: string): FileIdentity {
845
+ const effectiveUserId = process.geteuid?.();
846
+ if (
847
+ !metadata.isFile()
848
+ || metadata.isSymbolicLink()
849
+ || metadata.nlink !== 1n
850
+ || (effectiveUserId !== undefined && metadata.uid !== BigInt(effectiveUserId))
851
+ ) {
852
+ throw new Error(`URL metadata write lock must be an owned regular single-link file: ${path}`);
853
+ }
854
+ return identity(metadata);
855
+ }
856
+
857
+ function readDescriptorText(descriptor: number, maximumBytes: number, label: string): string {
858
+ const metadata = fstatSync(descriptor, { bigint: true });
859
+ if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1n) {
860
+ throw new Error(`${label} descriptor must be a regular single-link file.`);
861
+ }
862
+ if (metadata.size > BigInt(maximumBytes)) throw new Error(`${label} exceeds ${maximumBytes} bytes.`);
863
+ const bytes = Buffer.alloc(Number(metadata.size));
864
+ let offset = 0;
865
+ while (offset < bytes.byteLength) {
866
+ const count = readSync(descriptor, bytes, offset, bytes.byteLength - offset, offset);
867
+ if (count === 0) throw new Error(`${label} ended before its validated size.`);
868
+ offset += count;
869
+ }
870
+ try {
871
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
872
+ } catch (error) {
873
+ throw new Error(`${label} must be valid UTF-8.`, { cause: error });
874
+ }
875
+ }
876
+
877
+ function lockOwnerFromDescriptor(descriptor: number): UrlMetadataWriteLockOwner {
878
+ let value: unknown;
879
+ try {
880
+ value = JSON.parse(readDescriptorText(descriptor, MAX_LOCK_BYTES, "URL metadata write lock"));
881
+ } catch (error) {
882
+ throw new Error("URL metadata write lock owner metadata is malformed.", { cause: error });
883
+ }
884
+ return parseUrlMetadataWriteLockOwner(value);
885
+ }
886
+
887
+ function writeLockOwner(descriptor: number, owner: UrlMetadataWriteLockOwner): FileIdentity {
888
+ const bytes = Buffer.from(`${JSON.stringify(owner)}\n`, "utf8");
889
+ if (bytes.byteLength > MAX_LOCK_BYTES) throw new Error("URL metadata write lock owner metadata is oversized.");
890
+ ftruncateSync(descriptor, 0);
891
+ let offset = 0;
892
+ while (offset < bytes.byteLength) {
893
+ const written = writeSync(descriptor, bytes, offset, bytes.byteLength - offset, offset);
894
+ if (written === 0) throw new Error("URL metadata write lock owner metadata could not be fully written.");
895
+ offset += written;
896
+ }
897
+ ftruncateSync(descriptor, bytes.byteLength);
898
+ fchmodSync(descriptor, 0o600);
899
+ fsyncSync(descriptor);
900
+ const parsed = lockOwnerFromDescriptor(descriptor);
901
+ if (
902
+ parsed.host !== owner.host
903
+ || parsed.pid !== owner.pid
904
+ || parsed.processIdentity !== owner.processIdentity
905
+ || parsed.token !== owner.token
906
+ || parsed.acquiredAt !== owner.acquiredAt
907
+ ) throw new Error("URL metadata write lock owner metadata failed verification.");
908
+ return validateOwnedLockFile(fstatSync(descriptor, { bigint: true }), "opened lock descriptor");
909
+ }
910
+
911
+ function assertMalformedLockIsOldEnoughToRecover(
912
+ descriptor: number,
913
+ identity: FileIdentity,
914
+ path: string,
915
+ ): void {
916
+ try {
917
+ lockOwnerFromDescriptor(descriptor);
918
+ return;
919
+ } catch (error) {
920
+ const ageNanoseconds = BigInt(Date.now()) * 1_000_000n - identity.modifiedNanoseconds;
921
+ const recoveryAgeNanoseconds = BigInt(MALFORMED_LOCK_RECOVERY_AGE_MILLISECONDS) * 1_000_000n;
922
+ if (ageNanoseconds < recoveryAgeNanoseconds) {
923
+ throw new Error(
924
+ `Refusing to reclaim a fresh malformed URL metadata write lock: ${path}`,
925
+ { cause: error },
926
+ );
927
+ }
928
+ }
929
+ }
930
+
931
+ function activeLockOwnerDescription(path: string, openedIdentity: FileIdentity): string {
932
+ try {
933
+ const publishedIdentity = validateOwnedLockFile(lstatSync(path, { bigint: true }), path);
934
+ if (!sameInode(openedIdentity, publishedIdentity)) return "an active writer after concurrent lock replacement";
935
+ const owner = parseUrlMetadataWriteLockOwner(JSON.parse(
936
+ readBoundedSingleLinkFile(path, MAX_LOCK_BYTES, "URL metadata write lock").text,
937
+ ));
938
+ if (
939
+ owner.host === hostname()
940
+ && operatingSystemProcessIdentity(owner.pid) === owner.processIdentity
941
+ ) return `active writer pid ${owner.pid} (${owner.token})`;
942
+ } catch {
943
+ // The kernel lock remains authoritative while owner metadata is initializing or malformed.
944
+ }
945
+ return "an active writer with unverified owner metadata";
946
+ }
947
+
948
+ function acquireUrlMetadataWriteLock(path: string): HeldUrlMetadataWriteLock {
949
+ const owner = currentWriteLockOwner();
950
+ let descriptor: number | null = null;
951
+ let existedBeforeOpen = true;
952
+ try {
953
+ try {
954
+ validateOwnedLockFile(lstatSync(path, { bigint: true }), path);
955
+ } catch (error) {
956
+ const cause = error as NodeJS.ErrnoException;
957
+ if (cause.code !== "ENOENT") throw error;
958
+ existedBeforeOpen = false;
959
+ }
960
+ descriptor = openSync(
961
+ path,
962
+ constants.O_RDWR | constants.O_CREAT | constants.O_NOFOLLOW,
963
+ 0o600,
964
+ );
965
+ setDescriptorCloseOnExec(descriptor);
966
+ const openedIdentity = validateOwnedLockFile(fstatSync(descriptor, { bigint: true }), path);
967
+ const publishedIdentity = validateOwnedLockFile(lstatSync(path, { bigint: true }), path);
968
+ if (!sameInode(openedIdentity, publishedIdentity)) {
969
+ throw new Error(`URL metadata write lock identity changed while opening: ${path}`);
970
+ }
971
+ if (!tryExclusiveFileLock(descriptor)) {
972
+ throw new Error(`URL metadata sidecar is locked by ${activeLockOwnerDescription(path, openedIdentity)}.`);
973
+ }
974
+ if (existedBeforeOpen) assertMalformedLockIsOldEnoughToRecover(descriptor, openedIdentity, path);
975
+ const identity = writeLockOwner(descriptor, owner);
976
+ const ownedPathIdentity = validateOwnedLockFile(lstatSync(path, { bigint: true }), path);
977
+ if (!sameIdentity(identity, ownedPathIdentity)) {
978
+ throw new Error(`URL metadata write lock changed while publishing its owner: ${path}`);
979
+ }
980
+ const held = Object.freeze({ descriptor, identity, owner, path });
981
+ descriptor = null;
982
+ return held;
983
+ } finally {
984
+ if (descriptor !== null) closeSync(descriptor);
985
+ }
986
+ }
987
+
988
+ function releaseUrlMetadataWriteLock(held: HeldUrlMetadataWriteLock): void {
989
+ let releaseError: Error | undefined;
990
+ try {
991
+ assertHeldUrlMetadataWriteLockCurrent(held);
992
+ unlinkSync(held.path);
993
+ } catch (error) {
994
+ releaseError = errorFromUnknown(error, "URL metadata write lock release failed.");
995
+ }
996
+ try {
997
+ closeSync(held.descriptor);
998
+ } catch (error) {
999
+ releaseError ??= errorFromUnknown(error, "URL metadata write lock descriptor close failed.");
1000
+ }
1001
+ if (releaseError !== undefined) throw releaseError;
1002
+ }
1003
+
1004
+ function assertHeldUrlMetadataWriteLockCurrent(held: HeldUrlMetadataWriteLock): void {
1005
+ const openedIdentity = validateOwnedLockFile(
1006
+ fstatSync(held.descriptor, { bigint: true }),
1007
+ held.path,
1008
+ );
1009
+ const publishedIdentity = validateOwnedLockFile(lstatSync(held.path, { bigint: true }), held.path);
1010
+ const owner = lockOwnerFromDescriptor(held.descriptor);
1011
+ if (
1012
+ !sameIdentity(held.identity, openedIdentity)
1013
+ || !sameIdentity(held.identity, publishedIdentity)
1014
+ || owner.token !== held.owner.token
1015
+ || owner.pid !== held.owner.pid
1016
+ || owner.processIdentity !== held.owner.processIdentity
1017
+ ) throw new Error(`URL metadata write lock was replaced while held: ${held.path}`);
1018
+ }
1019
+
1020
+ function removeOwnedTemporaryFile(path: string, expected: FileIdentity): void {
1021
+ let published: FileIdentity;
1022
+ try {
1023
+ published = validateRegularSingleLink(
1024
+ lstatSync(path, { bigint: true }),
1025
+ path,
1026
+ MAX_SIDECAR_BYTES,
1027
+ "URL metadata temporary sidecar",
1028
+ );
1029
+ } catch (error) {
1030
+ const cause = error as NodeJS.ErrnoException;
1031
+ if (cause.code === "ENOENT") return;
1032
+ throw error;
1033
+ }
1034
+ if (!sameIdentity(expected, published)) {
1035
+ throw new Error(`Refusing to remove a replaced URL metadata temporary sidecar: ${path}`);
1036
+ }
1037
+ unlinkSync(path);
1038
+ }
1039
+
1040
+ function directoryIdentity(metadata: BigIntStats): DirectoryIdentity {
1041
+ return Object.freeze({
1042
+ device: metadata.dev,
1043
+ inode: metadata.ino,
1044
+ mode: metadata.mode,
1045
+ });
1046
+ }
1047
+
1048
+ function sameDirectoryIdentity(left: DirectoryIdentity, right: DirectoryIdentity): boolean {
1049
+ return left.device === right.device
1050
+ && left.inode === right.inode
1051
+ && left.mode === right.mode;
1052
+ }
1053
+
1054
+ function validateArticleDirectory(path: string): DirectoryIdentity {
1055
+ const metadata = lstatSync(path, { bigint: true });
1056
+ if (!metadata.isDirectory() || metadata.isSymbolicLink() || realpathSync(path) !== path) {
1057
+ throw new Error(`Article directory must be a real directory: ${path}`);
1058
+ }
1059
+ return directoryIdentity(metadata);
1060
+ }
1061
+
1062
+ function validateOpenedArticleDirectory(descriptor: number, path: string): DirectoryIdentity {
1063
+ const metadata = fstatSync(descriptor, { bigint: true });
1064
+ if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
1065
+ throw new Error(`Article directory descriptor is no longer a directory: ${path}`);
1066
+ }
1067
+ return directoryIdentity(metadata);
1068
+ }
1069
+
1070
+ function validateRegularSingleLink(
1071
+ metadata: BigIntStats,
1072
+ path: string,
1073
+ maximumBytes: number,
1074
+ label: string,
1075
+ ): FileIdentity {
1076
+ if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1n) {
1077
+ throw new Error(`${label} must be a regular single-link file: ${path}`);
1078
+ }
1079
+ if (metadata.size > BigInt(maximumBytes)) throw new Error(`${label} exceeds ${maximumBytes} bytes: ${path}`);
1080
+ return identity(metadata);
1081
+ }
1082
+
1083
+ function readBoundedSingleLinkFile(
1084
+ path: string,
1085
+ maximumBytes: number,
1086
+ label: string,
1087
+ ): { readonly text: string; readonly identity: FileIdentity } {
1088
+ const pathIdentity = validateRegularSingleLink(lstatSync(path, { bigint: true }), path, maximumBytes, label);
1089
+ const descriptor = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
1090
+ try {
1091
+ const openedIdentity = validateRegularSingleLink(fstatSync(descriptor, { bigint: true }), path, maximumBytes, label);
1092
+ if (!sameIdentity(pathIdentity, openedIdentity)) throw new Error(`${label} identity changed before read: ${path}`);
1093
+ const bytes = Buffer.alloc(Number(openedIdentity.size));
1094
+ let offset = 0;
1095
+ while (offset < bytes.byteLength) {
1096
+ const count = readSync(descriptor, bytes, offset, bytes.byteLength - offset, offset);
1097
+ if (count === 0) throw new Error(`${label} ended before its validated size: ${path}`);
1098
+ offset += count;
1099
+ }
1100
+ const finishedIdentity = validateRegularSingleLink(fstatSync(descriptor, { bigint: true }), path, maximumBytes, label);
1101
+ const finalPathIdentity = validateRegularSingleLink(lstatSync(path, { bigint: true }), path, maximumBytes, label);
1102
+ if (!sameIdentity(openedIdentity, finishedIdentity) || !sameIdentity(openedIdentity, finalPathIdentity)) {
1103
+ throw new Error(`${label} identity changed during read: ${path}`);
1104
+ }
1105
+ let text: string;
1106
+ try {
1107
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
1108
+ } catch (error) {
1109
+ throw new Error(`${label} must be valid UTF-8: ${path}`, { cause: error });
1110
+ }
1111
+ return Object.freeze({ text, identity: openedIdentity });
1112
+ } finally {
1113
+ closeSync(descriptor);
1114
+ }
1115
+ }
1116
+
1117
+ export function discoverSavedUrlRecords(vaultRoot: string): readonly SavedUrlRecord[] {
1118
+ const resolvedVault = realpathSync(resolve(vaultRoot));
1119
+ const vaultMetadata = lstatSync(resolvedVault);
1120
+ if (!vaultMetadata.isDirectory() || vaultMetadata.isSymbolicLink()) throw new Error("The vault root must be a real directory.");
1121
+ const articlesRoot = join(resolvedVault, "articles");
1122
+ const articlesMetadata = lstatSync(articlesRoot);
1123
+ if (!articlesMetadata.isDirectory() || articlesMetadata.isSymbolicLink()) throw new Error("The articles root must be a real directory.");
1124
+ if (realpathSync(articlesRoot) !== articlesRoot) throw new Error("The articles root must resolve to itself.");
1125
+
1126
+ const entries = readdirSync(articlesRoot, { withFileTypes: true });
1127
+ const directories = entries.filter((entry) => entry.isDirectory());
1128
+ if (directories.length > MAX_ARTICLE_DIRECTORIES) {
1129
+ throw new Error(`The articles root exceeds ${MAX_ARTICLE_DIRECTORIES} directories.`);
1130
+ }
1131
+ for (const entry of entries) {
1132
+ if (entry.isSymbolicLink()) throw new Error(`The articles root contains a symbolic link: ${entry.name}`);
1133
+ }
1134
+
1135
+ const records: SavedUrlRecord[] = [];
1136
+ for (const entry of directories.sort((left, right) => compareText(left.name, right.name))) {
1137
+ const directory = join(articlesRoot, entry.name);
1138
+ confined(articlesRoot, directory, "Article directory");
1139
+ const capturedDirectoryIdentity = validateArticleDirectory(directory);
1140
+ const children = readdirSync(directory, { withFileTypes: true });
1141
+ if (children.length > MAX_DIRECTORY_ENTRIES) {
1142
+ throw new Error(`Article directory exceeds ${MAX_DIRECTORY_ENTRIES} entries: ${directory}`);
1143
+ }
1144
+ if (children.some((child) => child.isSymbolicLink())) {
1145
+ throw new Error(`Article directory contains a symbolic link: ${directory}`);
1146
+ }
1147
+ const urlMarkdown: Array<{
1148
+ readonly path: string;
1149
+ readonly identity: FileIdentity;
1150
+ readonly url: string;
1151
+ }> = [];
1152
+ for (const child of children) {
1153
+ if (!child.isFile() || !child.name.endsWith(".md") || child.name === "AGENTS.md") continue;
1154
+ const markdownPath = join(directory, child.name);
1155
+ const markdown = readBoundedSingleLinkFile(markdownPath, MAX_MARKDOWN_BYTES, "Article Markdown");
1156
+ const metadata = frontmatterObject(markdown.text, markdownPath);
1157
+ const source = absoluteSource(metadata.source, `${markdownPath} source`, true);
1158
+ const sourceUrl = absoluteSource(metadata.source_url, `${markdownPath} source_url`, false);
1159
+ if (source !== null && sourceUrl !== null && source !== sourceUrl) {
1160
+ throw new Error(`Article Markdown has conflicting source and source_url values: ${markdownPath}`);
1161
+ }
1162
+ const url = source ?? sourceUrl;
1163
+ if (url !== null) urlMarkdown.push({ path: markdownPath, identity: markdown.identity, url });
1164
+ }
1165
+ if (urlMarkdown.length > 1) throw new Error(`Article directory contains multiple URL-bearing Markdown files: ${directory}`);
1166
+ const selected = urlMarkdown[0];
1167
+ if (selected === undefined) continue;
1168
+ if (!sameDirectoryIdentity(capturedDirectoryIdentity, validateArticleDirectory(directory))) {
1169
+ throw new Error(`Article directory identity changed during inventory: ${directory}`);
1170
+ }
1171
+ const saved: SavedUrlRecord = {
1172
+ articleId: entry.name,
1173
+ directory,
1174
+ markdownPath: selected.path,
1175
+ sidecarPath: join(directory, URL_METADATA_FILENAME),
1176
+ subjectUrl: selected.url,
1177
+ };
1178
+ savedUrlRecordIdentities.set(saved, Object.freeze({
1179
+ directory: capturedDirectoryIdentity,
1180
+ markdown: selected.identity,
1181
+ }));
1182
+ records.push(Object.freeze(saved));
1183
+ }
1184
+ return Object.freeze(records);
1185
+ }
1186
+
1187
+ function fileIdentity(path: string): FileIdentity {
1188
+ return identity(lstatSync(path, { bigint: true }));
1189
+ }
1190
+
1191
+ function savedUrlRecordIdentity(saved: SavedUrlRecord): SavedUrlRecordIdentity {
1192
+ const owned = savedUrlRecordIdentities.get(saved);
1193
+ if (owned === undefined) throw new Error("Saved URL record was not produced by the validated inventory.");
1194
+ return owned;
1195
+ }
1196
+
1197
+ function assertSavedUrlRecordCurrent(saved: SavedUrlRecord): void {
1198
+ const expected = savedUrlRecordIdentity(saved);
1199
+ if (dirname(saved.markdownPath) !== saved.directory || !basename(saved.markdownPath).endsWith(".md")) {
1200
+ throw new Error("Saved URL Markdown path is not owned by its article directory.");
1201
+ }
1202
+ const currentDirectoryIdentity = validateArticleDirectory(saved.directory);
1203
+ if (!sameDirectoryIdentity(expected.directory, currentDirectoryIdentity)) {
1204
+ throw new Error(`Article directory identity changed before metadata write: ${saved.directory}`);
1205
+ }
1206
+ const markdown = readBoundedSingleLinkFile(saved.markdownPath, MAX_MARKDOWN_BYTES, "Article Markdown");
1207
+ if (!sameIdentity(expected.markdown, markdown.identity)) {
1208
+ throw new Error(`Article Markdown identity changed before metadata write: ${saved.markdownPath}`);
1209
+ }
1210
+ const metadata = frontmatterObject(markdown.text, saved.markdownPath);
1211
+ const source = absoluteSource(metadata.source, `${saved.markdownPath} source`, true);
1212
+ const sourceUrl = absoluteSource(metadata.source_url, `${saved.markdownPath} source_url`, false);
1213
+ if (source !== null && sourceUrl !== null && source !== sourceUrl) {
1214
+ throw new Error(`Article Markdown has conflicting source and source_url values: ${saved.markdownPath}`);
1215
+ }
1216
+ if ((source ?? sourceUrl) !== saved.subjectUrl) {
1217
+ throw new Error(`Article Markdown source changed before metadata write: ${saved.markdownPath}`);
1218
+ }
1219
+ }
1220
+
1221
+ function readOwnedSidecar(path: string, subjectUrl: string): { readonly document: UrlMetadataDocument; readonly identity: FileIdentity } {
1222
+ const read = readBoundedSingleLinkFile(path, MAX_SIDECAR_BYTES, "URL metadata sidecar");
1223
+ let value: unknown;
1224
+ try {
1225
+ value = JSON.parse(read.text);
1226
+ } catch (error) {
1227
+ throw new Error(`Refusing to replace malformed URL metadata sidecar: ${path}`, { cause: error });
1228
+ }
1229
+ const document = parseUrlMetadataDocument(value);
1230
+ if (document.subjectUrl !== subjectUrl) {
1231
+ throw new Error(`Refusing to replace URL metadata sidecar for a different subject: ${path}`);
1232
+ }
1233
+ return { document, identity: read.identity };
1234
+ }
1235
+
1236
+ /** Read one tool-owned sidecar without following links or accepting a subject mismatch. */
1237
+ export function readUrlMetadataDocument(saved: SavedUrlRecord): UrlMetadataDocument {
1238
+ if (dirname(saved.sidecarPath) !== saved.directory || basename(saved.sidecarPath) !== URL_METADATA_FILENAME) {
1239
+ throw new Error("URL metadata sidecar path is not owned by its article directory.");
1240
+ }
1241
+ assertSavedUrlRecordCurrent(saved);
1242
+ const document = readOwnedSidecar(saved.sidecarPath, saved.subjectUrl).document;
1243
+ assertSavedUrlRecordCurrent(saved);
1244
+ return document;
1245
+ }
1246
+
1247
+ export function writeUrlMetadataDocument(
1248
+ saved: SavedUrlRecord,
1249
+ document: UrlMetadataDocument,
1250
+ ): { readonly changed: boolean; readonly path: string } {
1251
+ const parsed = parseUrlMetadataDocument(document);
1252
+ if (parsed.subjectUrl !== saved.subjectUrl) throw new Error("URL metadata document does not match its saved URL record.");
1253
+ if (dirname(saved.sidecarPath) !== saved.directory || basename(saved.sidecarPath) !== URL_METADATA_FILENAME) {
1254
+ throw new Error("URL metadata sidecar path is not owned by its article directory.");
1255
+ }
1256
+ assertSavedUrlRecordCurrent(saved);
1257
+ const rendered = renderUrlMetadataDocument(parsed);
1258
+ if (utf8Length(rendered) > MAX_SIDECAR_BYTES) throw new Error("URL metadata sidecar exceeds its byte limit.");
1259
+
1260
+ const lockPath = join(saved.directory, `.${URL_METADATA_FILENAME}.lock`);
1261
+ let heldLock: HeldUrlMetadataWriteLock | null = null;
1262
+ let temporaryPath: string | null = null;
1263
+ let temporaryDescriptor: number | null = null;
1264
+ let temporaryIdentity: FileIdentity | null = null;
1265
+ let directoryDescriptor: number | null = null;
1266
+ let operationError: unknown;
1267
+ try {
1268
+ heldLock = acquireUrlMetadataWriteLock(lockPath);
1269
+ temporaryPath = join(
1270
+ saved.directory,
1271
+ `.${URL_METADATA_FILENAME}.${process.pid}.${heldLock.owner.token}.tmp`,
1272
+ );
1273
+ assertSavedUrlRecordCurrent(saved);
1274
+ let existing: ReturnType<typeof readOwnedSidecar> | null = null;
1275
+ try {
1276
+ existing = readOwnedSidecar(saved.sidecarPath, saved.subjectUrl);
1277
+ } catch (error) {
1278
+ const cause = error as NodeJS.ErrnoException;
1279
+ if (cause.code !== "ENOENT") throw error;
1280
+ }
1281
+ if (existing !== null && renderUrlMetadataDocument(existing.document) === rendered) {
1282
+ assertSavedUrlRecordCurrent(saved);
1283
+ return { changed: false, path: saved.sidecarPath };
1284
+ }
1285
+
1286
+ temporaryDescriptor = openSync(
1287
+ temporaryPath,
1288
+ constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW,
1289
+ 0o600,
1290
+ );
1291
+ setDescriptorCloseOnExec(temporaryDescriptor);
1292
+ temporaryIdentity = validateRegularSingleLink(
1293
+ fstatSync(temporaryDescriptor, { bigint: true }),
1294
+ temporaryPath,
1295
+ MAX_SIDECAR_BYTES,
1296
+ "URL metadata temporary sidecar",
1297
+ );
1298
+ const publishedTemporaryIdentity = validateRegularSingleLink(
1299
+ lstatSync(temporaryPath, { bigint: true }),
1300
+ temporaryPath,
1301
+ MAX_SIDECAR_BYTES,
1302
+ "URL metadata temporary sidecar",
1303
+ );
1304
+ if (!sameIdentity(temporaryIdentity, publishedTemporaryIdentity)) {
1305
+ throw new Error(`URL metadata temporary sidecar identity changed while opening: ${temporaryPath}`);
1306
+ }
1307
+ writeFileSync(temporaryDescriptor, rendered, "utf8");
1308
+ fchmodSync(temporaryDescriptor, 0o644);
1309
+ fsyncSync(temporaryDescriptor);
1310
+ temporaryIdentity = validateRegularSingleLink(
1311
+ fstatSync(temporaryDescriptor, { bigint: true }),
1312
+ temporaryPath,
1313
+ MAX_SIDECAR_BYTES,
1314
+ "URL metadata temporary sidecar",
1315
+ );
1316
+ if (!sameIdentity(temporaryIdentity, validateRegularSingleLink(
1317
+ lstatSync(temporaryPath, { bigint: true }),
1318
+ temporaryPath,
1319
+ MAX_SIDECAR_BYTES,
1320
+ "URL metadata temporary sidecar",
1321
+ ))) throw new Error(`URL metadata temporary sidecar changed while writing: ${temporaryPath}`);
1322
+ closeSync(temporaryDescriptor);
1323
+ temporaryDescriptor = null;
1324
+
1325
+ if (existing === null) {
1326
+ try {
1327
+ lstatSync(saved.sidecarPath);
1328
+ throw new Error(`URL metadata sidecar appeared concurrently: ${saved.sidecarPath}`);
1329
+ } catch (error) {
1330
+ const cause = error as NodeJS.ErrnoException;
1331
+ if (cause.code !== "ENOENT") throw error;
1332
+ }
1333
+ } else if (!sameIdentity(existing.identity, fileIdentity(saved.sidecarPath))) {
1334
+ throw new Error(`URL metadata sidecar changed concurrently: ${saved.sidecarPath}`);
1335
+ }
1336
+
1337
+ directoryDescriptor = openSync(saved.directory, constants.O_RDONLY | constants.O_NOFOLLOW);
1338
+ if (!sameDirectoryIdentity(
1339
+ savedUrlRecordIdentity(saved).directory,
1340
+ validateOpenedArticleDirectory(directoryDescriptor, saved.directory),
1341
+ )) {
1342
+ throw new Error(`Article directory identity changed before metadata install: ${saved.directory}`);
1343
+ }
1344
+ assertSavedUrlRecordCurrent(saved);
1345
+ assertHeldUrlMetadataWriteLockCurrent(heldLock);
1346
+ if (!sameIdentity(temporaryIdentity, validateRegularSingleLink(
1347
+ lstatSync(temporaryPath, { bigint: true }),
1348
+ temporaryPath,
1349
+ MAX_SIDECAR_BYTES,
1350
+ "URL metadata temporary sidecar",
1351
+ ))) throw new Error(`URL metadata temporary sidecar changed before install: ${temporaryPath}`);
1352
+ renameSync(temporaryPath, saved.sidecarPath);
1353
+ temporaryPath = null;
1354
+ temporaryIdentity = null;
1355
+ const installed = readOwnedSidecar(saved.sidecarPath, saved.subjectUrl);
1356
+ if (renderUrlMetadataDocument(installed.document) !== rendered) {
1357
+ throw new Error(`URL metadata sidecar failed post-install verification: ${saved.sidecarPath}`);
1358
+ }
1359
+ assertHeldUrlMetadataWriteLockCurrent(heldLock);
1360
+ fsyncSync(directoryDescriptor);
1361
+ return { changed: true, path: saved.sidecarPath };
1362
+ } catch (error) {
1363
+ operationError = error;
1364
+ throw error;
1365
+ } finally {
1366
+ let cleanupError: unknown;
1367
+ if (directoryDescriptor !== null) {
1368
+ try {
1369
+ closeSync(directoryDescriptor);
1370
+ } catch (error) {
1371
+ cleanupError ??= error;
1372
+ }
1373
+ }
1374
+ if (temporaryDescriptor !== null) {
1375
+ try {
1376
+ closeSync(temporaryDescriptor);
1377
+ } catch (error) {
1378
+ cleanupError ??= error;
1379
+ }
1380
+ }
1381
+ if (temporaryPath !== null && temporaryIdentity !== null) {
1382
+ try {
1383
+ removeOwnedTemporaryFile(temporaryPath, temporaryIdentity);
1384
+ } catch (error) {
1385
+ cleanupError ??= error;
1386
+ }
1387
+ }
1388
+ if (heldLock !== null) {
1389
+ try {
1390
+ releaseUrlMetadataWriteLock(heldLock);
1391
+ } catch (error) {
1392
+ cleanupError ??= error;
1393
+ }
1394
+ }
1395
+ throwCleanupErrorWithoutOperationError(operationError, cleanupError);
1396
+ }
1397
+ }