@nylorun/runtime 0.9.0-beta → 0.10.0-beta

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 (129) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +96 -25
  3. package/dist/adapters/media.d.ts +2 -2
  4. package/dist/adapters/media.js +2 -2
  5. package/dist/adapters/observe.d.ts +2 -1
  6. package/dist/adapters/observe.js +2 -3
  7. package/dist/assets.d.ts +2 -2
  8. package/dist/assets.js +5 -3
  9. package/dist/core/executors.d.ts +11 -2
  10. package/dist/core/executors.js +20 -4
  11. package/dist/core/flow-host.d.ts +264 -0
  12. package/dist/core/flow-host.js +783 -0
  13. package/dist/core/limits.d.ts +40 -0
  14. package/dist/core/limits.js +76 -0
  15. package/dist/core/runtime.d.ts +5 -90
  16. package/dist/core/runtime.js +3 -1447
  17. package/dist/core/sandbox-routes.d.ts +35 -0
  18. package/dist/core/sandbox-routes.js +106 -0
  19. package/dist/core/store.d.ts +3 -1
  20. package/dist/core/store.js +14 -73
  21. package/dist/core/turn-manifest.d.ts +50 -0
  22. package/dist/core/turn-manifest.js +66 -0
  23. package/dist/host/config-for.d.ts +16 -0
  24. package/dist/host/config-for.js +45 -0
  25. package/dist/host/config.d.ts +24 -0
  26. package/dist/host/create-host.d.ts +21 -0
  27. package/dist/host/create-host.js +396 -0
  28. package/dist/host/environment.d.ts +13 -0
  29. package/dist/host/environment.js +53 -0
  30. package/dist/host/http.d.ts +35 -0
  31. package/dist/host/http.js +136 -0
  32. package/dist/host/index.d.ts +6 -0
  33. package/dist/host/index.js +5 -0
  34. package/dist/host/logger.d.ts +3 -0
  35. package/dist/host/logger.js +18 -0
  36. package/dist/host/main.d.ts +3 -0
  37. package/dist/host/main.js +142 -0
  38. package/dist/index.d.ts +7 -2
  39. package/dist/index.js +4 -2
  40. package/dist/launcher/commands.d.ts +24 -0
  41. package/dist/launcher/commands.js +260 -0
  42. package/dist/launcher/ensure-config.d.ts +16 -0
  43. package/dist/launcher/ensure-config.js +79 -0
  44. package/dist/launcher/errors.d.ts +12 -0
  45. package/dist/launcher/errors.js +22 -0
  46. package/dist/launcher/host-config.d.ts +17 -0
  47. package/dist/launcher/host-config.js +64 -0
  48. package/dist/launcher/host-state.d.ts +17 -0
  49. package/dist/launcher/host-state.js +61 -0
  50. package/dist/launcher/lifecycle.d.ts +66 -0
  51. package/dist/launcher/lifecycle.js +407 -0
  52. package/dist/launcher/locks.d.ts +26 -0
  53. package/dist/launcher/locks.js +117 -0
  54. package/dist/launcher/logs.d.ts +18 -0
  55. package/dist/launcher/logs.js +117 -0
  56. package/dist/launcher/main.d.ts +2 -0
  57. package/dist/launcher/main.js +66 -0
  58. package/dist/launcher/output.d.ts +11 -0
  59. package/dist/launcher/output.js +68 -0
  60. package/dist/launcher/paths.d.ts +20 -0
  61. package/dist/launcher/paths.js +36 -0
  62. package/dist/launcher/probe.d.ts +23 -0
  63. package/dist/launcher/probe.js +72 -0
  64. package/dist/launcher/protocol.d.ts +62 -0
  65. package/dist/launcher/protocol.js +1 -0
  66. package/dist/launcher/schema-guard.d.ts +6 -0
  67. package/dist/launcher/schema-guard.js +36 -0
  68. package/dist/launcher/spawn.d.ts +22 -0
  69. package/dist/launcher/spawn.js +85 -0
  70. package/dist/launcher/status.d.ts +6 -0
  71. package/dist/launcher/status.js +65 -0
  72. package/dist/mcp/connect.d.ts +1 -0
  73. package/dist/mcp/connect.js +2 -0
  74. package/dist/mcp/pool.d.ts +3 -1
  75. package/dist/mcp/pool.js +2 -1
  76. package/dist/mcp/stdio.d.ts +2 -0
  77. package/dist/mcp/stdio.js +3 -1
  78. package/dist/model/auth-store.d.ts +1 -1
  79. package/dist/model/auth-store.js +2 -2
  80. package/dist/model/models.d.ts +4 -0
  81. package/dist/model/models.js +22 -26
  82. package/dist/model/pi-model.js +1 -1
  83. package/dist/model/settings.d.ts +5 -1
  84. package/dist/model/settings.js +6 -2
  85. package/dist/sandbox/select.d.ts +4 -3
  86. package/dist/sandbox/select.js +15 -9
  87. package/dist/sandbox/share.d.ts +23 -0
  88. package/dist/sandbox/share.js +58 -0
  89. package/dist/tenant/envelope.d.ts +10 -0
  90. package/dist/tenant/envelope.js +54 -0
  91. package/dist/tenant/ephemeral.d.ts +31 -0
  92. package/dist/tenant/ephemeral.js +156 -0
  93. package/dist/tenant/logger.d.ts +9 -0
  94. package/dist/tenant/logger.js +25 -0
  95. package/dist/tenant/migration.d.ts +16 -0
  96. package/dist/tenant/migration.js +116 -0
  97. package/dist/tenant/module.d.ts +17 -0
  98. package/dist/tenant/module.js +315 -0
  99. package/dist/tenant/paths.d.ts +24 -0
  100. package/dist/tenant/paths.js +65 -0
  101. package/dist/tenant/pool.d.ts +10 -0
  102. package/dist/tenant/pool.js +54 -0
  103. package/dist/tenant/principals.d.ts +17 -0
  104. package/dist/tenant/principals.js +44 -0
  105. package/dist/tenant/quarantine-error.d.ts +6 -0
  106. package/dist/tenant/quarantine-error.js +15 -0
  107. package/dist/tenant/quarantine.d.ts +35 -0
  108. package/dist/tenant/quarantine.js +94 -0
  109. package/dist/tenant/reset.d.ts +21 -0
  110. package/dist/tenant/reset.js +125 -0
  111. package/dist/tenant/runtime.d.ts +80 -0
  112. package/dist/tenant/runtime.js +2256 -0
  113. package/dist/tenant/schema.d.ts +23 -0
  114. package/dist/tenant/schema.js +151 -0
  115. package/dist/tenant/status.d.ts +28 -0
  116. package/dist/tenant/status.js +95 -0
  117. package/dist/tenant/store-fs.d.ts +16 -0
  118. package/dist/tenant/store-fs.js +205 -0
  119. package/dist/tenant/store-memory.d.ts +19 -0
  120. package/dist/tenant/store-memory.js +100 -0
  121. package/dist/tenant/types.d.ts +125 -0
  122. package/dist/tenant/types.js +1 -0
  123. package/dist/vault/kek.d.ts +5 -2
  124. package/dist/vault/kek.js +7 -10
  125. package/dist/version.d.ts +1 -1
  126. package/dist/version.js +1 -1
  127. package/package.json +9 -6
  128. package/dist/core/main.js +0 -35
  129. /package/dist/{core/main.d.ts → host/config.js} +0 -0
