@@ -0,0 +1,61 @@
|
||||
// lib/incarnation.ts — process-launch incarnation identity (DESIGN HAZARD).
|
||||
//
|
||||
// Mercer-measured hazard, carried by the NG-1 tasking: Pi documents
|
||||
// PI_SESSION_ID as SESSION identity, not incarnation, and Mercer's
|
||||
// environment carries no incarnation variable. Per L2-D01/D05 we therefore
|
||||
// do NOT relabel PI_SESSION_ID as an incarnation — the R6 journal keys on
|
||||
// the identity minted here.
|
||||
//
|
||||
// Resolution order (pure function of process state + injectable rng):
|
||||
// 1. Launcher claim: MOSAIC_LAUNCH_INCARNATION, VALIDATED (NG-7 F2): the
|
||||
// claim becomes a FILENAME COMPONENT in the journal and goal-state
|
||||
// paths, so anything outside ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$ is
|
||||
// IGNORED (no path separators, no leading dot, no traversal shape) and
|
||||
// resolution falls through — an invalid claim must never reach a path.
|
||||
// 2. Process-global: globalThis.__mosaicCoreIncarnation — set on first
|
||||
// mint. globalThis is per-process, so the identity survives extension
|
||||
// /reload (module re-import) and session switches WITHIN the process.
|
||||
// Deliberately NOT exported via process.env: child shells must not
|
||||
// inherit a forgeable-looking claim.
|
||||
// 3. Mint: rng() — DEFAULTS to crypto.randomUUID (NG-7 F1: the first cut
|
||||
// required an injectable rng with no default, and both production
|
||||
// call sites invoked incarnationIdentity() with no argument — a live
|
||||
// startup throw the test suites never caught because every arm
|
||||
// injected an rng or an explicit incarnationId).
|
||||
//
|
||||
// This function never reads PI_SESSION_ID. There is no code path here that
|
||||
// could conflate session identity with launch identity.
|
||||
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
export const LAUNCH_CLAIM_ENV = "MOSAIC_LAUNCH_INCARNATION";
|
||||
const GLOBAL_SLOT = "__mosaicCoreIncarnation";
|
||||
|
||||
/** A claim is usable only if it is a safe single path component. */
|
||||
export const CLAIM_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
||||
|
||||
type Rng = () => string;
|
||||
|
||||
export function validClaim(claim: string | undefined): claim is string {
|
||||
return typeof claim === "string" && CLAIM_RE.test(claim);
|
||||
}
|
||||
|
||||
export function incarnationIdentity(rng: Rng = () => randomUUID()): string {
|
||||
const claim = process.env[LAUNCH_CLAIM_ENV];
|
||||
if (validClaim(claim)) {
|
||||
return claim;
|
||||
}
|
||||
const g = globalThis as Record<string, unknown>;
|
||||
const existing = g[GLOBAL_SLOT];
|
||||
if (typeof existing === "string" && existing.length > 0) {
|
||||
return existing;
|
||||
}
|
||||
const minted = rng();
|
||||
g[GLOBAL_SLOT] = minted;
|
||||
return minted;
|
||||
}
|
||||
|
||||
/** Test/adapter helper: clear the process-global slot (never touches claims). */
|
||||
export function resetIncarnationGlobal(): void {
|
||||
delete (globalThis as Record<string, unknown>)[GLOBAL_SLOT];
|
||||
}
|
||||
Reference in New Issue
Block a user