@happyvertical/smrt-app-runtime 0.43.10 → 0.44.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.
package/AGENTS.md CHANGED
@@ -5,6 +5,9 @@ Application infrastructure composition for the validated runtime profiles in
5
5
 
6
6
  ## Local profile
7
7
 
8
+ - `validateApplicationId()` is the canonical strict validator for explicit app
9
+ IDs. `encodeApplicationId()` derives a length-bounded, collision-resistant
10
+ ID from package names; generated operational surfaces must share these APIs.
8
11
  - `initializeLocalApplicationRuntime()` owns user data paths, SQLite tuning,
9
12
  local application-secret creation, and the single-use owner bootstrap flow.
10
13
  - SQLite is acquired through `@happyvertical/sql`'s explicit
@@ -12,6 +15,13 @@ Application infrastructure composition for the validated runtime profiles in
12
15
  runtime establishes its user-owned mode-0700 data root.
13
16
  - Application migrations are explicit through `prepareDatabase`; runtime never
14
17
  creates application model tables implicitly.
18
+ - Standalone migration commands must call `prepareLocalDatabaseStorage()`
19
+ before opening SQLite so custody is established without creating schema or
20
+ bootstrap records.
21
+ - Read-only/copy/import operator commands must call
22
+ `validateLocalDatabaseStorage()` before opening SQLite; it verifies the
23
+ app-bound marker, source separation, real path chain, ownership, and mode
24
+ without creating or repairing storage.
15
25
  - Owner bootstrap creates normal `Person`, `User`, `Tenant`, owner `Role` /
16
26
  `Membership`, and `Session` records in one transaction.
17
27
  - Bootstrap is loopback-only. Only an HMAC of the short-lived token is stored.
@@ -47,6 +57,22 @@ Application infrastructure composition for the validated runtime profiles in
47
57
  - Cloud must keep required tenant context and must never introduce a root or
48
58
  unscoped tenant fallback. RLS remains an explicit deployment/migration choice.
49
59
 
60
+ ## Public runtime diagnostics
61
+
62
+ - `projectRuntimeDiagnostics()` is the only public diagnostic projection. It
63
+ constructs the schema-version-1 allowlist and never serializes the private
64
+ local or deployed diagnostics objects.
65
+ - The allowlist is limited to active profile/coarse health, schema and
66
+ migration readiness, six fixed capability statuses, a sorted/capped public
67
+ tool inventory plus digest, explicit operational topology differences,
68
+ coarse worker heartbeat state, and eight stable code/timestamp errors.
69
+ - Callers supply an explicit clock and worker-heartbeat seam. A missing
70
+ heartbeat is `unknown`; web-process liveness never proves worker liveness.
71
+ - Error ingestion maps unknown codes to `runtime_error`, truncates timestamps
72
+ to the minute, and ignores messages, stacks, logs, records, and nested state.
73
+ - Authentication and diagnostics-read authorization belong before every call
74
+ to the projector or its runtime probes.
75
+
50
76
  ## Invariants
51
77
 
52
78
  - Never expose application-secret bytes or bootstrap token hashes in diagnostics.
package/dist/index.d.ts CHANGED
@@ -11,6 +11,12 @@ import { SessionService } from '@happyvertical/smrt-users';
11
11
  import { TaskRunner } from '@happyvertical/smrt-jobs';
12
12
  import { TaskRunnerConfig } from '@happyvertical/smrt-jobs';
13
13
 