@@ -0,0 +1,35 @@
1
+ import type { Action } from "@nylorun/core/contracts";
2
+ import type { SandboxManager } from "../sandbox/manager.js";
3
+ import type { SandboxToolOutcome } from "../sandbox/tools.js";
4
+ import { type SessionSandboxRef } from "../sandbox/share.js";
5
+ export declare class SandboxRouteError extends Error {
6
+ readonly status: number;
7
+ constructor(status: number, message: string);
8
+ }
9
+ export type SandboxRouteSession = SessionSandboxRef & {
10
+ readonly activeTurnId: string | null;
11
+ readonly status: string;
12
+ };
13
+ export type SandboxRouteDeps = {
14
+ readonly sandbox: SandboxManager;
15
+ readonly session: (id: string) => SandboxRouteSession;
16
+ readonly lookup: (id: string) => SessionSandboxRef | undefined;
17
+ readonly getAction: (id: string) => Action | undefined;
18
+ };
19
+ /**
20
+ * `POST /v1/sessions/:id/sandbox/:tool` — application principal.
21
+ * Refused while the session has an active agent turn.
22
+ */
23
+ export declare function handleSessionSandboxTool(deps: SandboxRouteDeps, sessionId: string, toolName: string, body: unknown, signal: AbortSignal): Promise<SandboxToolOutcome>;
24
+ /**
25
+ * `POST /v1/actions/:id/sandbox/:tool` — executor, authorized by the live claim.
26
+ * Body must include claimId and generation matching the claimed action.
27
+ */
28
+ export declare function handleActionSandboxTool(deps: SandboxRouteDeps, actionId: string, toolName: string, body: unknown, signal: AbortSignal): Promise<SandboxToolOutcome>;
29
+ /** Validate PutSession.sandbox attach: same owner, identical specs. */
30
+ export declare function validateSandboxAttach(body: {
31
+ ownerUserId: string;
32
+ sandbox?: {
33
+ session: string;
34
+ };
35
+ }, newManifest: SessionSandboxRef["manifest"], lookup: (id: string) => SessionSandboxRef | undefined): string | undefined;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * HTTP handlers for session-scoped and claim-scoped sandbox tool endpoints.
3
+ * Wired from TenantRuntime; keeps route bodies out of the hot runtime file.
4
+ */
5
+ import { isSandboxToolName, } from "@nylorun/core/define";
6
+ import { capabilityForSandbox, owningSandboxSessionId, sandboxSpecOf, sandboxSpecsEqual, } from "../sandbox/share.js";
7
+ export class SandboxRouteError extends Error {
8
+ status;
9
+ constructor(status, message) {
10
+ super(message);
11
+ this.status = status;
12
+ this.name = "SandboxRouteError";
13
+ }
14
+ }
15
+ function resolveSpec(session, lookup) {
16
+ const ownerId = owningSandboxSessionId(session, lookup);
17
+ const owner = ownerId === session.id ? session : lookup(ownerId) ?? session;
18
+ const spec = sandboxSpecOf(owner.manifest) ?? sandboxSpecOf(session.manifest);
19
+ if (!spec)
20
+ throw new SandboxRouteError(404, "Session has no sandbox");
21
+ return spec;
22
+ }
23
+ function parseTool(tool) {
24
+ if (!isSandboxToolName(tool))
25
+ throw new SandboxRouteError(400, `Unknown sandbox tool '${tool}'`);
26
+ return tool;
27
+ }
28
+ function toolInput(body) {
29
+ if (body === undefined || body === null)
30
+ return {};
31
+ if (typeof body !== "object" || Array.isArray(body))
32
+ throw new SandboxRouteError(400, "Sandbox tool body must be an object");
33
+ const record = body;
34
+ const { claimId: _c, generation: _g, requestId: _r, ...input } = record;
35
+ return input;
36
+ }
37
+ async function runTool(deps, session, tool, input, signal) {
38
+ const spec = resolveSpec(session, deps.lookup);
39
+ const ownerId = owningSandboxSessionId(session, deps.lookup);
40
+ const ref = {
41
+ id: ownerId,
42
+ activeTurnId: session.activeTurnId,
43
+ manifest: session.manifest,
44
+ };
45
+ return deps.sandbox.run(ref, capabilityForSandbox(spec), tool, input, signal);
46
+ }
47
+ /**
48
+ * `POST /v1/sessions/:id/sandbox/:tool` — application principal.
49
+ * Refused while the session has an active agent turn.
50
+ */
51
+ export async function handleSessionSandboxTool(deps, sessionId, toolName, body, signal) {
52
+ const session = deps.session(sessionId);
53
+ if (session.activeTurnId)
54
+ throw new SandboxRouteError(409, "Sandbox is unavailable while the session has an active agent turn");
55
+ const tool = parseTool(toolName);
56
+ return runTool(deps, session, tool, toolInput(body), signal);
57
+ }
58
+ /**
59
+ * `POST /v1/actions/:id/sandbox/:tool` — executor, authorized by the live claim.
60
+ * Body must include claimId and generation matching the claimed action.
61
+ */
62
+ export async function handleActionSandboxTool(deps, actionId, toolName, body, signal) {
63
+ if (!body || typeof body !== "object" || Array.isArray(body))
64
+ throw new SandboxRouteError(400, "Sandbox tool body must be an object");
65
+ const record = body;
66
+ const claimId = record.claimId;
67
+ const generation = record.generation;
68
+ if (typeof claimId !== "string" || claimId.length === 0)
69
+ throw new SandboxRouteError(400, "claimId is required");
70
+ if (typeof generation !== "number" || !Number.isInteger(generation) || generation < 1)
71
+ throw new SandboxRouteError(400, "generation must be a positive integer");
72
+ const action = deps.getAction(actionId);
73
+ if (!action)
74
+ throw new SandboxRouteError(404, "Action not found");
75
+ if (action.status !== "claimed" ||
76
+ action.claimId !== claimId ||
77
+ action.generation !== generation ||
78
+ !action.leaseExpiresAt ||
79
+ Date.parse(action.leaseExpiresAt) <= Date.now())
80
+ throw new SandboxRouteError(409, "Stale or expired claim");
81
+ const session = deps.session(action.sessionId);
82
+ if (session.activeTurnId !== action.turnId)
83
+ throw new SandboxRouteError(409, "Action unavailable");
84
+ const tool = parseTool(toolName);
85
+ return runTool(deps, session, tool, toolInput(body), signal);
86
+ }
87
+ /** Validate PutSession.sandbox attach: same owner, identical specs. */
88
+ export function validateSandboxAttach(body, newManifest, lookup) {
89
+ const share = body.sandbox?.session;
90
+ if (!share)
91
+ return undefined;
92
+ const owner = lookup(share);
93
+ if (!owner)
94
+ throw new SandboxRouteError(404, "Sandbox session not found");
95
+ if (owner.ownerUserId !== body.ownerUserId)
96
+ throw new SandboxRouteError(403, "Sandbox session must belong to the same owner");
97
+ const ownerSpec = sandboxSpecOf(owner.manifest);
98
+ const newSpec = sandboxSpecOf(newManifest);
99
+ if (!ownerSpec)
100
+ throw new SandboxRouteError(400, "Target session has no sandbox to share");
101
+ if (!newSpec)
102
+ throw new SandboxRouteError(400, "Session must declare a sandbox identical to the shared session");
103
+ if (!sandboxSpecsEqual(ownerSpec, newSpec))
104
+ throw new SandboxRouteError(409, "Sandbox specs must be identical to share a sandbox");
105
+ return owningSandboxSessionId(owner, lookup);
106
+ }
@@ -6,12 +6,14 @@ export interface ExecutorRow {
6
6
  tokenHash: string;
7
7
  implementationVersion: string;
8
8
  manifestHash?: string;
9
+ principalId?: string;
9
10
  createdAt: string;
10
11
  updatedAt: string;
11
12
  }
