@ours.network/fleet 1.1.0-nightly.2 → 1.1.0-nightly.3

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.
@@ -1,7 +1,8 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { createHash } from 'node:crypto';
3
- import { ROOMS_KEYS as RK, ROOMS_OWNER_KEYS as ROK, ROOMS_COWORK_KEYS as RCK, ROOMS_DEFAULTS_KEYS as RDK, TASKS_KEYS as TK, TEMPLATE_KEYS as TPK, TEMPLATE_MEMBER_KEYS as TMK, TEMPLATE_OVERRIDE_KEYS as TOK, } from './types.js';
3
+ import { ROOMS_KEYS as RK, ROOMS_OWNER_KEYS as ROK, ROOMS_COWORK_KEYS as RCK, ROOMS_DEFAULTS_KEYS as RDK, TASKS_KEYS as TK, TEMPLATE_KEYS as TPK, TEMPLATE_MEMBER_KEYS as TMK, } from './types.js';
4
4
  import { BUILTIN_TEMPLATES } from './templates.js';
5
+ import { isSensitiveConfigKey } from '../sensitive-config.js';
5
6
  const isPlainObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
6
7
  const CID_RE = /^[0-9a-fA-F]{64}$/;
7
8
  export class RoomsTasksConfigError extends Error {
@@ -136,19 +137,33 @@ export function validateRoomTemplatesConfig(raw, path) {
136
137
  throw new RoomsTasksConfigError(path, `room_templates.${name}.members[${i}].role: required string`);
137
138
  if (!Number.isInteger(m.count) || m.count < 1)
138
139
  throw new RoomsTasksConfigError(path, `room_templates.${name}.members[${i}].count: required positive integer`);
139
- if (!m.role_ref || typeof m.role_ref !== 'string')
140
- throw new RoomsTasksConfigError(path, `room_templates.${name}.members[${i}].role_ref: required string`);
141
- if (m.overrides !== undefined) {
142
- if (!isPlainObject(m.overrides))
143
- throw new RoomsTasksConfigError(path, `room_templates.${name}.members[${i}].overrides: must be a mapping`);
144
- rejectUnknown(m.overrides, TOK, path, `room_templates.${name}.members[${i}].overrides`);
140
+ if (!isPlainObject(m.agent))
141
+ throw new RoomsTasksConfigError(path, `room_templates.${name}.members[${i}].agent: required mapping`);
142
+ const agentKeys = Object.keys(m.agent);
143
+ const isRef = agentKeys.length === 1 && typeof m.agent.ref === 'string';
144
+ const isDefinition = agentKeys.includes('role') && agentKeys.includes('brain');
145
+ if (!isRef && !isDefinition)
146
+ throw new RoomsTasksConfigError(path, `room_templates.${name}.members[${i}].agent: must be {ref} or canonical {role, brain, ...}`);
147
+ const containsSensitive = (value) => Array.isArray(value)
148
+ ? value.some(containsSensitive)
149
+ : Boolean(value && typeof value === 'object' && Object.entries(value)
150
+ .some(([key, child]) => isSensitiveConfigKey(key) || containsSensitive(child)));
151
+ if (isDefinition && containsSensitive(m.agent))
152
+ throw new RoomsTasksConfigError(path, `room_templates.${name}.members[${i}].agent: inline sensitive configuration cannot be persisted; use references`);
153
+ if (isDefinition) {
154
+ const definition = m.agent;
155
+ const brain = isPlainObject(definition.brain) && isPlainObject(definition.brain.inline)
156
+ ? definition.brain.inline : undefined;
157
+ if (definition.env !== undefined || definition.owner_channel !== undefined
158
+ || definition.auth_proxy !== undefined || brain?.harness_options !== undefined
159
+ || brain?.session_options !== undefined)
160
+ throw new RoomsTasksConfigError(path, `room_templates.${name}.members[${i}].agent: secret-capable inline fields cannot be persisted; use references`);
145
161
  }
146
162
  return {
147
163
  slot: m.slot,
148
164
  role: m.role,
149
165
  count: m.count,
150
- role_ref: m.role_ref,
151
- overrides: m.overrides,
166
+ agent: structuredClone(m.agent),
152
167
  };
153
168
  });
154
169
  let room;
@@ -1,10 +1,10 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { existsSync, readFileSync, realpathSync } from 'node:fs';
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
3
3
  import { parse } from 'yaml';
4
4
  import { advanceSaga, setSagaError, updateMemberSeats, updateMemberStartup, activateRoom, getRoomRecord, } from './room-state.js';
5
5
  import { activateTask, updateTaskMembers, blockTask, unblockTask, getTask, } from './task-state.js';
6
6
  import { spawnTemp } from '../spawn.js';
7
- import { findRole } from '../config.js';
7
+ import { findRole, loadConfig } from '../config.js';
8
8
  import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, } from '../fleet-proxy.js';