14
+ declare const CAPABILITY_IDS: readonly ["asset-storage", "authentication", "background-jobs", "database", "paid-capabilities", "secret-storage"];
15
+
16
+ declare type CapabilityId = (typeof CAPABILITY_IDS)[number];
17
+
18
+ declare type CapabilityStatus = 'available' | 'disabled' | 'unavailable' | 'unknown';
19
+
14
20
  export declare interface ClaimLocalOwnerInput {
15
21
  token: string;
16
22
  name: string;
@@ -136,6 +142,11 @@ export declare interface DeployedRuntimeReadiness {
136
142
  readonly secretValuesIncluded: false;
137
143
  }
138
144
 
145
+ /** Encode a package/project identity into a collision-resistant runtime ID. */
146
+ export declare function encodeApplicationId(value: string): string;
147
+
148
+ declare const ERROR_CODES: readonly ["authentication_unavailable", "database_unavailable", "migration_required", "provider_unavailable", "runtime_stopped", "worker_stale"];
149
+
139
150
  /**
140
151
  * Initialize a self-hosted or cloud application composition.
141
152
  *
@@ -263,6 +274,23 @@ export declare interface LocalRuntimePaths {
263
274
  readonly applicationSecret: string;
264
275
  }
265
276
 
277
+ export declare const MAX_RUNTIME_DIAGNOSTIC_ERRORS = 8;
278
+
279
+ export declare const MAX_RUNTIME_DIAGNOSTIC_TOOLS = 64;
280
+
281
+ /**
282
+ * Claim the secure local database root before a standalone migration command
283
+ * opens SQLite. This does not create application schema or bootstrap records.
284
+ */
285
+ export declare function prepareLocalDatabaseStorage(options: ResolveLocalRuntimePathsOptions): Promise<LocalRuntimePaths>;
286
+
287
+ /**
288
+ * Project private local/deployed state into the deterministic public contract.
289
+ * Unknown, oversized, nested, accessor-backed, or prohibited values are never
290
+ * copied. Invalid error codes become `runtime_error` at ingestion.
291
+ */
292
+ export declare function projectRuntimeDiagnostics(input: RuntimeDiagnosticsProjectionInput): RuntimeDiagnostics;
293
+
266
294
  export declare type PublicAuthenticationProvider = Exclude<AuthenticationProvider, 'owner-bootstrap'>;
267
295
 
268
296
  /**
@@ -285,4 +313,72 @@ export declare interface ResolveLocalRuntimePathsOptions {
285
313
  env?: NodeJS.ProcessEnv;
286
314
  }
287
315
 
316
+ export declare const RUNTIME_DIAGNOSTICS_SCHEMA_VERSION: 1;
317
+
318
+ export declare const RUNTIME_WORKER_FRESHNESS_SECONDS = 120;
319
+
320
+ declare type RuntimeDiagnosticErrorCode = (typeof ERROR_CODES)[number] | 'runtime_error';
321
+
322
+ /**
323
+ * Versioned public diagnostics returned only after application authorization.
324
+ * Every field is a fixed allowlist projection; this is intentionally not a
325
+ * cleaned or serialized form of either private runtime diagnostics object.
326
+ */
327
+ export declare interface RuntimeDiagnostics {
328
+ readonly schemaVersion: 1;
329
+ readonly profile: ApplicationRuntimeProfile | 'unknown';
330
+ readonly health: 'healthy' | 'degraded' | 'stopped' | 'unknown';
331
+ readonly schema: {
332
+ readonly status: 'ready' | 'not-ready' | 'unknown';
333
+ readonly migrations: 'current' | 'failed' | 'pending' | 'unknown';
334
+ };
335
+ readonly capabilities: readonly {
336
+ readonly id: CapabilityId;
337
+ readonly status: CapabilityStatus;
338
+ }[];
339
+ readonly tools: {
340
+ readonly names: readonly string[];
341
+ readonly count: number;
342
+ readonly digest: string;
343
+ };
344
+ readonly operationalDifferences: {
345
+ readonly backgroundJobs: 'disabled' | 'enabled';
346
+ readonly workerTopology: WorkerTopology;
347
+ };
348
+ readonly worker: {
349
+ readonly topology: WorkerTopology;
350
+ readonly liveness: 'alive' | 'not-required' | 'stale' | 'unknown';
351
+ readonly heartbeatAt: string | null;
352
+ readonly observedAt: string;
353
+ };
354
+ readonly recentErrors: readonly {
355
+ readonly code: RuntimeDiagnosticErrorCode;
356
+ readonly at: string;
357
+ }[];
358
+ }
359
+
360
+ export declare interface RuntimeDiagnosticsProjectionInput {
361
+ readonly profile?: unknown;
362
+ readonly health?: unknown;
363
+ readonly schema?: unknown;
364
+ readonly capabilities?: unknown;
365
+ readonly toolNames?: unknown;
366
+ readonly worker?: unknown;
367
+ readonly recentErrors?: unknown;
368
+ /** Required explicit clock seam; the projector never reads the process clock. */
369
+ readonly observedAt?: unknown;
370
+ }
371
+
372
+ /** Validate an explicit runtime identity. */
373
+ export declare function validateApplicationId(value: string): string;
374
+
375
+ /**
376
+ * Verify an already-initialized local database root without creating or
377
+ * changing any filesystem/database artifact. Operator read/copy/import paths
378
+ * use this boundary before opening SQLite outside the runtime bootstrap.
379
+ */
380
+ export declare function validateLocalDatabaseStorage(options: ResolveLocalRuntimePathsOptions): Promise<LocalRuntimePaths>;
381
+
382
+ declare type WorkerTopology = 'embedded' | 'external' | 'inline' | 'scalable';
383
+
288
384
  export { }
package/dist/index.js CHANGED
@@ -505,7 +505,7 @@ function createDiagnostics(runtime) {
505
505
  provider: value,
506
506
  configured: true
507
507
  });
