polygram 0.38.7 → 0.38.9

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.
@@ -0,0 +1,1033 @@
1
+ 'use strict';
2
+
3
+ const { createHash, randomBytes } = require('node:crypto');
4
+ const {
5
+ chmodSync,
6
+ closeSync,
7
+ constants: fsConstants,
8
+ lstatSync,
9
+ mkdirSync,
10
+ openSync,
11
+ readdirSync,
12
+ readFileSync,
13
+ realpathSync,
14
+ renameSync,
15
+ rmSync,
16
+ writeFileSync,
17
+ writeSync,
18
+ } = require('node:fs');
19
+ const path = require('node:path');
20
+
21
+ const { readCheckedInBundle } = require('./hook-bundle');
22
+
23
+ const HOOK_ARTIFACT_MANIFEST_SCHEMA = 'u23-hook-artifact-manifest/v1';
24
+ const HOOK_ARTIFACT_ROOT_SCHEMA = 'u23-hook-artifact-root/v1';
25
+ const HOOK_CLOSURE_SCHEMA = 'u23-hook-closure/v1';
26
+ const HOOK_ARTIFACT_MANIFEST_NAME = 'manifest.json';
27
+ const HOOK_ARTIFACT_ROOT_MARKER_NAME = 'artifact-root.json';
28
+ const HOOK_ARTIFACT_BUNDLE_NAME = 'hook-observer.js';
29
+ const HOOK_ARTIFACT_BUNDLE_ID = 'hook-bundle';
30
+ const HOOK_RUNTIME_ID = 'node';
31
+ const HOOK_RUNTIME_RELATIVE_PATH = 'bin/node';
32
+ const QUARANTINE_DIR_NAME = 'quarantine';
33
+ const LOCK_NAME = 'install.lock';
34
+ const STAGING_PREFIX = '.staging-';
35
+
36
+ // Closed mode sets. Every accepted mode is readable — and for a runtime or a
37
+ // directory, traversable — by an account that is neither the owner nor in the
38
+ // owning group, which is what a distinct service account needs; and none of
39
+ // them is writable by group or other. A `0700` runtime is refused for exactly
40
+ // that reason: the service could not execute it.
41
+ const DIRECTORY_MODES = new Set([0o755, 0o555]);
42
+ const MEMBER_MODES = new Set([0o644, 0o444]);
43
+ const RUNTIME_MODES = new Set([0o755, 0o555]);
44
+
45
+ const VERSION_RE = /^[0-9A-Za-z][0-9A-Za-z._-]{0,63}$/;
46
+ const RELATIVE_NAME_RE = /^[0-9A-Za-z][0-9A-Za-z._-]{0,63}$/;
47
+ const SHA256_RE = /^[a-f0-9]{64}$/;
48
+ const RESERVED_NAMES = new Set([
49
+ 'current',
50
+ 'latest',
51
+ 'stable',
52
+ QUARANTINE_DIR_NAME,
53
+ ]);
54
+ // The tree's own bookkeeping, which shares the character set a version name
55
+ // uses and must never be read as one.
56
+ const TREE_FILE_NAMES = new Set([HOOK_ARTIFACT_ROOT_MARKER_NAME, LOCK_NAME]);
57
+ const MAX_QUARANTINE_ATTEMPTS = 64;
58
+ const LOCK_TOKEN_BYTES = 16;
59
+
60
+ // A receipt is the only evidence a renderer accepts, and membership here is
61
+ // what distinguishes one from an object of the same shape. Identity is not a
62
+ // readable mark: a copy, a clone, or a forwarding proxy is a different object
63
+ // and is therefore not a receipt.
64
+ const ATTESTED_RECEIPTS = new WeakSet();
65
+
66
+ const INSTALL_ACTION = 'Install the hook artifact version as an operator-owned '
67
+ + 'directory whose whole canonical chain is unwritable by the service account.';
68
+
69
+ class CodexHookArtifactError extends Error {
70
+ constructor(message, code, action) {
71
+ super(message);
72
+ this.name = 'CodexHookArtifactError';
73
+ this.code = code;
74
+ this.action = action;
75
+ }
76
+ }
77
+
78
+ function fail(message, code, action = INSTALL_ACTION) {
79
+ return new CodexHookArtifactError(message, code, action);
80
+ }
81
+
82
+ function invalid(message) {
83
+ return fail(
84
+ message,
85
+ 'CODEX_HOOK_ARTIFACT_INVALID',
86
+ 'Name the artifact root, version, runtime identity, and account uids '
87
+ + 'exactly, from configuration.',
88
+ );
89
+ }
90
+
91
+ function unsafe(message) {
92
+ return fail(message, 'CODEX_HOOK_ARTIFACT_UNSAFE');
93
+ }
94
+
95
+ function mismatch(message) {
96
+ return fail(
97
+ message,
98
+ 'CODEX_HOOK_ARTIFACT_MISMATCH',
99
+ 'Reinstall as a new version; a generated closure is never repaired in place.',
100
+ );
101
+ }
102
+
103
+ function missing(message) {
104
+ return fail(
105
+ message,
106
+ 'CODEX_HOOK_ARTIFACT_MISSING',
107
+ 'Install the artifact version before referencing it.',
108
+ );
109
+ }
110
+
111
+ function reserved(message) {
112
+ return fail(
113
+ message,
114
+ 'CODEX_HOOK_ARTIFACT_VERSION_RESERVED',
115
+ 'Release under a new version id; a retired id is never reused.',
116
+ );
117
+ }
118
+
119
+ function locked(message) {
120
+ return fail(
121
+ message,
122
+ 'CODEX_HOOK_ARTIFACT_LOCKED',
123
+ 'Wait for the other operator run on this artifact root to finish.',
124
+ );
125
+ }
126
+
127
+ // The expected accounts come from the caller's configuration. Nothing on disk
128
+ // is allowed to nominate the account that is trusted to own the tree.
129
+ function assertIdentities({ operatorUid, serviceUid }) {
130
+ for (const [label, value] of [['operator', operatorUid], ['service', serviceUid]]) {
131
+ if (!Number.isInteger(value) || value < 0) {
132
+ throw invalid(`The expected ${label} uid must be configured explicitly.`);
133
+ }
134
+ }
135
+ if (serviceUid === 0) {
136
+ throw invalid(
137
+ 'A service account of root has no boundary to enforce; run the service '
138
+ + 'as an unprivileged account.',
139
+ );
140
+ }
141
+ if (serviceUid === operatorUid) {
142
+ throw invalid('The service account must not be the operator account.');
143
+ }
144
+ return Object.freeze({ operatorUid, serviceUid });
145
+ }
146
+
147
+ function assertName(value, label) {
148
+ if (typeof value !== 'string' || !VERSION_RE.test(value)
149
+ || RESERVED_NAMES.has(value)) {
150
+ throw invalid(
151
+ `The ${label} must be an exact immutable name, never a moving alias or `
152
+ + 'a path.',
153
+ );
154
+ }
155
+ return value;
156
+ }
157
+
158
+ function canonicalDirectory(target, label) {
159
+ if (typeof target !== 'string' || target.length === 0
160
+ || !path.isAbsolute(target) || path.normalize(target) !== target) {
161
+ throw invalid(`The ${label} must be a canonical absolute path.`);
162
+ }
163
+ let resolved;
164
+ try {
165
+ resolved = realpathSync(target);
166
+ } catch (error) {
167
+ throw missing(`The ${label} does not exist.`);
168
+ }
169
+ if (resolved !== target) {
170
+ throw invalid(`The ${label} must not be reached through an alias.`);
171
+ }
172
+ return resolved;
173
+ }
174
+
175
+ // A tight file under a loose directory is not protected: the directory permits
176
+ // wholesale replacement of the file. Ownership and POSIX mode bits are all
177
+ // this can see — ACLs and extended attributes are not inspected, so this is a
178
+ // necessary condition, not a proof of unwritability.
179
+ function assertSafeChain(target, { operatorUid, serviceUid }) {
180
+ const root = path.parse(target).root;
181
+ let component = root;
182
+ for (const part of target.slice(root.length).split('/').filter(Boolean)) {
183
+ component = path.join(component, part);
184
+ let stat;
185
+ try {
186
+ stat = lstatSync(component);
187
+ } catch (error) {
188
+ throw missing(`The artifact path component ${part} does not exist.`);
189
+ }
190
+ if (stat.isSymbolicLink()) {
191
+ throw unsafe(`The artifact path component ${part} is a symbolic link.`);
192
+ }
193
+ if (stat.uid === serviceUid) {
194
+ throw unsafe(`The artifact path component ${part} is service-owned.`);
195
+ }
196
+ if (stat.uid !== 0 && stat.uid !== operatorUid) {
197
+ throw unsafe(
198
+ `The artifact path component ${part} is owned by neither root nor the `
199
+ + 'configured operator account.',
200
+ );
201
+ }
202
+ if ((stat.mode & 0o022) !== 0) {
203
+ throw unsafe(`The artifact path component ${part} is service-writable.`);
204
+ }
205
+ if ((stat.mode & 0o7000) !== 0) {
206
+ throw unsafe(`The artifact path component ${part} carries a special mode bit.`);
207
+ }
208
+ if (stat.isDirectory() && (stat.mode & 0o001) === 0) {
209
+ throw unsafe(
210
+ `The artifact path component ${part} cannot be traversed by the `
211
+ + 'service account.',
212
+ );
213
+ }
214
+ }
215
+ }
216
+
217
+ function assertProtectedDirectory(target, identities, label) {
218
+ let stat;
219
+ try {
220
+ stat = lstatSync(target);
221
+ } catch (error) {
222
+ throw missing(`The ${label} does not exist.`);
223
+ }
224
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
225
+ throw unsafe(`The ${label} is not a directory.`);
226
+ }
227
+ if ((stat.mode & 0o7000) !== 0) {
228
+ throw unsafe(`The ${label} carries a special mode bit.`);
229
+ }
230
+ if (!DIRECTORY_MODES.has(stat.mode & 0o777)) {
231
+ throw unsafe(`The ${label} does not carry an accepted directory mode.`);
232
+ }
233
+ assertSafeChain(target, identities);
234
+ return stat;
235
+ }
236
+
237
+ function assertProtectedFile(target, identities, { modes, label }) {
238
+ let stat;
239
+ try {
240
+ stat = lstatSync(target);
241
+ } catch (error) {
242
+ throw missing(`The ${label} is absent.`);
243
+ }
244
+ if (stat.isSymbolicLink() || !stat.isFile()) {
245
+ throw unsafe(`The ${label} is not a regular file.`);
246
+ }
247
+ if (stat.nlink !== 1) {
248
+ throw unsafe(`The ${label} is hard-linked.`);
249
+ }
250
+ if (stat.uid === identities.serviceUid) {
251
+ throw unsafe(`The ${label} is service-owned.`);
252
+ }
253
+ if (stat.uid !== 0 && stat.uid !== identities.operatorUid) {
254
+ throw unsafe(
255
+ `The ${label} is owned by neither root nor the configured operator `
256
+ + 'account.',
257
+ );
258
+ }
259
+ if ((stat.mode & 0o7000) !== 0) {
260
+ throw unsafe(`The ${label} carries a special mode bit.`);
261
+ }
262
+ if (!modes.has(stat.mode & 0o777)) {
263
+ throw unsafe(`The ${label} does not carry an accepted file mode.`);
264
+ }
265
+ return stat;
266
+ }
267
+
268
+ // Staging is owner-only from the moment it exists, so a half-written closure
269
+ // is never readable — let alone executable — under any name.
270
+ function createPrivateStagingDirectory(root, version) {
271
+ const staging = path.join(root, `${STAGING_PREFIX}${version}-${process.pid}`);
272
+ mkdirSync(staging, { mode: 0o700 });
273
+ chmodSync(staging, 0o700);
274
+ return staging;
275
+ }
276
+
277
+ // Members are created inside that private directory, exclusively, and without
278
+ // following anything that may already be there; the final modes and the
279
+ // publish happen only once every member is written.
280
+ function writePrivateMember(target, body) {
281
+ const bytes = Buffer.isBuffer(body) ? body : Buffer.from(body);
282
+ const descriptor = openSync(
283
+ target,
284
+ // eslint-disable-next-line no-bitwise
285
+ fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL
286
+ | fsConstants.O_NOFOLLOW,
287
+ 0o600,
288
+ );
289
+ try {
290
+ // A single write can be short, and a member that is only mostly there is
291
+ // exactly the thing this whole boundary exists to prevent.
292
+ let written = 0;
293
+ while (written < bytes.length) {
294
+ const chunk = writeSync(descriptor, bytes, written, bytes.length - written);
295
+ if (chunk <= 0) {
296
+ throw unsafe(`The member ${path.basename(target)} could not be written whole.`);
297
+ }
298
+ written += chunk;
299
+ }
300
+ } finally {
301
+ closeSync(descriptor);
302
+ }
303
+ }
304
+
305
+ function digestOf(contents) {
306
+ return createHash('sha256').update(contents).digest('hex');
307
+ }
308
+
309
+ function readMember(target, label) {
310
+ try {
311
+ return readFileSync(target);
312
+ } catch (error) {
313
+ throw missing(`The ${label} is absent.`);
314
+ }
315
+ }
316
+
317
+ // The closure U23 installs is derived here and nowhere else: callers cannot
318
+ // pass one in, widen it, or narrow it. A hand-maintained list is precisely how
319
+ // a member goes unlisted, so the build output is the only authority.
320
+ function createHookClosureDeclaration() {
321
+ const bundle = readCheckedInBundle();
322
+ const members = [Object.freeze({
323
+ id: HOOK_ARTIFACT_BUNDLE_ID,
324
+ mode: 0o644,
325
+ relativePath: HOOK_ARTIFACT_BUNDLE_NAME,
326
+ sha256: bundle.sha256,
327
+ sourcePath: bundle.path,
328
+ })];
329
+ const ids = new Set(members.map((member) => member.id));
330
+ const paths = new Set(members.map((member) => member.relativePath));
331
+ if (members.length === 0 || ids.size !== members.length
332
+ || paths.size !== members.length
333
+ || members.some((member) => !RELATIVE_NAME_RE.test(member.relativePath))) {
334
+ throw invalid('The generated hook closure is not a unique member set.');
335
+ }
336
+ return Object.freeze({
337
+ memberCount: members.length,
338
+ members: Object.freeze(members),
339
+ runtime: Object.freeze({
340
+ id: HOOK_RUNTIME_ID,
341
+ mode: 0o755,
342
+ relativePath: HOOK_RUNTIME_RELATIVE_PATH,
343
+ }),
344
+ schema: HOOK_CLOSURE_SCHEMA,
345
+ });
346
+ }
347
+
348
+ function rootMarkerBytes() {
349
+ return `${JSON.stringify({ schema: HOOK_ARTIFACT_ROOT_SCHEMA }, null, 2)}\n`;
350
+ }
351
+
352
+ function assertRootMarker(root, identities) {
353
+ const markerPath = path.join(root, HOOK_ARTIFACT_ROOT_MARKER_NAME);
354
+ assertProtectedFile(markerPath, identities, {
355
+ modes: MEMBER_MODES,
356
+ label: 'artifact root marker',
357
+ });
358
+ if (readFileSync(markerPath, 'utf8') !== rootMarkerBytes()) {
359
+ throw mismatch('The artifact root marker is not the generated marker.');
360
+ }
361
+ return markerPath;
362
+ }
363
+
364
+ // The manifest is rendered from trusted expectations and compared byte for
365
+ // byte against what is installed. Nothing about the expected closure is ever
366
+ // read back out of it.
367
+ function renderManifest({ version, members, runtime }) {
368
+ return `${JSON.stringify({
369
+ artifacts: [...members]
370
+ .map((member) => ({
371
+ id: member.id,
372
+ path: member.relativePath,
373
+ sha256: member.sha256,
374
+ }))
375
+ .sort((left, right) => left.id.localeCompare(right.id)),
376
+ runtime: {
377
+ id: runtime.id,
378
+ path: runtime.path,
379
+ sha256: runtime.sha256,
380
+ version: runtime.version,
381
+ },
382
+ schema: HOOK_ARTIFACT_MANIFEST_SCHEMA,
383
+ version,
384
+ }, null, 2)}\n`;
385
+ }
386
+
387
+ function closureDigest({ manifestSha256, runtime, artifacts }) {
388
+ return digestOf(JSON.stringify({
389
+ artifacts: artifacts.map((entry) => [entry.id, entry.sha256]),
390
+ manifest: manifestSha256,
391
+ runtime: runtime.sha256,
392
+ }));
393
+ }
394
+
395
+ // The runtime is a versioned member of the tree's contract, not an arbitrary
396
+ // absolute path: it is named by an immutable identity under a configured root,
397
+ // and one identity may never denote two different binaries.
398
+ function measureRuntime({
399
+ runtimeRoot,
400
+ runtimeId,
401
+ expectedSha256,
402
+ identities,
403
+ declaration,
404
+ }) {
405
+ assertName(runtimeId, 'runtime identity');
406
+ const root = canonicalDirectory(runtimeRoot, 'runtime root');
407
+ const versionDir = path.join(root, runtimeId);
408
+ assertProtectedDirectory(versionDir, identities, 'runtime version directory');
409
+ const runtimePath = path.join(versionDir, declaration.runtime.relativePath);
410
+ assertSafeChain(runtimePath, identities);
411
+ assertProtectedFile(runtimePath, identities, {
412
+ modes: RUNTIME_MODES,
413
+ label: 'protected runtime',
414
+ });
415
+ const sha256 = digestOf(readMember(runtimePath, 'protected runtime'));
416
+ if (expectedSha256 !== null && expectedSha256 !== undefined) {
417
+ if (!SHA256_RE.test(expectedSha256)) {
418
+ throw invalid('The expected runtime digest must be a lowercase SHA-256.');
419
+ }
420
+ if (expectedSha256 !== sha256) {
421
+ throw mismatch('The protected runtime is not the expected binary.');
422
+ }
423
+ }
424
+ return Object.freeze({
425
+ id: declaration.runtime.id,
426
+ kind: 'protected-runtime',
427
+ path: runtimePath,
428
+ sha256,
429
+ version: runtimeId,
430
+ });
431
+ }
432
+
433
+ // A probe for optional state answers one question — is it there — and nothing
434
+ // else. Any other error is damage, and damage fails the run closed.
435
+ function absent(error) {
436
+ if (error?.code === 'ENOENT') return true;
437
+ throw unsafe(`An artifact tree entry could not be inspected: ${error?.code}.`);
438
+ }
439
+
440
+ // Once a root is initialized, its contents are enumerable: bookkeeping, a
441
+ // quarantine, staging, and version directories. Anything else — a renamed
442
+ // quarantine among them — is unaccounted for, and an unaccounted entry could
443
+ // be holding the retired identities this tree relies on.
444
+ function assertRootOccupants(root) {
445
+ for (const name of readdirSync(root)) {
446
+ if (TREE_FILE_NAMES.has(name) || name === QUARANTINE_DIR_NAME
447
+ || name.startsWith(STAGING_PREFIX)) {
448
+ continue;
449
+ }
450
+ if (!VERSION_RE.test(name) || RESERVED_NAMES.has(name)) {
451
+ throw unsafe(
452
+ `The artifact root holds ${name}, which is neither a version nor part `
453
+ + 'of the tree.',
454
+ );
455
+ }
456
+ }
457
+ }
458
+
459
+ // Every version-shaped entry must actually be a version directory. A file or
460
+ // a symlink wearing a version name is damage, not something to step over.
461
+ function installedVersionNames(root) {
462
+ const names = [];
463
+ for (const name of readdirSync(root)) {
464
+ if (!VERSION_RE.test(name) || RESERVED_NAMES.has(name)
465
+ || TREE_FILE_NAMES.has(name)) {
466
+ continue;
467
+ }
468
+ const stat = lstatSync(path.join(root, name));
469
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
470
+ throw unsafe(`The artifact root holds ${name}, which is not a version directory.`);
471
+ }
472
+ names.push(name);
473
+ }
474
+ return names.sort();
475
+ }
476
+
477
+ // Retired identities are read exactly: one directory per retired version name,
478
+ // holding one numbered directory per retirement of that name. The quarantine
479
+ // is held to the same ownership, mode and type rules as the live tree — a
480
+ // retired closure the service can rewrite is no evidence at all — and anything
481
+ // unreadable or unexpected fails the run closed rather than silently freeing
482
+ // an id for reuse.
483
+ function retiredVersionNames(root, identities) {
484
+ const quarantineRoot = path.join(root, QUARANTINE_DIR_NAME);
485
+ try {
486
+ lstatSync(quarantineRoot);
487
+ } catch (error) {
488
+ if (absent(error)) return new Set();
489
+ }
490
+ assertProtectedDirectory(quarantineRoot, identities, 'quarantine directory');
491
+ let entries;
492
+ try {
493
+ entries = readdirSync(quarantineRoot);
494
+ } catch (error) {
495
+ throw unsafe('The quarantine directory cannot be read.');
496
+ }
497
+ const names = new Set();
498
+ for (const name of entries) {
499
+ if (!VERSION_RE.test(name) || RESERVED_NAMES.has(name)) {
500
+ throw unsafe(`The quarantine directory holds ${name}, which is not a retired version.`);
501
+ }
502
+ assertProtectedDirectory(
503
+ path.join(quarantineRoot, name),
504
+ identities,
505
+ `quarantine of version ${name}`,
506
+ );
507
+ names.add(name);
508
+ }
509
+ return names;
510
+ }
511
+
512
+ // Every manifest in the tree, live or retired, that the runtime-identity
513
+ // history must account for. A retired entry also carries the name it was
514
+ // retired under, so a renamed quarantine directory cannot pass its closure off
515
+ // as another identity — or hand the original identity back for reuse.
516
+ function manifestPathsForHistory(root, identities) {
517
+ assertRootOccupants(root);
518
+ const entries = installedVersionNames(root).map((version) => {
519
+ // A sibling the service can rewrite cannot lend its manifest any weight.
520
+ assertProtectedDirectory(
521
+ path.join(root, version),
522
+ identities,
523
+ `artifact version ${version}`,
524
+ );
525
+ return {
526
+ manifestPath: path.join(root, version, HOOK_ARTIFACT_MANIFEST_NAME),
527
+ version,
528
+ };
529
+ });
530
+ const quarantineRoot = path.join(root, QUARANTINE_DIR_NAME);
531
+ for (const version of retiredVersionNames(root, identities)) {
532
+ const versionRoot = path.join(quarantineRoot, version);
533
+ for (const attempt of readdirSync(versionRoot)) {
534
+ if (!/^[0-9]+$/.test(attempt)) {
535
+ throw unsafe(
536
+ `The retired version ${version} holds ${attempt}, which is not a closure.`,
537
+ );
538
+ }
539
+ assertProtectedDirectory(
540
+ path.join(versionRoot, attempt),
541
+ identities,
542
+ `retirement ${attempt} of version ${version}`,
543
+ );
544
+ entries.push({
545
+ manifestPath: path.join(versionRoot, attempt, HOOK_ARTIFACT_MANIFEST_NAME),
546
+ version,
547
+ });
548
+ }
549
+ }
550
+ return entries;
551
+ }
552
+
553
+ // One runtime identity, one binary — and no reading past damage. A manifest
554
+ // that cannot be read, parsed, or shown to carry a runtime binding is a broken
555
+ // tree, not a version to skip; a retired closure counts too, because a session
556
+ // may still be executing the runtime it named.
557
+ function assertRuntimeBindingsIntact(root, runtime, identities) {
558
+ for (const { manifestPath, version } of manifestPathsForHistory(root, identities)) {
559
+ assertProtectedFile(manifestPath, identities, {
560
+ modes: MEMBER_MODES,
561
+ label: `manifest of version ${version}`,
562
+ });
563
+ let document;
564
+ try {
565
+ document = JSON.parse(readFileSync(manifestPath, 'utf8'));
566
+ } catch (error) {
567
+ throw mismatch(`A manifest in this tree cannot be read: ${manifestPath}.`);
568
+ }
569
+ if (document?.version !== version) {
570
+ throw mismatch(
571
+ `A closure filed under ${version} was written for a different version.`,
572
+ );
573
+ }
574
+ const recorded = document?.runtime;
575
+ if (!recorded || typeof recorded !== 'object'
576
+ || typeof recorded.version !== 'string' || !VERSION_RE.test(recorded.version)
577
+ || !SHA256_RE.test(recorded.sha256 ?? '')) {
578
+ throw mismatch(`A manifest in this tree carries no runtime binding: ${manifestPath}.`);
579
+ }
580
+ if (recorded.version === runtime.version && recorded.sha256 !== runtime.sha256) {
581
+ throw mismatch(
582
+ `The runtime identity ${runtime.version} already denotes a different `
583
+ + 'binary in this tree.',
584
+ );
585
+ }
586
+ }
587
+ }
588
+
589
+ function processIsAlive(pid) {
590
+ if (!Number.isInteger(pid) || pid <= 0) return false;
591
+ try {
592
+ process.kill(pid, 0);
593
+ return true;
594
+ } catch (error) {
595
+ return error?.code === 'EPERM';
596
+ }
597
+ }
598
+
599
+ // There is no automatic reclaim. A lock file cannot prove which run created
600
+ // it — a pid is reused, a token cannot be checked against a dead process — so
601
+ // an existing lock is a refusal, and clearing it is a deliberate act by the
602
+ // account that owns the tree.
603
+ function acquireArtifactRootLock(root) {
604
+ const lockPath = path.join(root, LOCK_NAME);
605
+ const token = randomBytes(LOCK_TOKEN_BYTES).toString('hex');
606
+ let descriptor;
607
+ try {
608
+ descriptor = openSync(lockPath, 'wx', 0o644);
609
+ } catch (error) {
610
+ if (error?.code === 'EEXIST') {
611
+ throw locked(
612
+ 'Another operator run holds this artifact root; clear the lock by hand '
613
+ + 'once no run is in flight.',
614
+ );
615
+ }
616
+ throw unsafe('The artifact root install lock could not be taken.');
617
+ }
618
+ try {
619
+ writeSync(descriptor, `${JSON.stringify({ pid: process.pid, token })}\n`);
620
+ } finally {
621
+ closeSync(descriptor);
622
+ }
623
+ return Object.freeze({ lockPath, token });
624
+ }
625
+
626
+ function releaseArtifactRootLock(handle) {
627
+ let holder = null;
628
+ try {
629
+ holder = JSON.parse(readFileSync(handle.lockPath, 'utf8'));
630
+ } catch (error) {
631
+ throw locked('The artifact root lock was replaced while it was held.');
632
+ }
633
+ if (holder?.token !== handle.token) {
634
+ throw locked('The artifact root lock is no longer the one this run took.');
635
+ }
636
+ rmSync(handle.lockPath, { force: true });
637
+ }
638
+
639
+ function withArtifactRootLock(root, run) {
640
+ const handle = acquireArtifactRootLock(root);
641
+ let result;
642
+ try {
643
+ result = run(handle);
644
+ } catch (error) {
645
+ try {
646
+ releaseArtifactRootLock(handle);
647
+ } catch (releaseError) {
648
+ // The primary failure is the one worth reporting; a lock that is no
649
+ // longer ours is left in place for the operator to inspect.
650
+ }
651
+ throw error;
652
+ }
653
+ releaseArtifactRootLock(handle);
654
+ return result;
655
+ }
656
+
657
+ // Staging directories are cleared only after the root is known to be ours,
658
+ // and only the exact name this version would have used. Anything else with the
659
+ // staging prefix belongs to a run this one cannot account for.
660
+ function clearStagingRemnants(root, version, identities) {
661
+ const prefix = `${STAGING_PREFIX}${version}-`;
662
+ for (const name of readdirSync(root)) {
663
+ if (!name.startsWith(STAGING_PREFIX)) continue;
664
+ const target = path.join(root, name);
665
+ if (!name.startsWith(prefix) || !/^[0-9]+$/.test(name.slice(prefix.length))) {
666
+ throw unsafe(`A staging directory ${name} from another run blocks the install.`);
667
+ }
668
+ const stat = lstatSync(target);
669
+ if (stat.isSymbolicLink() || !stat.isDirectory()
670
+ || stat.uid === identities.serviceUid
671
+ || (stat.uid !== 0 && stat.uid !== identities.operatorUid)) {
672
+ throw unsafe(`A foreign staging directory ${name} blocks the install.`);
673
+ }
674
+ const pid = Number(name.slice(prefix.length));
675
+ if (pid !== process.pid && processIsAlive(pid)) {
676
+ throw locked(`A live staging directory ${name} blocks the install.`);
677
+ }
678
+ try {
679
+ rmSync(target, { recursive: true });
680
+ } catch (error) {
681
+ throw unsafe(`The staging directory ${name} could not be removed.`);
682
+ }
683
+ }
684
+ }
685
+
686
+ function attestHookArtifactVersion({
687
+ artifactRoot,
688
+ version,
689
+ runtimeRoot,
690
+ runtimeId,
691
+ runtimeSha256,
692
+ operatorUid,
693
+ serviceUid,
694
+ }) {
695
+ const identities = assertIdentities({ operatorUid, serviceUid });
696
+ const declaration = createHookClosureDeclaration();
697
+ assertName(version, 'artifact version');
698
+ if (!SHA256_RE.test(runtimeSha256 ?? '')) {
699
+ throw invalid(
700
+ 'Attestation requires the expected runtime digest from configuration.',
701
+ );
702
+ }
703
+ const root = canonicalDirectory(artifactRoot, 'artifact root');
704
+ assertProtectedDirectory(root, identities, 'artifact root');
705
+ assertRootMarker(root, identities);
706
+ const runtime = measureRuntime({
707
+ runtimeRoot,
708
+ runtimeId,
709
+ expectedSha256: runtimeSha256,
710
+ identities,
711
+ declaration,
712
+ });
713
+ assertRuntimeBindingsIntact(root, runtime, identities);
714
+
715
+ const versionDir = path.join(root, version);
716
+ assertProtectedDirectory(versionDir, identities, `artifact version ${version}`);
717
+
718
+ const artifacts = declaration.members.map((member) => {
719
+ const target = path.join(versionDir, member.relativePath);
720
+ assertProtectedFile(target, identities, {
721
+ modes: MEMBER_MODES,
722
+ label: `artifact ${member.relativePath}`,
723
+ });
724
+ const sha256 = digestOf(readMember(target, `artifact ${member.relativePath}`));
725
+ if (sha256 !== member.sha256) {
726
+ throw mismatch(
727
+ `The artifact ${member.relativePath} is not the released body.`,
728
+ );
729
+ }
730
+ return Object.freeze({
731
+ id: member.id,
732
+ kind: 'protected-artifact',
733
+ path: target,
734
+ sha256,
735
+ });
736
+ });
737
+
738
+ const manifestPath = path.join(versionDir, HOOK_ARTIFACT_MANIFEST_NAME);
739
+ assertProtectedFile(manifestPath, identities, {
740
+ modes: MEMBER_MODES,
741
+ label: 'artifact manifest',
742
+ });
743
+ const present = readdirSync(versionDir).sort();
744
+ const expected = [
745
+ HOOK_ARTIFACT_MANIFEST_NAME,
746
+ ...declaration.members.map((member) => member.relativePath),
747
+ ].sort();
748
+ if (present.length !== expected.length
749
+ || present.some((name, index) => name !== expected[index])) {
750
+ throw mismatch(
751
+ `The artifact version ${version} does not hold exactly the declared `
752
+ + 'closure.',
753
+ );
754
+ }
755
+ const manifestText = readFileSync(manifestPath, 'utf8');
756
+ if (manifestText !== renderManifest({
757
+ version,
758
+ members: declaration.members,
759
+ runtime,
760
+ })) {
761
+ throw mismatch('The artifact manifest is not what the generator produces.');
762
+ }
763
+
764
+ const manifestSha256 = digestOf(manifestText);
765
+ const receipt = {
766
+ artifacts: Object.freeze(artifacts),
767
+ closureSha256: closureDigest({ manifestSha256, runtime, artifacts }),
768
+ manifestPath,
769
+ manifestSha256,
770
+ runtime,
771
+ version,
772
+ versionDir,
773
+ };
774
+ Object.freeze(receipt);
775
+ ATTESTED_RECEIPTS.add(receipt);
776
+ return receipt;
777
+ }
778
+
779
+ // A renderer must not act on a shape that merely looks like a receipt: only
780
+ // the object a full attestation returned carries the brand.
781
+ function isAttestationReceipt(value) {
782
+ return Boolean(value) && typeof value === 'object'
783
+ && ATTESTED_RECEIPTS.has(value);
784
+ }
785
+
786
+ function installHookArtifactVersion({
787
+ artifactRoot,
788
+ version,
789
+ runtimeRoot,
790
+ runtimeId,
791
+ operatorUid,
792
+ serviceUid,
793
+ expectedRuntimeSha256,
794
+ }) {
795
+ const identities = assertIdentities({ operatorUid, serviceUid });
796
+ if (!SHA256_RE.test(expectedRuntimeSha256 ?? '')) {
797
+ throw invalid(
798
+ 'Installing requires the expected runtime digest from configuration.',
799
+ );
800
+ }
801
+ const uid = process.getuid?.() ?? 0;
802
+ if (uid === serviceUid) {
803
+ throw unsafe('The service account cannot install the artifacts it executes.');
804
+ }
805
+ if (uid !== 0 && uid !== operatorUid) {
806
+ throw unsafe(
807
+ 'The installer must run as root or as the configured operator account.',
808
+ );
809
+ }
810
+ const declaration = createHookClosureDeclaration();
811
+ assertName(version, 'artifact version');
812
+ const root = canonicalDirectory(artifactRoot, 'artifact root');
813
+ assertProtectedDirectory(root, identities, 'artifact root');
814
+ const runtime = measureRuntime({
815
+ runtimeRoot,
816
+ runtimeId,
817
+ expectedSha256: expectedRuntimeSha256,
818
+ identities,
819
+ declaration,
820
+ });
821
+
822
+ withArtifactRootLock(root, (lock) => {
823
+ const markerPath = path.join(root, HOOK_ARTIFACT_ROOT_MARKER_NAME);
824
+ let marked = true;
825
+ try {
826
+ lstatSync(markerPath);
827
+ } catch (error) {
828
+ marked = !absent(error);
829
+ }
830
+ if (!marked) {
831
+ // A marker is claimed only on a directory dedicated to this purpose:
832
+ // the lock this run just created is the one thing allowed to be there.
833
+ const occupants = readdirSync(root)
834
+ .filter((name) => name !== path.basename(lock.lockPath));
835
+ if (occupants.length !== 0) {
836
+ throw unsafe(
837
+ 'The artifact root already holds unrelated entries; point the '
838
+ + 'installer at a directory dedicated to hook artifacts.',
839
+ );
840
+ }
841
+ writeFileSync(markerPath, rootMarkerBytes(), { mode: 0o644 });
842
+ chmodSync(markerPath, 0o644);
843
+ }
844
+ assertRootMarker(root, identities);
845
+ assertRuntimeBindingsIntact(root, runtime, identities);
846
+
847
+ const versionDir = path.join(root, version);
848
+ let installed = true;
849
+ try {
850
+ lstatSync(versionDir);
851
+ } catch (error) {
852
+ installed = !absent(error);
853
+ }
854
+ if (installed) {
855
+ throw fail(
856
+ `The artifact version ${version} is already installed.`,
857
+ 'CODEX_HOOK_ARTIFACT_VERSION_EXISTS',
858
+ 'Install a new version directory; a released version is never '
859
+ + 'overwritten in place.',
860
+ );
861
+ }
862
+ if (retiredVersionNames(root, identities).has(version)) {
863
+ throw reserved(`The artifact version ${version} was retired already.`);
864
+ }
865
+
866
+ clearStagingRemnants(root, version, identities);
867
+ const staging = createPrivateStagingDirectory(root, version);
868
+ try {
869
+ const published = [];
870
+ for (const member of declaration.members) {
871
+ const body = readMember(member.sourcePath, `source ${member.relativePath}`);
872
+ if (digestOf(body) !== member.sha256) {
873
+ throw mismatch(
874
+ `The source of ${member.relativePath} changed during the install.`,
875
+ );
876
+ }
877
+ const target = path.join(staging, member.relativePath);
878
+ writePrivateMember(target, body);
879
+ published.push([target, member.mode, member.sha256]);
880
+ }
881
+ const manifestPath = path.join(staging, HOOK_ARTIFACT_MANIFEST_NAME);
882
+ const manifest = renderManifest({
883
+ version,
884
+ members: declaration.members,
885
+ runtime,
886
+ });
887
+ writePrivateMember(manifestPath, manifest);
888
+ published.push([manifestPath, 0o644, digestOf(manifest)]);
889
+ // What is about to be published is re-read from disk and compared before
890
+ // it takes its final name; a partial or altered staged member never
891
+ // becomes a version.
892
+ for (const [target, , expected] of published) {
893
+ if (digestOf(readMember(target, `staged ${path.basename(target)}`)) !== expected) {
894
+ throw mismatch(
895
+ `The staged ${path.basename(target)} is not what this install wrote.`,
896
+ );
897
+ }
898
+ }
899
+ for (const [target, mode] of published) chmodSync(target, mode);
900
+ chmodSync(staging, 0o755);
901
+ renameSync(staging, versionDir);
902
+ } catch (error) {
903
+ rmSync(staging, { recursive: true, force: true });
904
+ throw error;
905
+ }
906
+ chmodSync(versionDir, 0o755);
907
+ });
908
+
909
+ return attestHookArtifactVersion({
910
+ artifactRoot: root,
911
+ version,
912
+ runtimeRoot,
913
+ runtimeId,
914
+ runtimeSha256: runtime.sha256,
915
+ operatorUid,
916
+ serviceUid,
917
+ });
918
+ }
919
+
920
+ function listHookArtifactVersions({ artifactRoot }) {
921
+ return installedVersionNames(canonicalDirectory(artifactRoot, 'artifact root'));
922
+ }
923
+
924
+ // Retirement is recoverable by design. A caller's reference list cannot be
925
+ // verified from here, so a wrong list must never be able to destroy a release:
926
+ // the version is moved aside, and deleting it stays an operator decision. The
927
+ // root lock is taken before anything is inspected, so nothing can be swapped
928
+ // between the attestation and the rename.
929
+ function quarantineHookArtifactVersion({
930
+ artifactRoot,
931
+ version,
932
+ runtimeRoot,
933
+ runtimeId,
934
+ runtimeSha256,
935
+ operatorUid,
936
+ serviceUid,
937
+ referencedVersions,
938
+ }) {
939
+ const identities = assertIdentities({ operatorUid, serviceUid });
940
+ assertName(version, 'artifact version');
941
+ const root = canonicalDirectory(artifactRoot, 'artifact root');
942
+ // Nothing is written into a tree that has not first proved it is ours: the
943
+ // marker is read, and only then does a lock file appear inside the root.
944
+ assertProtectedDirectory(root, identities, 'artifact root');
945
+ assertRootMarker(root, identities);
946
+
947
+ return withArtifactRootLock(root, () => {
948
+ const receipt = attestHookArtifactVersion({
949
+ artifactRoot: root,
950
+ version,
951
+ runtimeRoot,
952
+ runtimeId,
953
+ runtimeSha256,
954
+ operatorUid,
955
+ serviceUid,
956
+ });
957
+ if (!Array.isArray(referencedVersions)
958
+ || referencedVersions.some((entry) => typeof entry !== 'string')) {
959
+ throw invalid(
960
+ 'Retiring a version requires the exact list of referenced versions.',
961
+ );
962
+ }
963
+ if (referencedVersions.includes(version)) {
964
+ throw fail(
965
+ `The artifact version ${version} is still referenced.`,
966
+ 'CODEX_HOOK_ARTIFACT_VERSION_REFERENCED',
967
+ 'Retire every session holding the version before retiring the version.',
968
+ );
969
+ }
970
+
971
+ // One directory per retired identity, one numbered directory per
972
+ // retirement of it, so a retired id reads back exactly as it was named.
973
+ const quarantineRoot = path.join(root, QUARANTINE_DIR_NAME);
974
+ for (const [directory, mode] of [[quarantineRoot, 0o755], [path.join(quarantineRoot, version), 0o755]]) {
975
+ let exists = true;
976
+ try {
977
+ lstatSync(directory);
978
+ } catch (error) {
979
+ exists = !absent(error);
980
+ }
981
+ if (!exists) {
982
+ mkdirSync(directory, { mode: 0o700 });
983
+ chmodSync(directory, mode);
984
+ }
985
+ }
986
+ assertProtectedDirectory(quarantineRoot, identities, 'quarantine directory');
987
+ const retiredRoot = path.join(quarantineRoot, version);
988
+ assertProtectedDirectory(retiredRoot, identities, `quarantine of version ${version}`);
989
+
990
+ for (let attempt = 1; attempt <= MAX_QUARANTINE_ATTEMPTS; attempt += 1) {
991
+ const candidate = path.join(retiredRoot, String(attempt));
992
+ try {
993
+ lstatSync(candidate);
994
+ continue;
995
+ } catch (error) {
996
+ // Only a genuinely free name is claimed; anything else is damage.
997
+ absent(error);
998
+ renameSync(receipt.versionDir, candidate);
999
+ return Object.freeze({
1000
+ closureSha256: receipt.closureSha256,
1001
+ quarantinePath: candidate,
1002
+ version,
1003
+ versionDir: receipt.versionDir,
1004
+ });
1005
+ }
1006
+ }
1007
+ throw unsafe('The quarantine of this version has no free slot.');
1008
+ });
1009
+ }
1010
+
1011
+ module.exports = {
1012
+ CodexHookArtifactError,
1013
+ HOOK_ARTIFACT_BUNDLE_ID,
1014
+ HOOK_ARTIFACT_BUNDLE_NAME,
1015
+ HOOK_ARTIFACT_MANIFEST_NAME,
1016
+ HOOK_ARTIFACT_MANIFEST_SCHEMA,
1017
+ HOOK_ARTIFACT_ROOT_MARKER_NAME,
1018
+ HOOK_ARTIFACT_ROOT_SCHEMA,
1019
+ HOOK_CLOSURE_SCHEMA,
1020
+ HOOK_RUNTIME_ID,
1021
+ HOOK_RUNTIME_RELATIVE_PATH,
1022
+ QUARANTINE_DIR_NAME,
1023
+ acquireArtifactRootLock,
1024
+ attestHookArtifactVersion,
1025
+ createHookClosureDeclaration,
1026
+ createPrivateStagingDirectory,
1027
+ isAttestationReceipt,
1028
+ installHookArtifactVersion,
1029
+ listHookArtifactVersions,
1030
+ quarantineHookArtifactVersion,
1031
+ releaseArtifactRootLock,
1032
+ writePrivateMember,
1033
+ };