9
9
  import { controlRequest } from '../session/control.js';
10
10
  import { SessionControlError } from '../session/types.js';
@@ -21,6 +21,26 @@ export function getBinPath() {
21
21
  return process.argv[1];
22
22
  }
23
23
  }
24
+ function canonicalJson(value) {
25
+ if (Array.isArray(value))
26
+ return `[${value.map(canonicalJson).join(',')}]`;
27
+ if (value && typeof value === 'object')
28
+ return `{${Object.entries(value)
29
+ .sort(([a], [b]) => a.localeCompare(b))
30
+ .map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(',')}}`;
31
+ return JSON.stringify(value);
32
+ }
33
+ function launchDefinition(definition) {
34
+ const selectionEvidence = (selection) => 'ref' in selection ? { kind: 'ref', id: selection.ref } : { kind: 'inline' };
35
+ return {
36
+ projection: {
37
+ brain: selectionEvidence(definition.brain), role: selectionEvidence(definition.role),
38
+ operationalFields: Object.keys(definition)
39
+ .filter(key => key !== 'brain' && key !== 'role').sort(),
40
+ },
41
+ fingerprint: createHash('sha256').update(canonicalJson(definition)).digest('hex'),
42
+ };
43
+ }
24
44
  async function spawnRoomMember(options, binPath) {
25
45
  const stateDir = process.env[FLEET_PROXY_STATE_DIR_ENV];
26
46
  if (!stateDir)
@@ -52,28 +72,31 @@ function expandMembers(template, prefix) {
52
72
  name: `${prefix}-${slot.slot}-${i}`,
53
73
  slot: slot.slot,
54
74
  coworkRole: slot.role,
55
- roleRef: slot.role_ref,
56
- overrides: slot.overrides,
75
+ agent: structuredClone(slot.agent),
57
76
  });
58
77
  }
59
78
  }
60
79
  return result;
61
80
  }