508
- return deepFreeze({
508
+ return deepFreeze$1({
509
509
  schemaVersion: 1,
510
510
  runtime,
511
511
  providers: {
@@ -547,7 +547,7 @@ function createStoppedReadiness(profile) {
547
547
  }, false);
548
548
  }
549
549
  function freezeReadiness(profile, components, ready) {
550
- return deepFreeze({
550
+ return deepFreeze$1({
551
551
  schemaVersion: 1,
552
552
  profile,
553
553
  status: ready ? "ready" : "not-ready",
@@ -581,12 +581,247 @@ async function cleanupAfterStartupFailure(binding, db, startupError) {
581
581
  }
582
582
  throw startupError;
583
583
  }
584
- function deepFreeze(value, seen = /* @__PURE__ */ new WeakSet()) {
584
+ function deepFreeze$1(value, seen = /* @__PURE__ */ new WeakSet()) {
585
585
  if (value && typeof value === "object") {
586
586
  if (seen.has(value)) return value;
587
587
  seen.add(value);
588
588
  if (!Object.isFrozen(value)) Object.freeze(value);
589
- for (const child of Object.values(value)) deepFreeze(child, seen);
589
+ for (const child of Object.values(value)) deepFreeze$1(child, seen);
590
+ }
591
+ return value;
592
+ }
593
+ //#endregion
594
+ //#region src/runtime-diagnostics.ts
595
+ var RUNTIME_DIAGNOSTICS_SCHEMA_VERSION = 1;
596
+ var MAX_RUNTIME_DIAGNOSTIC_TOOLS = 64;
597
+ var MAX_RUNTIME_DIAGNOSTIC_ERRORS = 8;
598
+ var RUNTIME_WORKER_FRESHNESS_SECONDS = 120;
599
+ var CAPABILITY_IDS = [
600
+ "asset-storage",
601
+ "authentication",
602
+ "background-jobs",
603
+ "database",
604
+ "paid-capabilities",
605
+ "secret-storage"
606
+ ];
607
+ var ERROR_CODES = [
608
+ "authentication_unavailable",
609
+ "database_unavailable",
610
+ "migration_required",
611
+ "provider_unavailable",
612
+ "runtime_stopped",
613
+ "worker_stale"
614
+ ];
615
+ var PROHIBITED_IDENTIFIER_PARTS = /* @__PURE__ */ new Set([
616
+ "authorization",
617
+ "cookie",
618
+ "constructor",
619
+ "credential",
620
+ "email",
621
+ "hash",
622
+ "key",
623
+ "log",
624
+ "logs",
625
+ "password",
626
+ "path",
627
+ "record",
628
+ "prototype",
629
+ "secret",
630
+ "session",
631
+ "stack",
632
+ "tenant",
633
+ "token",
634
+ "url",
635
+ "user"
636
+ ]);
637
+ function projectRuntimeDiagnostics(input) {
638
+ const source = plainRecord(input);
639
+ const observedAtValue = read(source, "observedAt");
640
+ const observedAtMilliseconds = timestampMilliseconds(observedAtValue);
641
+ const observedAt = coarseTimestamp(observedAtValue) ?? EPOCH;
642
+ const profile = runtimeProfile(read(source, "profile"));
643
+ const worker = projectWorker(read(source, "worker"), observedAt, observedAtMilliseconds);
644
+ const capabilities = projectCapabilities(read(source, "capabilities"));
645
+ const tools = canonicalTools(read(source, "toolNames"));
646
+ return deepFreeze({
647
+ schemaVersion: 1,
648
+ profile,
649
+ health: oneOf(read(source, "health"), HEALTH_VALUES, "unknown"),
650
+ schema: projectSchema(read(source, "schema")),
651
+ capabilities,
652
+ tools: {
653
+ names: tools,
654
+ count: tools.length,
655
+ digest: createHash("sha256").update(tools.join("\n")).digest("hex")
656
+ },
657
+ operationalDifferences: {
658
+ backgroundJobs: capabilityStatus(capabilities, "background-jobs") === "available" ? "enabled" : "disabled",
659
+ workerTopology: worker.topology
660
+ },
661
+ worker,
662
+ recentErrors: projectErrors(read(source, "recentErrors"))
663
+ });
664
+ }
665
+ var EPOCH = "1970-01-01T00:00:00.000Z";
666
+ var PROFILES = [
667
+ "local",
668
+ "self-hosted",
669
+ "cloud",
670
+ "unknown"
671
+ ];
672
+ var HEALTH_VALUES = [
673
+ "healthy",
674
+ "degraded",
675
+ "stopped",
676
+ "unknown"
677
+ ];
678
+ var SCHEMA_VALUES = [
679
+ "ready",
680
+ "not-ready",
681
+ "unknown"
682
+ ];
683
+ var MIGRATION_VALUES = [
684
+ "current",
685
+ "failed",
686
+ "pending",
687
+ "unknown"
688
+ ];
689
+ var CAPABILITY_VALUES = [
690
+ "available",
691
+ "disabled",
692
+ "unavailable",
693
+ "unknown"
694
+ ];
695
+ var WORKER_TOPOLOGIES = [
696
+ "embedded",
697
+ "external",
698
+ "inline",
699
+ "scalable"
700
+ ];
701
+ function runtimeProfile(value) {
702
+ return oneOf(value, PROFILES, "unknown");
703
+ }
704
+ function projectSchema(value) {
705
+ const source = plainRecord(value);
706
+ return {
707
+ status: oneOf(read(source, "status"), SCHEMA_VALUES, "unknown"),
708
+ migrations: oneOf(read(source, "migrations"), MIGRATION_VALUES, "unknown")
709
+ };
710
+ }
711
+ function projectCapabilities(value) {
712
+ const source = plainRecord(value);
713
+ return CAPABILITY_IDS.map((id) => ({
714
+ id,
715
+ status: oneOf(read(source, id), CAPABILITY_VALUES, "unknown")
716
+ }));
717
+ }
718
+ function canonicalTools(value) {
719
+ const names = /* @__PURE__ */ new Set();
720
+ for (const candidate of safeArrayItems(value, 256)) {
721
+ if (typeof candidate !== "string" || !isPublicIdentifier(candidate)) continue;
722
+ names.add(candidate);
723
+ }
724
+ return [...names].sort().slice(0, 64);
725
+ }
726
+ function isPublicIdentifier(value) {
727
+ if (value.length < 1 || value.length > 80 || !/^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/.test(value)) return false;
728
+ return !value.split(/[._-]/).some((part) => PROHIBITED_IDENTIFIER_PARTS.has(part));
729
+ }
730
+ function projectWorker(value, observedAt, observedAtMilliseconds) {
731
+ const source = plainRecord(value);
732
+ const topology = oneOf(read(source, "topology"), WORKER_TOPOLOGIES, "inline");
733
+ const required = read(source, "required") === true;
734
+ const heartbeatValue = read(source, "heartbeatAt");
735
+ const heartbeatMilliseconds = timestampMilliseconds(heartbeatValue);
736
+ const heartbeatAt = coarseTimestamp(heartbeatValue);
737
+ let liveness = "not-required";
738
+ if (required) {
739
+ liveness = "unknown";
740
+ if (heartbeatMilliseconds !== null && observedAtMilliseconds !== null) {
741
+ const ageSeconds = (observedAtMilliseconds - heartbeatMilliseconds) / 1e3;
742
+ if (ageSeconds >= 0) liveness = ageSeconds <= 120 ? "alive" : "stale";
743
+ }
744
+ }
745
+ return {
746
+ topology,
747
+ liveness,
748
+ heartbeatAt,
749
+ observedAt
750
+ };
751
+ }
752
+ function projectErrors(value) {
753
+ const errors = [];
754
+ for (const candidate of safeArrayItems(value, 32)) {
755
+ const source = plainRecord(candidate);
756
+ const at = coarseTimestamp(read(source, "at"));
757
+ if (!at) continue;
758
+ const rawCode = read(source, "code");
759
+ const code = ERROR_CODES.includes(rawCode) ? rawCode : "runtime_error";
760
+ errors.push({
761
+ code,
762
+ at
763
+ });
764
+ }
765
+ return errors.sort((left, right) => left.at === right.at ? left.code.localeCompare(right.code) : right.at.localeCompare(left.at)).slice(0, 8);
766
+ }
767
+ function capabilityStatus(capabilities, id) {
768
+ return capabilities.find((capability) => capability.id === id)?.status ?? "unknown";
769
+ }
770
+ function coarseTimestamp(value) {
771
+ const milliseconds = timestampMilliseconds(value);
772
+ if (milliseconds === null) return null;
773
+ const minute = Math.floor(milliseconds / 6e4) * 6e4;
774
+ return new Date(minute).toISOString();
775
+ }
776
+ function timestampMilliseconds(value) {
777
+ try {
778
+ if (!(typeof value === "string" || value instanceof Date)) return null;
779
+ const milliseconds = value instanceof Date ? value.getTime() : Date.parse(value);
780
+ return Number.isFinite(milliseconds) ? milliseconds : null;
781
+ } catch {
782
+ return null;
783
+ }
784
+ }
785
+ function oneOf(value, values, fallback) {
786
+ return typeof value === "string" && values.includes(value) ? value : fallback;
787
+ }
788
+ function plainRecord(value) {
789
+ try {
790
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
791
+ const prototype = Object.getPrototypeOf(value);
792
+ return prototype === Object.prototype || prototype === null ? value : null;
793
+ } catch {
794
+ return null;
795
+ }
796
+ }
797
+ function read(source, key) {
798
+ try {
799
+ if (!source || !Object.hasOwn(source, key)) return;
800
+ return source[key];
801
+ } catch {
802
+ return;
803
+ }
804
+ }
805
+ function safeArrayItems(value, maximum) {
806
+ try {
807
+ if (!Array.isArray(value)) return [];
808
+ const length = Math.min(value.length, maximum);
809
+ if (!Number.isSafeInteger(length) || length < 0) return [];
810
+ const items = [];
811
+ for (let index = 0; index < length; index += 1) try {
812
+ items.push(value[index]);
813
+ } catch {
814
+ items.push(void 0);
815
+ }
816
+ return items;
817
+ } catch {
818
+ return [];
819
+ }
820
+ }
821
+ function deepFreeze(value) {
822
+ if (value && typeof value === "object") {
823
+ Object.freeze(value);
824
+ for (const child of Object.values(value)) if (child && typeof child === "object" && !Object.isFrozen(child)) deepFreeze(child);
590
825
  }
591
826
  return value;
592
827
  }
@@ -645,6 +880,44 @@ var INITIALIZATION_LOCK_WORKER = `
645
880
  });
646
881
  `;
647
882
  var execFileAsync = promisify(execFile);
883
+ async function prepareLocalDatabaseStorage(options) {
884
+ const paths = resolveLocalRuntimePaths(options);
885
+ const sourceRoot = resolve(options.sourceRoot ?? process.cwd());
886
+ const appId = validateApplicationId(options.appId);
887
+ await preflightLocalApplicationRoot(paths.root, sourceRoot, appId);
888
+ const initializationLock = await acquireInitializationLock(paths.root);
889
+ let db;
890
+ let failure;
891
+ try {
892
+ await preflightLocalApplicationRoot(paths.root, sourceRoot, appId);
893
+ db = (await acquireLocalDatabase(paths, sourceRoot, appId)).db;
894
+ await db.close?.();
895
+ db = void 0;
896
+ } catch (error) {
897
+ failure = db ? await closeDatabaseAfterFailure(db, error) : error;
898
+ db = void 0;
899
+ }
900
+ try {
901
+ await initializationLock.release();
902
+ } catch (releaseError) {
903
+ failure = failure ? preservePrimaryFailure(failure, releaseError, "initializationLockReleaseError", "Local database storage preparation and initialization-lock release both failed.") : releaseError;
904
+ }
905
+ if (failure) throw failure;
906
+ return paths;
907
+ }
908
+ async function validateLocalDatabaseStorage(options) {
909
+ const paths = resolveLocalRuntimePaths(options);
910
+ const sourceRoot = resolve(options.sourceRoot ?? process.cwd());
911
+ const appId = validateApplicationId(options.appId);
912
+ await preflightLocalApplicationRoot(paths.root, sourceRoot, appId);
913
+ const canonicalSourceRoot = await realpath(sourceRoot);
914
+ await validateRootMarker(resolve(paths.root, `${ROOT_MARKER_PREFIX}${appId}`), canonicalSourceRoot);
915
+ await validateExistingPathChain(paths.database, canonicalSourceRoot, "file");
916
+ const database = await lstat(paths.database);
917
+ const currentUid = process.getuid?.();
918
+ if (database.isSymbolicLink() || !database.isFile() || currentUid !== void 0 && database.uid !== currentUid || (database.mode & 511) !== 384) throw unsafeFilesystemEntry(`Local application database must be a current-user-owned mode-0600 file: ${paths.database}`);
919
+ return paths;
920
+ }
648
921
  var LocalRuntimeError = class extends Error {
649
922
  constructor(code, message) {
650
923
  super(message);
@@ -654,7 +927,7 @@ var LocalRuntimeError = class extends Error {
654
927
  code;
655
928
  };
656
929
  function resolveLocalRuntimePaths(options) {
657
- const appId = normalizeAppId(options.appId);
930
+ const appId = validateApplicationId(options.appId);
658
931
  const runtimePlatform = options.platform ?? platform();
659
932
  const runtimeHome = options.homeDirectory ?? homedir();
660
933
  const defaultRoot = defaultApplicationDataRoot(runtimePlatform, runtimeHome, options.env ?? process.env);
@@ -679,7 +952,7 @@ async function initializeLocalApplicationRuntime(options) {
679
952
  });
680
953
  const paths = resolveLocalRuntimePaths(options);
681
954
  const sourceRoot = resolve(options.sourceRoot ?? process.cwd());
682
- const appId = normalizeAppId(options.appId);
955
+ const appId = validateApplicationId(options.appId);
683
956
  await preflightLocalApplicationRoot(paths.root, sourceRoot, appId);
684
957
  const initializationLock = await acquireInitializationLock(paths.root);
685
958
  let db;
@@ -1075,13 +1348,16 @@ async function acquireLocalDatabase(paths, sourceRoot, appId) {
1075
1348
  root: paths.root
1076
1349
  }
1077
1350
  });
1351
+ await enforcePrivateDatabaseMode(paths.database, canonicalSourceRoot);
1078
1352
  } catch (error) {
1353
+ if (db) throw await closeDatabaseAfterFailure(db, error);
1079
1354
  if (prepared.pendingMarkerCreatedByAttempt) {
1080
1355
  await removeMarkerIfPresent(prepared.pendingMarker, canonicalSourceRoot);
1081
1356
  await removeCreatedDirectories(createdDirectories);
1082
1357
  }
1083
1358
  throw error;
1084
1359
  }
1360
+ if (!db) throw new Error("Local runtime database acquisition returned no database.");
1085
1361
  try {
1086
1362
  await publishRootMarker(prepared.finalMarker, canonicalSourceRoot);
1087
1363
  await removeMarkerIfPresent(prepared.pendingMarker, canonicalSourceRoot);
@@ -1093,6 +1369,15 @@ async function acquireLocalDatabase(paths, sourceRoot, appId) {
1093
1369
  db
1094
1370
  };
1095
1371
  }
1372
+ async function enforcePrivateDatabaseMode(path, canonicalSourceRoot) {
1373
+ const database = await openSafeFile(path, constants.O_RDWR, 384, "Local application database", canonicalSourceRoot);
1374
+ try {
1375
+ await database.chmod(384);
1376
+ await database.sync();
1377
+ } finally {
1378
+ await database.close();
1379
+ }
1380
+ }
1096
1381
  async function closeDatabaseAfterFailure(db, primaryFailure) {
1097
1382
  try {
1098
1383
  await db.close?.();
@@ -1475,11 +1760,17 @@ function normalizeLoopbackHost(value) {
1475
1760
  if (octets.length === 4 && octets.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255) && Number(octets[0]) === 127) return host;
1476
1761
  throw new LocalRuntimeError("unsafe_public_exposure", "Owner bootstrap is restricted to a loopback IP literal. Use ::1 or 127.0.0.0/8.");
1477
1762
  }
1478
- function normalizeAppId(value) {
1763
+ function validateApplicationId(value) {
1479
1764
  const appId = value.trim().toLowerCase();
1480
1765
  if (!/^[a-z0-9][a-z0-9.-]{0,62}$/.test(appId)) throw new LocalRuntimeError("invalid_configuration", "appId must contain only lowercase letters, digits, dots, or hyphens.");
1481
1766
  return appId;
1482
1767
  }
1768
+ function encodeApplicationId(value) {
1769
+ const source = value.trim().toLowerCase();
1770
+ if (/^[a-z0-9][a-z0-9.-]{0,62}$/.test(source)) return source;
1771
+ const digest = createHash("sha256").update(value.trim()).digest("hex").slice(0, 10);
1772
+ return validateApplicationId(`${source.replace(/[^a-z0-9.-]+/g, "-").replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, "").slice(0, 52).replace(/[^a-z0-9]+$/g, "") || "app"}-${digest}`);
1773
+ }
1483
1774
  function defaultApplicationDataRoot(runtimePlatform, runtimeHome, env) {
1484
1775
  if (runtimePlatform === "darwin") return resolve(runtimeHome, "Library", "Application Support");
1485
1776
  if (runtimePlatform === "win32") return resolve(env.LOCALAPPDATA ?? env.APPDATA ?? runtimeHome);
@@ -1511,6 +1802,6 @@ function isDirectoryNotEmpty(error) {
1511
1802
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOTEMPTY";
1512
1803
  }
1513
1804
  //#endregion
1514
- export { DeployedRuntimeCleanupError, DeployedRuntimeError, LocalRuntimeError, initializeDeployedApplicationRuntime, initializeLocalApplicationRuntime, resolveLocalRuntimePaths };
1805
+ export { DeployedRuntimeCleanupError, DeployedRuntimeError, LocalRuntimeError, MAX_RUNTIME_DIAGNOSTIC_ERRORS, MAX_RUNTIME_DIAGNOSTIC_TOOLS, RUNTIME_DIAGNOSTICS_SCHEMA_VERSION, RUNTIME_WORKER_FRESHNESS_SECONDS, encodeApplicationId, initializeDeployedApplicationRuntime, initializeLocalApplicationRuntime, prepareLocalDatabaseStorage, projectRuntimeDiagnostics, resolveLocalRuntimePaths, validateApplicationId, validateLocalDatabaseStorage };
1515
1806
 
1516
1807
  //# sourceMappingURL=index.js.map