12
13
  export declare class Store {
14
+ private readonly tenantId;
13
15
  readonly db: DatabaseSync;
14
- constructor(path: string);
16
+ constructor(path: string, tenantId: string);
15
17
  tx<T>(fn: () => T): T;
16
18
  get<T = any>(table: string, id: string): T | undefined;
17
19
  all<T = any>(table: string): T[];
@@ -1,5 +1,6 @@
1
1
  import { DatabaseSync } from "node:sqlite";
2
2
  import { randomUUID } from "node:crypto";
3
+ import { migrateTenantDatabase } from "../tenant/schema.js";
3
4
  export function canonical(value) {
4
5
  if (Array.isArray(value))
5
6
  return "[" + value.map(canonical).join(",") + "]";
@@ -14,69 +15,13 @@ export function canonical(value) {
14
15
  return JSON.stringify(value) ?? "null";
15
16
  }
16
17
  export class Store {
18
+ tenantId;
17
19
  db;
18
- constructor(path) {
20
+ constructor(path, tenantId) {
21
+ this.tenantId = tenantId;
19
22
  this.db = new DatabaseSync(path);
20
- this.db
21
- .exec(`PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000;
22
- CREATE TABLE IF NOT EXISTS definitions(id TEXT PRIMARY KEY, body TEXT NOT NULL);
23
- CREATE TABLE IF NOT EXISTS sessions(id TEXT PRIMARY KEY, body TEXT NOT NULL);
24
- CREATE TABLE IF NOT EXISTS commands(id TEXT PRIMARY KEY, body TEXT NOT NULL);
25
- CREATE TABLE IF NOT EXISTS checkpoints(id TEXT PRIMARY KEY, body TEXT NOT NULL);
26
- CREATE TABLE IF NOT EXISTS effects(id TEXT PRIMARY KEY, body TEXT NOT NULL);
27
- CREATE TABLE IF NOT EXISTS actions(id TEXT PRIMARY KEY, body TEXT NOT NULL);
28
- CREATE TABLE IF NOT EXISTS sandboxes(id TEXT PRIMARY KEY, body TEXT NOT NULL);
29
- CREATE TABLE IF NOT EXISTS events(sequence INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, body TEXT NOT NULL);
30
- CREATE INDEX IF NOT EXISTS events_session ON events(session_id, sequence);
31
- CREATE TABLE IF NOT EXISTS vaults(
32
- id TEXT PRIMARY KEY,
33
- name TEXT NOT NULL,
34
- owner_user_id TEXT NOT NULL,
35
- metadata_json TEXT,
36
- created_at TEXT NOT NULL,
37
- scope TEXT NOT NULL DEFAULT 'user'
38
- );
39
- CREATE INDEX IF NOT EXISTS vaults_owner ON vaults(owner_user_id);
40
- CREATE TABLE IF NOT EXISTS vault_credentials(
41
- id TEXT PRIMARY KEY,
42
- vault_id TEXT NOT NULL REFERENCES vaults(id) ON DELETE CASCADE,
43
- name TEXT NOT NULL,
44
- type TEXT NOT NULL,
45
- binding_json TEXT NOT NULL,
46
- expires_at TEXT,
47
- created_at TEXT NOT NULL,
48
- rotated_at TEXT,
49
- kek_id TEXT NOT NULL,
50
- nonce BLOB NOT NULL,
51
- ciphertext BLOB NOT NULL,
52
- wrapped_dek BLOB NOT NULL
53
- );
54
- CREATE INDEX IF NOT EXISTS vault_credentials_vault ON vault_credentials(vault_id);
55
- CREATE TABLE IF NOT EXISTS vault_audit(
56
- id TEXT PRIMARY KEY,
57
- at TEXT NOT NULL,
58
- actor TEXT NOT NULL,
59
- action TEXT NOT NULL,
60
- vault_id TEXT,
61
- credential_id TEXT,
62
- session_id TEXT,
63
- target TEXT,
64
- outcome TEXT NOT NULL
65
- );
66
- CREATE TABLE IF NOT EXISTS vault_idempotency(
67
- id TEXT PRIMARY KEY,
68
- body_hash TEXT NOT NULL,
69
- response TEXT NOT NULL
70
- );
71
- CREATE TABLE IF NOT EXISTS executors(
72
- agent_id TEXT PRIMARY KEY,
73
- token_hash TEXT NOT NULL UNIQUE,
74
- implementation_version TEXT NOT NULL,
75
- manifest_hash TEXT,
76
- created_at TEXT NOT NULL,
77
- updated_at TEXT NOT NULL
78
- );`);
79
- migrateVaultScope(this.db);
23
+ this.db.exec(`PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000;`);
24
+ migrateTenantDatabase(this.db);
80
25
  }
81
26
  tx(fn) {
82
27
  this.db.exec("BEGIN IMMEDIATE");
@@ -112,27 +57,29 @@ export class Store {
112
57
  // token hash instead of flowing through the generic JSON document helpers above.
113
58
  allExecutors() {
114
59
  return this.db
115
- .prepare("SELECT agent_id,token_hash,implementation_version,manifest_hash,created_at,updated_at FROM executors")
60
+ .prepare("SELECT agent_id,token_hash,implementation_version,manifest_hash,principal_id,created_at,updated_at FROM executors")
116
61
  .all()
117
62
  .map((r) => ({
118
63
  agentId: String(r.agent_id),
119
64
  tokenHash: String(r.token_hash),
120
65
  implementationVersion: String(r.implementation_version),
121
66
  manifestHash: r.manifest_hash == null ? undefined : String(r.manifest_hash),
67
+ principalId: r.principal_id == null ? undefined : String(r.principal_id),
122
68
  createdAt: String(r.created_at),
123
69
  updatedAt: String(r.updated_at),
124
70
  }));
125
71
  }
126
72
  putExecutor(row) {
127
73
  this.db
128
- .prepare(`INSERT INTO executors(agent_id,token_hash,implementation_version,manifest_hash,created_at,updated_at)
129
- VALUES(?,?,?,?,?,?)
74
+ .prepare(`INSERT INTO executors(agent_id,token_hash,implementation_version,manifest_hash,principal_id,created_at,updated_at)
75
+ VALUES(?,?,?,?,?,?,?)
130
76
  ON CONFLICT(agent_id) DO UPDATE SET
131
77
  token_hash=excluded.token_hash,
132
78
  implementation_version=excluded.implementation_version,
133
79
  manifest_hash=excluded.manifest_hash,
80
+ principal_id=excluded.principal_id,
134
81
  updated_at=excluded.updated_at`)
135
- .run(row.agentId, row.tokenHash, row.implementationVersion, row.manifestHash ?? null, row.updatedAt, row.updatedAt);
82
+ .run(row.agentId, row.tokenHash, row.implementationVersion, row.manifestHash ?? null, row.principalId ?? null, row.updatedAt, row.updatedAt);
136
83
  }
137
84
  deleteExecutor(agentId) {
138
85
  this.db.prepare("DELETE FROM executors WHERE agent_id=?").run(agentId);
@@ -151,6 +98,7 @@ export class Store {
151
98
  .run(sessionId, "{}");
152
99
  const event = {
153
100
  ...base,
101
+ tenantId: this.tenantId,
154
102
  cursor: Buffer.from(`${sessionId}:${result.lastInsertRowid}`).toString("base64url"),
155
103
  };
156
104
  this.db
@@ -190,13 +138,6 @@ export class Store {
190
138
  }
191
139
  }
192
140
  function belongsTo(event, agent) {
193
- const ref = event.payload
194
- ?.agent;
141
+ const ref = event.payload?.agent;
195
142
  return ref?.delegationId === agent || ref?.path === agent;
196
143
  }
197
- function migrateVaultScope(db) {
198
- const columns = db.prepare("PRAGMA table_info(vaults)").all();
199
- if (!columns.some((column) => column.name === "scope"))
200
- db.exec("ALTER TABLE vaults ADD COLUMN scope TEXT NOT NULL DEFAULT 'user'");
201
- db.exec("CREATE UNIQUE INDEX IF NOT EXISTS vaults_one_host ON vaults(scope) WHERE scope = 'host'");
202
- }
@@ -0,0 +1,50 @@
1
+ import { type AgentManifest } from "@nylorun/core/contracts";
2
+ export type TurnManifestStore = {
3
+ /** Look up a previously pinned turn variant by hash. */
4
+ get(hash: string): AgentManifest | undefined;
5
+ /** Persist a validated turn variant for replay / Studio. */
6
+ put(hash: string, manifest: AgentManifest): void;
7
+ };
8
+ export type TurnManifestOk = {
9
+ readonly ok: true;
10
+ readonly manifest: AgentManifest;
11
+ readonly hash: string;
12
+ };
13
+ export type TurnManifestErr = {
14
+ readonly ok: false;
15
+ readonly code: "loop.invalid-agent";
16
+ readonly message: string;
17
+ };
18
+ /**
19
+ * Parse `message.manifest`, require it to be a variant of the session pin,
20
+ * hash it, and store it. Applies to that turn only (caller must not latch).
21
+ */
22
+ export declare function pinTurnManifest(args: {
23
+ readonly candidate: unknown;
24
+ readonly pinned: AgentManifest;
25
+ readonly store: TurnManifestStore;
26
+ }): TurnManifestOk | TurnManifestErr;
27
+ /** Resolve the manifest a turn runs with: optional message field, else the pin. */
28
+ export declare function resolveMessageManifest(args: {
29
+ readonly pinned: AgentManifest;
30
+ readonly pinnedHash: string;
31
+ readonly messageManifest?: unknown;
32
+ readonly store: TurnManifestStore;
33
+ }): TurnManifestOk | TurnManifestErr;
34
+ /**
35
+ * At turn start: if state's hash differs from the turn's, set `state.manifestHash`
36
+ * to the turn's when both are the pin or validated variants (`loops.md` §4.2).
37
+ * Leaves state unchanged when either hash is unrelated (runDurable still rejects).
38
+ */
39
+ export declare function rebaseTurnState<S extends {
40
+ manifestHash?: string;
41
+ }>(args: {
42
+ readonly state: S | undefined;
43
+ readonly turnManifestHash: string;
44
+ readonly isAllowedHash: (hash: string) => boolean;
45
+ }): S | undefined;
46
+ /** Allowed hashes: the session pin plus every stored turn variant. */
47
+ export declare function allowedManifestHashes(args: {
48
+ readonly pinnedHash: string;
49
+ readonly variantHashes: Iterable<string>;
50
+ }): Set<string>;
@@ -0,0 +1,66 @@
1
+ import { AgentManifestSchema, } from "@nylorun/core/contracts";
2
+ import { hashManifest } from "@nylorun/core/compatibility";
3
+ import { isVariantOf } from "@nylorun/core/define";
4
+ /**
5
+ * Parse `message.manifest`, require it to be a variant of the session pin,
6
+ * hash it, and store it. Applies to that turn only (caller must not latch).
7
+ */
8
+ export function pinTurnManifest(args) {
9
+ const parsed = AgentManifestSchema.safeParse(args.candidate);
10
+ if (!parsed.success) {
11
+ return {
12
+ ok: false,
13
+ code: "loop.invalid-agent",
14
+ message: "Turn manifest failed AgentManifestSchema validation",
15
+ };
16
+ }
17
+ const manifest = parsed.data;
18
+ if (!isVariantOf(manifest, args.pinned)) {
19
+ return {
20
+ ok: false,
21
+ code: "loop.invalid-agent",
22
+ message: "Turn manifest is not a variant of the session's pinned manifest",
23
+ };
24
+ }
25
+ const hash = hashManifest(manifest);
26
+ args.store.put(hash, manifest);
27
+ return { ok: true, manifest, hash };
28
+ }
29
+ /** Resolve the manifest a turn runs with: optional message field, else the pin. */
30
+ export function resolveMessageManifest(args) {
31
+ if (args.messageManifest === undefined) {
32
+ return {
33
+ ok: true,
34
+ manifest: args.pinned,
35
+ hash: args.pinnedHash,
36
+ };
37
+ }
38
+ return pinTurnManifest({
39
+ candidate: args.messageManifest,
40
+ pinned: args.pinned,
41
+ store: args.store,
42
+ });
43
+ }
44
+ /**
45
+ * At turn start: if state's hash differs from the turn's, set `state.manifestHash`
46
+ * to the turn's when both are the pin or validated variants (`loops.md` §4.2).
47
+ * Leaves state unchanged when either hash is unrelated (runDurable still rejects).
48
+ */
49
+ export function rebaseTurnState(args) {
50
+ const state = args.state;
51
+ if (!state?.manifestHash)
52
+ return state;
53
+ if (state.manifestHash === args.turnManifestHash)
54
+ return state;
55
+ if (!args.isAllowedHash(state.manifestHash) ||
56
+ !args.isAllowedHash(args.turnManifestHash))
57
+ return state;
58
+ return { ...state, manifestHash: args.turnManifestHash };
59
+ }
60
+ /** Allowed hashes: the session pin plus every stored turn variant. */
61
+ export function allowedManifestHashes(args) {
62
+ const set = new Set([args.pinnedHash]);
63
+ for (const hash of args.variantHashes)
64
+ set.add(hash);
65
+ return set;
66
+ }
@@ -0,0 +1,16 @@
1
+ import type { HostConfigFile } from "./config.js";
2
+ import type { Logger, TenantConfig } from "../tenant/types.js";
3
+ /**
4
+ * Builds a TenantConfig for `openTenantRuntime`.
5
+ * Reads seeded sandbox backend from the Tenant database when present (I1).
6
+ */
7
+ export declare function configForFactory(options: {
8
+ hostRoot: string;
9
+ hostConfig: HostConfigFile;
10
+ logger: Logger;
11
+ /** Allowlisted baseline from `baselineEnvironment` (built in `host/main.ts`). */
12
+ baseline: Readonly<Record<string, string>>;
13
+ mode?: TenantConfig["mode"];
14
+ /** Override model; default vault. Fixture/scripted require ephemeral/test mode. */
15
+ model?: TenantConfig["model"];
16
+ }): (id: string) => TenantConfig;
@@ -0,0 +1,45 @@
1
+ import { existsSync } from "node:fs";
2
+ import { DatabaseSync } from "node:sqlite";
3
+ import { tenantPaths } from "../tenant/paths.js";
4
+ import { readTenantConfig } from "../tenant/schema.js";
5
+ import { tenantChildEnvironment } from "./environment.js";
6
+ /**
7
+ * Builds a TenantConfig for `openTenantRuntime`.
8
+ * Reads seeded sandbox backend from the Tenant database when present (I1).
9
+ */
10
+ export function configForFactory(options) {
11
+ const { baseline } = options;
12
+ return (id) => {
13
+ const tenant = tenantPaths(options.hostRoot, id);
14
+ let sandboxBackend = "auto";
15
+ if (existsSync(tenant.database)) {
16
+ try {
17
+ const db = new DatabaseSync(tenant.database, { readOnly: true });
18
+ try {
19
+ const seeded = readTenantConfig(db).sandboxBackend;
20
+ if (seeded)
21
+ sandboxBackend = seeded;
22
+ }
23
+ finally {
24
+ db.close();
25
+ }
26
+ }
27
+ catch {
28
+ /* unreadable — keep default */
29
+ }
30
+ }
31
+ return {
32
+ tenantId: id,
33
+ mode: options.mode ?? "shared",
34
+ paths: tenant,
35
+ sandbox: { backend: sandboxBackend },
36
+ model: options.model ?? { kind: "vault" },
37
+ childEnv: tenantChildEnvironment(baseline, options.hostConfig, tenant),
38
+ logger: {
39
+ info: (message, fields) => options.logger.info(message, { tenantId: id, ...fields }),
40
+ warn: (message, fields) => options.logger.warn(message, { tenantId: id, ...fields }),
41
+ error: (message, fields) => options.logger.error(message, { tenantId: id, ...fields }),
42
+ },
43
+ };
44
+ };
45
+ }
@@ -0,0 +1,24 @@
1
+ /** host.json — durable, no secrets */
2
+ export interface HostConfigFile {
3
+ hostId: string;
4
+ host: string;
5
+ port: number;
6
+ allowNonLoopback?: boolean;
7
+ proxy?: {
8
+ httpsProxy?: string;
9
+ noProxy?: string;
10
+ nodeExtraCaCerts?: string;
11
+ };
12
+ }
13
+ /** host-state.json — process only */
14
+ export interface HostStateFile {
15
+ pid: number;
16
+ startedAt: string;
17
+ version: string;
18
+ entry: string;
19
+ url: string;
20
+ }
21
+ /** host-credentials.json, mode 0600 */
22
+ export interface HostCredentialsFile {
23
+ adminKey: string;
24
+ }
@@ -0,0 +1,21 @@
1
+ import type { Logger, TenantModule } from "../tenant/types.js";
2
+ import type { HostConfigFile, HostCredentialsFile } from "./config.js";
3
+ export interface CreateHostOptions {
4
+ hostRoot: string;
5
+ module: TenantModule;
6
+ config: HostConfigFile;
7
+ credentials: HostCredentialsFile;
8
+ logger: Logger;
9
+ /** Diagnostic package version of `@nylorun/core` for `/health`. */
10
+ coreVersion: string;
11
+ /** Process id reported by `/health` and admin host status. Defaults to `process.pid`. */
12
+ pid?: number;
13
+ }
14
+ export interface HostServer {
15
+ listen(): Promise<void>;
16
+ close(): Promise<void>;
17
+ readonly url: string;
18
+ }
19
+ /** Constant-time comparison of SHA-256 digests of the presented and stored admin keys. */
20
+ export declare function adminKeyMatches(presented: string | undefined, adminKey: string): boolean;
21
+ export declare function createHost(options: CreateHostOptions): HostServer;