62
81
  function settingsFor(member, cfg) {
63
- let refRole;
64
- try {
65
- refRole = findRole(cfg, member.roleRef);
82
+ if (!('ref' in member.agent)) {
83
+ const role = member.agent.role;
84
+ if (cfg.files[0] && existsSync(cfg.files[0]) && statSync(cfg.files[0]).isFile())
85
+ loadConfig(cfg.files[0], { additionalAgent: {
86
+ id: `RoomPreflight${member.name.replace(/[^A-Za-z0-9_-]/g, '')}`.slice(0, 64),
87
+ definition: member.agent,
88
+ } });
89
+ return { definition: structuredClone(member.agent),
90
+ ...('inline' in role && typeof role.inline.persona === 'string'
91
+ ? { persona: role.inline.persona } : {}) };
66
92
  }
67
- catch { /* no ref role */ }
68
- const permissions = member.overrides?.permissions;
93
+ const refRole = findRole(cfg, member.agent.ref);
94
+ const definition = cfg.agentDefinitions?.[member.agent.ref];
95
+ if (!definition)
96
+ throw new Error(`Agent '${member.agent.ref}' has no canonical definition`);
69
97
  return {
70
- model: member.overrides?.model ?? refRole?.model,
71
- harness: member.overrides?.harness ?? refRole?.harness,
72
- cwd: member.overrides?.cwd ?? refRole?.cwd,
73
- persona: member.overrides?.persona ?? refRole?.persona,
74
- approval: permissions?.approval,
75
- filesystem: permissions?.filesystem,
76
- unattended: permissions?.unattended,
98
+ definition: structuredClone(definition),
99
+ persona: refRole.persona,
77
100
  };
78
101
  }
79
102
  function roomTask(input, member, settings, members, roomIdentityCid, ownerSeatCid) {
@@ -104,13 +127,11 @@ function launchMatches(dir, member, actionId, taskSha, roomId, roomIdentityCid,
104
127
  const role = parse(readFileSync(`${dir}/role.yaml`, 'utf8'));
105
128
  const startup = role.roomMemberStartup;
106
129
  return role.identity === member.name
107
- && typeof role.mission === 'string'
108
- && sha256Text(role.mission) === taskSha
109
130
  && startup?.room_id === roomId
110
131
  && startup.room_identity_cid === roomIdentityCid
111
132
  && startup.identity_name === member.name
112
133
  && startup.role === member.coworkRole
113
- && startup.task === role.mission
134
+ && sha256Text(startup.task ?? '') === taskSha
114
135
  && (expectedInviteId === undefined || startup.invite_id === expectedInviteId)
115
136
  && typeof startup.invite === 'string'
116
137
  && startup.invite.length > 0;
@@ -199,10 +220,13 @@ async function launchMember(input) {
199
220
  let effectiveActionId = actionId;
200
221
  const attempt = (seat.launch?.attempt ?? 0) + 1;
201
222
  const taskSha = sha256Text(startup.task);
223
+ const effectiveAgentDefinition = structuredClone(settings.definition);
224
+ const { projection: agentDefinition, fingerprint: agentFingerprint } = launchDefinition(effectiveAgentDefinition);
202
225
  const proxyCaller = process.env[FLEET_PROXY_STATE_DIR_ENV]
203
226
  ? process.env[FLEET_PROXY_CALLER_ENV] : undefined;
204
227
  updateMemberStartup(provision.roomId, member.name, { launch: {
205
228
  state: 'intent', attempt, action_id: actionId, mission_sha256: taskSha,
229
+ agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
206
230
  ...(proxyCaller ? { caller_role: proxyCaller } : {}),
207
231
  updated_at: new Date().toISOString(),
208
232
  } });
@@ -211,13 +235,7 @@ async function launchMember(input) {
211
235
  name: member.name,
212
236
  temp: true,
213
237
  identity: member.name,
214
- mission: startup.task,
215
- model: settings.model,
216
- harness: settings.harness,
217
- cwd: settings.cwd,
218
- approval: settings.approval,
219
- filesystem: settings.filesystem,
220
- unattended: settings.unattended,
238
+ agentDefinition: settings.definition,
221
239
  surface: 'agent',
222
240
  creationActionId: actionId,
223
241
  roomMemberStartup: startup,
@@ -227,7 +245,8 @@ async function launchMember(input) {
227
245
  if (launched.creationActionId !== actionId) {
228
246
  updateMemberStartup(provision.roomId, member.name, { launch: {
229
247
  state: 'intent', attempt, action_id: launched.creationActionId,
230
- mission_sha256: taskSha, updated_at: new Date().toISOString(),
248
+ mission_sha256: taskSha, agent_definition: agentDefinition,
249
+ agent_fingerprint: agentFingerprint, updated_at: new Date().toISOString(),
231
250
  ...(launched.callerRole ? { caller_role: launched.callerRole } : {}),
232
251
  } });
233
252
  }
@@ -237,6 +256,7 @@ async function launchMember(input) {
237
256
  }
238
257
  updateMemberStartup(provision.roomId, member.name, { launch: {
239
258
  state: 'launched', attempt, action_id: launched.creationActionId, mission_sha256: taskSha,
259
+ agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
240
260
  ...(launched.callerRole ? { caller_role: launched.callerRole } : {}),
241
261
  launch_id: supervisor.launchId, updated_at: new Date().toISOString(),
242
262
  } });
@@ -244,6 +264,7 @@ async function launchMember(input) {
244
264
  catch (error) {
245
265
  updateMemberStartup(provision.roomId, member.name, { launch: {
246
266
  state: 'failed', attempt, action_id: effectiveActionId, mission_sha256: taskSha,
267
+ agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
247
268
  ...(proxyCaller ? { caller_role: proxyCaller } : {}),
248
269
  updated_at: new Date().toISOString(),
249
270
  error: error instanceof Error ? error.message : String(error),
@@ -289,6 +310,8 @@ export async function provisionMembers(input) {
289
310
  const { cfg, cowork, roomId, taskId, template } = input;
290
311
  const prefix = taskId ? shortId(taskId) : `room-${shortId(roomId)}`;
291
312
  const members = expandMembers(template, prefix);
313
+ // Resolve every Agent before persisting launch intent or touching Cowork membership.
314
+ const settings = new Map(members.map(member => [member.name, settingsFor(member, cfg)]));
292
315
  const existing = getRoomRecord(roomId);
293
316
  if (!existing?.room_identity_cid)
294
317
  throw new Error(`room ${roomId} has no pinned room identity CID`);
@@ -299,15 +322,27 @@ export async function provisionMembers(input) {
299
322
  && members.every(member => persistedNames.has(member.name));
300
323
  if (!resuming) {
301
324
  advanceSaga(roomId, 'create_members', 3);
302
- updateMemberSeats(roomId, members.map(member => ({
303
- role_name: member.name,
304
- slot: member.slot,
305
- cowork_role: member.coworkRole,
306
- seat_state: 'pending',
307
- launch: { state: 'pending', attempt: 0, updated_at: new Date().toISOString() },
308
- })));
325
+ updateMemberSeats(roomId, members.map(member => {
326
+ const evidence = launchDefinition(settings.get(member.name).definition);
327
+ return ({
328
+ role_name: member.name,
329
+ slot: member.slot,
330
+ cowork_role: member.coworkRole,
331
+ seat_state: 'pending',
332
+ launch: { state: 'pending', attempt: 0,
333
+ agent_definition: evidence.projection, agent_fingerprint: evidence.fingerprint,
334
+ updated_at: new Date().toISOString() },
335
+ });
336
+ }));
337
+ }
338
+ else {
339
+ for (const member of members) {
340
+ const seat = existing.member_seats.find(candidate => candidate.role_name === member.name);
341
+ const evidence = launchDefinition(settings.get(member.name).definition);
342
+ if (!seat.launch?.agent_fingerprint || seat.launch.agent_fingerprint !== evidence.fingerprint)
343
+ throw new Error(`Agent definition drift for ${member.name}; durable launch intent does not match current configuration`);
344
+ }
309
345
  }
310
- const settings = new Map(members.map(member => [member.name, settingsFor(member, cfg)]));
311
346
  const tasks = new Map(members.map(member => [member.name, roomTask(input, member, settings.get(member.name), members, roomIdentityCid, ownerSeatCid)]));
312
347
  const policy = {
313
348
  timeoutMs: input.startupWait?.timeoutMs ?? 60_000,
@@ -12,9 +12,9 @@ const TEAM = {
12
12
  'Completion requires tester sign-off.',
13
13
  ].join('\n'),
14
14
  members: [
15
- { slot: 'architect', role: 'Architect', count: 1, role_ref: 'Architect' },
16
- { slot: 'developer', role: 'Developer', count: 1, role_ref: 'Developer' },
17
- { slot: 'tester', role: 'Tester', count: 1, role_ref: 'Tester' },
15
+ { slot: 'architect', role: 'Architect', count: 1, agent: { ref: 'Architect' } },
16
+ { slot: 'developer', role: 'Developer', count: 1, agent: { ref: 'Developer' } },
17
+ { slot: 'tester', role: 'Tester', count: 1, agent: { ref: 'Tester' } },
18
18
  ],
19
19
  };
20
20
  const PAIR = {
@@ -30,8 +30,8 @@ const PAIR = {
30
30
  'Completion requires joint sign-off.',
31
31
  ].join('\n'),
32
32
  members: [
33
- { slot: 'secretary', role: 'Secretary', count: 1, role_ref: 'Secretary' },
34
- { slot: 'critic', role: 'Critic', count: 1, role_ref: 'Critic' },
33
+ { slot: 'secretary', role: 'Secretary', count: 1, agent: { ref: 'Secretary' } },
34
+ { slot: 'critic', role: 'Critic', count: 1, agent: { ref: 'Critic' } },
35
35
  ],
36
36
  };
37
37
  const SINGLE = {
@@ -45,7 +45,7 @@ const SINGLE = {
45
45
  'Owner reviews and approves completion.',
46
46
  ].join('\n'),
47
47
  members: [
48
- { slot: 'agent', role: 'Agent', count: 1, role_ref: 'Agent' },
48
+ { slot: 'agent', role: 'Agent', count: 1, agent: { ref: 'Agent' } },
49
49
  ],
50
50
  };
51
51
  export const BUILTIN_TEMPLATES = [TEAM, PAIR, SINGLE];
@@ -100,6 +100,9 @@ export interface RoomMemberLaunchState {
100
100
  /** Expected authenticated proxy caller while adopting a post-spawn crash. */
101
101
  caller_role?: string;
102
102
  mission_sha256?: string;
103
+ /** Redacted, deterministic effective Agent launch definition retained for retry inspection. */
104
+ agent_definition?: Record<string, unknown>;
105
+ agent_fingerprint?: string;
103
106
  launch_id?: string;
104
107
  updated_at: string;
105
108
  error?: string;
@@ -189,19 +192,10 @@ export interface TemplateMemberSlot {
189
192
  slot: string;
190
193
  role: string;
191
194
  count: number;
192
- role_ref: string;
193
- overrides?: TemplateRoleOverrides;
194
- }
195
- export interface TemplateRoleOverrides {
196
- harness?: string;
197
- model?: string;
198
- model_chain?: string[];
199
- permissions?: Record<string, unknown>;
200
- isolation?: Record<string, unknown>;
201
- cwd?: string;
202
- env?: Record<string, string>;
203
- persona?: string;
204
- mission?: string;
195
+ /** Canonical Agent definition, or stable reference to a declared Agent. */
196
+ agent: import('../config.js').AgentDefinition | {
197
+ ref: string;
198
+ };
205
199
  }
206
200
  export interface TemplateRoomConfig {
207
201
  quiet_membership?: boolean;
@@ -252,5 +246,4 @@ export declare const ROOMS_COWORK_KEYS: readonly ["config"];
252
246
  export declare const ROOMS_DEFAULTS_KEYS: readonly ["template", "attach_owner", "close_when_task_done"];
253
247
  export declare const TASKS_KEYS: readonly ["default_room_template", "create_mode", "close_room_on_done", "retain_completed_for"];
254
248
  export declare const TEMPLATE_KEYS: readonly ["version", "description", "room", "contract", "members", "override_builtin"];
255
- export declare const TEMPLATE_MEMBER_KEYS: readonly ["slot", "role", "count", "role_ref", "overrides"];
256
- export declare const TEMPLATE_OVERRIDE_KEYS: readonly ["harness", "model", "model_chain", "permissions", "isolation", "cwd", "env", "persona", "mission"];
249
+ export declare const TEMPLATE_MEMBER_KEYS: readonly ["slot", "role", "count", "agent"];
@@ -14,7 +14,4 @@ export const ROOMS_COWORK_KEYS = ['config'];
14
14
  export const ROOMS_DEFAULTS_KEYS = ['template', 'attach_owner', 'close_when_task_done'];
15
15
  export const TASKS_KEYS = ['default_room_template', 'create_mode', 'close_room_on_done', 'retain_completed_for'];
16
16
  export const TEMPLATE_KEYS = ['version', 'description', 'room', 'contract', 'members', 'override_builtin'];
17
- export const TEMPLATE_MEMBER_KEYS = ['slot', 'role', 'count', 'role_ref', 'overrides'];
18
- export const TEMPLATE_OVERRIDE_KEYS = [
19
- 'harness', 'model', 'model_chain', 'permissions', 'isolation', 'cwd', 'env', 'persona', 'mission',
20
- ];
17
+ export const TEMPLATE_MEMBER_KEYS = ['slot', 'role', 'count', 'agent'];
package/dist/spawn.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { IsolationConfig } from './isolation/types.js';
2
- import { type ApprovalMode, type FilesystemMode, type ResolvedRole, type RoleConfig, type MonitorConfig, type SessionBackendId, type UnattendedMode, type RoomMemberStartup } from './config.js';
2
+ import { type ApprovalMode, type FilesystemMode, type ResolvedRole, type MonitorConfig, type UnattendedMode, type RoomMemberStartup, type AgentDefinition, type AgentSelection } from './config.js';
3
3
  import { type OpsDeps } from './ops.js';
4
4
  import { type CreationDeps, type CreationProvenance } from './creation.js';
5
5
  import './harness/claude-code.js';
@@ -13,34 +13,18 @@ export declare let lastProvenance: CreationProvenance | undefined;
13
13
  export interface SpawnOpts {
14
14
  name: string;
15
15
  temp?: boolean;
16
- harness?: string;
17
- session?: SessionBackendId;
18
- mission?: string;
19
- missionFile?: string;
16
+ brain?: AgentSelection;
17
+ role?: AgentSelection;
18
+ /** Trusted canonical definition used by room provisioning; never a second schema. */
19
+ agentDefinition?: AgentDefinition;
20
20
  identity?: string;
21
21
  cwd?: string;
22
22
  coordinator?: string;
23
- /** null explicitly selects the harness default; undefined retains normal fleet inheritance. */
24
- model?: string | null;
25
- permissionMode?: string;
26
23
  approval?: ApprovalMode;
27
24
  filesystem?: FilesystemMode;
28
25
  unattended?: UnattendedMode;
29
- sandbox?: string;
30
- profile?: string;
31
- launcher?: string;
32
- search?: boolean;
33
- codexConfig?: Record<string, string | number | boolean>;
34
- reasoningEffort?: string | null;
35
- addDirs?: string[];
36
- monitor?: boolean;
37
26
  /** Typed external monitor configuration used by trusted creation surfaces. */
38
27
  monitorConfig?: Partial<MonitorConfig>;
39
- bioFile?: string;
40
- personaFile?: string;
41
- /** Inline profile values for trusted typed callers such as the local web service. */
42
- bio?: string;
43
- persona?: string;
44
28
  /** Internal, non-sensitive provenance correlation for typed presentation layers. */
45
29
  surface?: 'cli' | 'web' | 'agent';
46
30
  creationActionId?: string;
@@ -61,12 +45,7 @@ export interface SpawnOpts {
61
45
  dryRun?: boolean;
62
46
  json?: boolean;
63
47
  }
64
- export declare function profileValues(o: SpawnOpts): {
65
- bio?: string;
66
- persona?: string;
67
- };
68
- /** Pure option-to-role mapping shared by CLI and application services. */
69
- export declare function buildRoleConfig(o: SpawnOpts, defaultHarness?: string): RoleConfig;
48
+ export declare function agentDefinitionFromSpawn(o: SpawnOpts): AgentDefinition;
70
49
  /**
71
50
  * Read and validate an `--isolation-file`. The file is the existing
72
51
  * `isolation:` mapping and nothing else — the same schema, the same validator
@@ -78,8 +57,6 @@ export declare function buildRoleConfig(o: SpawnOpts, defaultHarness?: string):
78
57
  */
79
58
  export declare function readIsolationFile(path: string): IsolationConfig;
80
59
  export declare function validateSpawnOpts(o: SpawnOpts): void;
81
- /** Read mission text without trimming or newline rewriting. */
82
- export declare function readMissionFile(path: string): string;
83
60
  /** The ours identity a spawn will bind: explicit, else the role name. */
84
61
  export declare const effectiveIdentity: (o: SpawnOpts) => string;
85
62
  export interface SpawnDryRun {
@@ -88,8 +65,6 @@ export interface SpawnDryRun {
88
65
  roleDocument: Record<string, unknown>;
89
66
  resolvedRole: ResolvedRole;
90
67
  }
91
- /** Bare Agent document written by v2 spawn: inline Role × inline Brain + operations. */
92
- export declare function buildAgentDocument(raw: RoleConfig): Record<string, unknown>;
93
68
  /**
94
69
  * Validate and resolve a spawn without reserving names, contacting the daemon,
95
70
  * or writing state. Collision checks are necessarily a point-in-time snapshot.