@loucompanion/forge-bridge 0.1.3-dev.b6be5e7f9668 → 0.1.3-dev.ba7d36726eda

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 (141) hide show
  1. package/README.md +78 -0
  2. package/dist/bridge/bridge.base.d.ts +5 -1
  3. package/dist/bridge/bridge.service.d.ts +52 -3
  4. package/dist/bridge/bridge.service.js +796 -22
  5. package/dist/bridge/bridge.types.d.ts +135 -1
  6. package/dist/cleanup/cleanup.service.d.ts +13 -2
  7. package/dist/cleanup/cleanup.service.js +435 -3
  8. package/dist/cleanup/cleanup.types.d.ts +10 -0
  9. package/dist/cleanup/index.d.ts +1 -1
  10. package/dist/cleanup/index.js +1 -1
  11. package/dist/cleanup/internals.d.ts +2 -2
  12. package/dist/cleanup/internals.js +1 -1
  13. package/dist/cli/cli.help.d.ts +2 -0
  14. package/dist/cli/cli.help.js +217 -0
  15. package/dist/cli/cli.service.d.ts +42 -2
  16. package/dist/cli/cli.service.js +356 -205
  17. package/dist/cli/local-config.d.ts +6 -0
  18. package/dist/cli/local-config.js +45 -2
  19. package/dist/runtime/attempt-node-runtime-launcher.d.ts +15 -0
  20. package/dist/runtime/attempt-node-runtime-launcher.js +50 -0
  21. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  22. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  23. package/dist/runtime/attempt-process-supervisor.d.ts +74 -0
  24. package/dist/runtime/attempt-process-supervisor.js +290 -0
  25. package/dist/runtime/host-listener-inspector.d.ts +28 -0
  26. package/dist/runtime/host-listener-inspector.js +185 -0
  27. package/dist/runtime/host-port-broker.d.ts +76 -0
  28. package/dist/runtime/host-port-broker.js +333 -0
  29. package/dist/runtime/host-runtime-contract.d.ts +39 -0
  30. package/dist/runtime/host-runtime-contract.js +295 -0
  31. package/dist/runtime/host-runtime-lease.d.ts +122 -0
  32. package/dist/runtime/host-runtime-lease.js +1241 -0
  33. package/dist/runtime/host-runtime-promotion.d.ts +21 -0
  34. package/dist/runtime/host-runtime-promotion.js +128 -0
  35. package/dist/runtime/node-host-runtime-adapter.d.ts +44 -0
  36. package/dist/runtime/node-host-runtime-adapter.js +205 -0
  37. package/dist/runtime/playwright-host-runtime-adapter.d.ts +20 -0
  38. package/dist/runtime/playwright-host-runtime-adapter.js +136 -0
  39. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  40. package/dist/runtime/runtime-isolation-lease.js +158 -0
  41. package/dist/sandbox/command-runner.d.ts +4 -0
  42. package/dist/sandbox/command-runner.js +47 -0
  43. package/dist/sandbox/contract.d.ts +5 -0
  44. package/dist/sandbox/contract.js +276 -0
  45. package/dist/sandbox/event-budget.d.ts +11 -0
  46. package/dist/sandbox/event-budget.js +27 -0
  47. package/dist/sandbox/image-policy.d.ts +35 -0
  48. package/dist/sandbox/image-policy.js +247 -0
  49. package/dist/sandbox/index.d.ts +5 -0
  50. package/dist/sandbox/index.js +5 -0
  51. package/dist/sandbox/lease-watchdog.d.ts +18 -0
  52. package/dist/sandbox/lease-watchdog.js +67 -0
  53. package/dist/sandbox/preflight.d.ts +45 -0
  54. package/dist/sandbox/preflight.js +300 -0
  55. package/dist/sandbox/process.d.ts +20 -0
  56. package/dist/sandbox/process.js +209 -0
  57. package/dist/sandbox/provider-auth/codex.d.ts +26 -0
  58. package/dist/sandbox/provider-auth/codex.js +212 -0
  59. package/dist/sandbox/repository-lock.d.ts +5 -0
  60. package/dist/sandbox/repository-lock.js +119 -0
  61. package/dist/sandbox/runtime.d.ts +297 -0
  62. package/dist/sandbox/runtime.js +2338 -0
  63. package/dist/sandbox/sandbox.types.d.ts +86 -0
  64. package/dist/sandbox/sandbox.types.js +1 -0
  65. package/dist/sandbox/smoke.d.ts +76 -0
  66. package/dist/sandbox/smoke.js +380 -0
  67. package/dist/sandbox/worker-supervisor.d.ts +13 -0
  68. package/dist/sandbox/worker-supervisor.js +36 -0
  69. package/dist/sandbox/worker.d.ts +1 -0
  70. package/dist/sandbox/worker.js +225 -0
  71. package/dist/sandbox/workspace.service.d.ts +13 -0
  72. package/dist/sandbox/workspace.service.js +67 -0
  73. package/dist/session/phase-completion.artifacts.d.ts +10 -0
  74. package/dist/session/phase-completion.artifacts.js +620 -0
  75. package/dist/session/phase-completion.d.ts +32 -44
  76. package/dist/session/phase-completion.helpers.d.ts +112 -0
  77. package/dist/session/phase-completion.helpers.js +665 -0
  78. package/dist/session/phase-completion.js +28 -3485
  79. package/dist/session/phase-completion.prompts.d.ts +14 -0
  80. package/dist/session/phase-completion.prompts.js +220 -0
  81. package/dist/session/phase-completion.runtime.d.ts +36 -0
  82. package/dist/session/phase-completion.runtime.js +611 -0
  83. package/dist/session/phase-completion.schemas.d.ts +3 -0
  84. package/dist/session/phase-completion.schemas.js +707 -0
  85. package/dist/session/phase-completion.validation.d.ts +34 -0
  86. package/dist/session/phase-completion.validation.js +724 -0
  87. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +93 -0
  88. package/dist/session/provider-cli/provider-cli.artifacts.js +778 -0
  89. package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
  90. package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
  91. package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
  92. package/dist/session/provider-cli/provider-cli.deterministic.d.ts +14 -0
  93. package/dist/session/provider-cli/provider-cli.deterministic.js +329 -0
  94. package/dist/session/provider-cli/provider-cli.process.d.ts +34 -0
  95. package/dist/session/provider-cli/provider-cli.process.js +316 -0
  96. package/dist/session/provider-cli/provider-cli.prompts.d.ts +47 -0
  97. package/dist/session/provider-cli/provider-cli.prompts.js +573 -0
  98. package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
  99. package/dist/session/provider-cli/provider-cli.qa-sidecars.js +65 -0
  100. package/dist/session/provider-cli/provider-cli.runtime.d.ts +9 -0
  101. package/dist/session/provider-cli/provider-cli.runtime.js +41 -0
  102. package/dist/session/provider-cli/provider-cli.service.d.ts +9 -53
  103. package/dist/session/provider-cli/provider-cli.service.js +182 -2536
  104. package/dist/session/provider-cli/provider-cli.types.d.ts +3 -0
  105. package/dist/session/provider-cli/provider-output-state.d.ts +20 -0
  106. package/dist/session/provider-cli/provider-output-state.js +445 -0
  107. package/dist/session/session-redaction.d.ts +3 -0
  108. package/dist/session/session-redaction.js +23 -0
  109. package/dist/session/session.base.d.ts +4 -0
  110. package/dist/session/session.service.d.ts +6 -1
  111. package/dist/session/session.service.js +202 -53
  112. package/dist/session/session.types.d.ts +123 -1
  113. package/dist/session/session.types.js +301 -3
  114. package/dist/shared/path/path.js +3 -1
  115. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  116. package/dist/shared/process/host-process-recovery.js +78 -0
  117. package/dist/shared/process/host-process-supervisor.d.ts +40 -0
  118. package/dist/shared/process/host-process-supervisor.js +195 -0
  119. package/dist/shared/process/windows-launch.d.ts +8 -0
  120. package/dist/shared/process/windows-launch.js +41 -0
  121. package/dist/shared/provider-capabilities.d.ts +1 -0
  122. package/dist/shared/provider-capabilities.js +3 -0
  123. package/dist/shared/redaction/redaction.js +10 -3
  124. package/dist/version.d.ts +1 -1
  125. package/dist/version.js +1 -1
  126. package/dist/worktree/artifact-injector.js +39 -6
  127. package/dist/worktree/dependency-warmer.d.ts +11 -0
  128. package/dist/worktree/dependency-warmer.js +45 -1
  129. package/dist/worktree/index.d.ts +1 -0
  130. package/dist/worktree/index.js +1 -0
  131. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  132. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  133. package/dist/worktree/worktree-paths.d.ts +4 -0
  134. package/dist/worktree/worktree-paths.js +30 -0
  135. package/dist/worktree/worktree.base.d.ts +3 -0
  136. package/dist/worktree/worktree.service.d.ts +8 -3
  137. package/dist/worktree/worktree.service.js +89 -52
  138. package/dist/worktree/worktree.types.d.ts +3 -1
  139. package/package.json +6 -3
  140. package/runtime/windows-job-supervisor.ps1 +287 -0
  141. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -2,11 +2,11 @@ import path from "node:path";
2
2
  export function parseRunOnceDispatchPacket(value) {
3
3
  const packet = asObject(value, "dispatch packet");
4
4
  const schemaVersion = requiredNumber(packet, "schemaVersion");
5
- if (schemaVersion !== 1) {
5
+ if (schemaVersion !== 1 && schemaVersion !== 2) {
6
6
  throw new Error(`Unsupported dispatch schemaVersion: ${schemaVersion}`);
7
7
  }
8
8
  return {
9
- schemaVersion: 1,
9
+ schemaVersion,
10
10
  userId: requiredString(packet, "userId"),
11
11
  workItem: parseWorkItem(packet.workItem),
12
12
  repo: parseRepo(packet.repo),
@@ -15,19 +15,146 @@ export function parseRunOnceDispatchPacket(value) {
15
15
  lease: parseLease(packet.lease),
16
16
  execution: parseExecution(packet.execution),
17
17
  serverLocal: parseServerLocal(packet.serverLocal),
18
+ sandbox: parseSandbox(packet.sandbox, schemaVersion),
18
19
  pullRequest: parsePullRequest(packet.pullRequest),
19
20
  apiKeySecretGrant: parseApiKeySecretGrant(packet.apiKeySecretGrant),
21
+ apiKeySecretGrantHandle: parseApiKeySecretGrantHandle(packet.apiKeySecretGrantHandle),
20
22
  artifacts: parseArtifacts(packet.artifacts),
23
+ priorPhaseArtifacts: parsePriorPhaseArtifacts(packet.priorPhaseArtifacts),
21
24
  warmPathOverrides: parseWarmPathOverrides(packet.warmPathOverrides),
22
25
  qaBrowser: parseQaBrowser(packet.qaBrowser),
23
26
  qaScope: parseQaScope(packet.qaScope ?? packet.qa_scope),
24
27
  qaRework: parseQaRework(packet.qaRework),
25
28
  architectRevision: parseArchitectRevision(packet.architectRevision),
29
+ architectDependencies: parseArchitectDependencies(packet.architectDependencies),
26
30
  storyDevelop: parseStoryDevelop(packet.storyDevelop),
27
31
  storyQa: parseStoryQa(packet.storyQa),
32
+ designerRefs: parseDesignerRefs(packet.designerRefs ?? packet.designer_refs),
28
33
  draft: parseDraftPrompt(packet.draft)
29
34
  };
30
35
  }
36
+ function parseSandbox(value, schemaVersion) {
37
+ if (value === undefined || value === null) {
38
+ if (schemaVersion === 2) {
39
+ throw new Error("sandbox is required for dispatch schemaVersion 2.");
40
+ }
41
+ return null;
42
+ }
43
+ const sandbox = asObject(value, "sandbox");
44
+ const executionMode = requiredString(sandbox, "executionMode");
45
+ if (executionMode !== "host" && executionMode !== "docker") {
46
+ throw new Error("sandbox.executionMode must be host or docker.");
47
+ }
48
+ const executionPolicy = requiredString(sandbox, "executionPolicy");
49
+ if (executionPolicy !== "host_allowed" && executionPolicy !== "sandbox_preferred" && executionPolicy !== "sandbox_required") {
50
+ throw new Error("sandbox.executionPolicy is invalid.");
51
+ }
52
+ return {
53
+ dispatchAttemptId: requiredString(sandbox, "dispatchAttemptId"),
54
+ executionLeaseId: requiredString(sandbox, "executionLeaseId"),
55
+ runnerInstanceId: requiredString(sandbox, "runnerInstanceId"),
56
+ executionMode,
57
+ executionPolicy,
58
+ networkPolicy: parseSandboxNetworkPolicy(sandbox.networkPolicy),
59
+ requestedResources: parseSandboxResourceRequest(sandbox.requestedResources),
60
+ runtime: optionalString(sandbox, "runtime"),
61
+ sandboxConfigCommitSha: optionalString(sandbox, "sandboxConfigCommitSha"),
62
+ inputCommitSha: optionalString(sandbox, "inputCommitSha"),
63
+ contractDigest: optionalString(sandbox, "contractDigest"),
64
+ imageDigest: optionalString(sandbox, "imageDigest"),
65
+ publicationMode: parsePublicationMode(sandbox.publicationMode),
66
+ publishGrantHandle: parsePublishGrantHandle(sandbox.publishGrantHandle)
67
+ };
68
+ }
69
+ function parsePublicationMode(value) {
70
+ if (value === undefined || value === null)
71
+ return "shared_filesystem";
72
+ if (value !== "bridge_push" && value !== "shared_filesystem")
73
+ throw new Error("sandbox.publicationMode is invalid.");
74
+ return value;
75
+ }
76
+ function parsePublishGrantHandle(value) {
77
+ if (value === undefined || value === null)
78
+ return null;
79
+ const handle = asObject(value, "sandbox.publishGrantHandle");
80
+ const grantId = requiredString(handle, "grantId");
81
+ const branch = requiredString(handle, "branch");
82
+ const expiresAt = requiredString(handle, "expiresAt");
83
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(grantId)) {
84
+ throw new Error("sandbox.publishGrantHandle.grantId is invalid.");
85
+ }
86
+ if (branch.length > 240)
87
+ throw new Error("sandbox.publishGrantHandle.branch is too long.");
88
+ if (!Number.isFinite(Date.parse(expiresAt)))
89
+ throw new Error("sandbox.publishGrantHandle.expiresAt is invalid.");
90
+ return { grantId, branch, expiresAt };
91
+ }
92
+ function parseSandboxResourceRequest(value) {
93
+ if (value === undefined || value === null) {
94
+ return {
95
+ sandboxSlots: 1,
96
+ cpuMillis: 2000,
97
+ workerMemoryLimitBytes: 2 * 1024 ** 3,
98
+ serviceMemoryLimitBytes: 0,
99
+ runtimeOverheadBytes: 128 * 1024 ** 2,
100
+ memoryBytes: 2 * 1024 ** 3 + 128 * 1024 ** 2,
101
+ workspaceBytes: 10 * 1024 ** 3,
102
+ pids: 512,
103
+ buildSlots: 1
104
+ };
105
+ }
106
+ const resources = asObject(value, "sandbox.requestedResources");
107
+ const memoryBytes = boundedInteger(resources, "memoryBytes", 128 * 1024 ** 2, 512 * 1024 ** 3);
108
+ const hasMemoryBreakdown = ["workerMemoryLimitBytes", "serviceMemoryLimitBytes", "runtimeOverheadBytes"]
109
+ .some(key => resources[key] !== undefined && resources[key] !== null);
110
+ const workerMemoryLimitBytes = hasMemoryBreakdown
111
+ ? boundedInteger(resources, "workerMemoryLimitBytes", 128 * 1024 ** 2, 512 * 1024 ** 3)
112
+ : memoryBytes;
113
+ const serviceMemoryLimitBytes = hasMemoryBreakdown
114
+ ? boundedInteger(resources, "serviceMemoryLimitBytes", 0, 512 * 1024 ** 3)
115
+ : 0;
116
+ const runtimeOverheadBytes = hasMemoryBreakdown
117
+ ? boundedInteger(resources, "runtimeOverheadBytes", 0, 64 * 1024 ** 3)
118
+ : 0;
119
+ if (workerMemoryLimitBytes + serviceMemoryLimitBytes + runtimeOverheadBytes !== memoryBytes) {
120
+ throw new Error("sandbox.requestedResources memory breakdown must equal memoryBytes.");
121
+ }
122
+ return {
123
+ sandboxSlots: boundedInteger(resources, "sandboxSlots", 1, 4),
124
+ cpuMillis: boundedInteger(resources, "cpuMillis", 100, 64_000),
125
+ workerMemoryLimitBytes,
126
+ serviceMemoryLimitBytes,
127
+ runtimeOverheadBytes,
128
+ memoryBytes,
129
+ workspaceBytes: boundedInteger(resources, "workspaceBytes", 1024 ** 3, 10 * 1024 ** 4),
130
+ pids: boundedInteger(resources, "pids", 32, 65_536),
131
+ buildSlots: boundedInteger(resources, "buildSlots", 0, 4)
132
+ };
133
+ }
134
+ function parseSandboxNetworkPolicy(value) {
135
+ if (value === undefined || value === null)
136
+ return { mode: "offline", allowedHosts: [] };
137
+ const policy = asObject(value, "sandbox.networkPolicy");
138
+ const mode = requiredString(policy, "mode");
139
+ if (mode !== "offline" && mode !== "public_allowlist" && mode !== "open_public") {
140
+ throw new Error("sandbox.networkPolicy.mode is invalid.");
141
+ }
142
+ const rawHosts = policy.allowedHosts ?? [];
143
+ if (!Array.isArray(rawHosts) || rawHosts.some((host) => typeof host !== "string")) {
144
+ throw new Error("sandbox.networkPolicy.allowedHosts must be a string array.");
145
+ }
146
+ const allowedHosts = [...new Set(rawHosts.map(host => host.trim().toLowerCase()).filter(Boolean))];
147
+ if (allowedHosts.length > 128 || allowedHosts.some(host => !/^(?:\*\.)?(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$/u.test(host))) {
148
+ throw new Error("sandbox.networkPolicy.allowedHosts contains an invalid hostname.");
149
+ }
150
+ if (mode !== "public_allowlist" && allowedHosts.length > 0) {
151
+ throw new Error("sandbox.networkPolicy.allowedHosts requires public_allowlist mode.");
152
+ }
153
+ if (mode === "public_allowlist" && allowedHosts.length === 0) {
154
+ return { mode: "offline", allowedHosts: [] };
155
+ }
156
+ return { mode, allowedHosts };
157
+ }
31
158
  export function failureResult(summary, events = []) {
32
159
  return {
33
160
  schemaVersion: 1,
@@ -119,9 +246,45 @@ function parseDraftPrompt(value) {
119
246
  return {
120
247
  kind: requiredString(draft, "kind"),
121
248
  systemPrompt: requiredString(draft, "systemPrompt"),
122
- userPrompt: requiredString(draft, "userPrompt")
249
+ userPrompt: requiredString(draft, "userPrompt"),
250
+ projectName: optionalString(draft, "projectName"),
251
+ repoName: optionalString(draft, "repoName"),
252
+ attachments: parseDraftAttachments(draft.attachments)
123
253
  };
124
254
  }
255
+ function parseDraftAttachments(value) {
256
+ if (value === undefined || value === null) {
257
+ return [];
258
+ }
259
+ if (!Array.isArray(value)) {
260
+ throw new Error("draft.attachments must be an array when provided.");
261
+ }
262
+ return value.map((entry, index) => {
263
+ const attachment = asObject(entry, `draft.attachments[${index}]`);
264
+ const kind = requiredString(attachment, "kind").trim().toLowerCase();
265
+ if (kind !== "text" && kind !== "image") {
266
+ throw new Error(`draft.attachments[${index}].kind must be text or image.`);
267
+ }
268
+ const textContent = optionalString(attachment, "textContent");
269
+ const contentBase64 = optionalString(attachment, "contentBase64");
270
+ if (kind === "text" && textContent === null) {
271
+ throw new Error(`draft.attachments[${index}].textContent is required for text attachments.`);
272
+ }
273
+ if (kind === "image" && contentBase64 === null) {
274
+ throw new Error(`draft.attachments[${index}].contentBase64 is required for image attachments.`);
275
+ }
276
+ return {
277
+ index: optionalNonNegativeInteger(attachment, "index") ?? index,
278
+ fileName: requiredString(attachment, "fileName"),
279
+ contentType: optionalString(attachment, "contentType") ?? "",
280
+ kind,
281
+ byteCount: optionalNonNegativeInteger(attachment, "byteCount") ?? 0,
282
+ summary: optionalString(attachment, "summary") ?? "",
283
+ textContent,
284
+ contentBase64
285
+ };
286
+ });
287
+ }
125
288
  function parseApiKeySecretGrant(value) {
126
289
  if (value === undefined || value === null) {
127
290
  return null;
@@ -134,6 +297,16 @@ function parseApiKeySecretGrant(value) {
134
297
  expiresAt: requiredString(grant, "expiresAt")
135
298
  };
136
299
  }
300
+ function parseApiKeySecretGrantHandle(value) {
301
+ if (value === undefined || value === null)
302
+ return null;
303
+ const grant = asObject(value, "apiKeySecretGrantHandle");
304
+ return {
305
+ grantId: requiredString(grant, "grantId"),
306
+ provider: requiredString(grant, "provider"),
307
+ expiresAt: requiredString(grant, "expiresAt")
308
+ };
309
+ }
137
310
  function parseServerLocal(value) {
138
311
  const serverLocal = asObject(value, "serverLocal");
139
312
  return {
@@ -282,6 +455,34 @@ function parseArchitectRevision(value) {
282
455
  feedback: requiredString(revision, "feedback")
283
456
  };
284
457
  }
458
+ function parseArchitectDependencies(value) {
459
+ if (value === undefined || value === null) {
460
+ return null;
461
+ }
462
+ const context = asObject(value, "architectDependencies");
463
+ const prerequisites = context.prerequisites;
464
+ if (!Array.isArray(prerequisites)) {
465
+ throw new Error("architectDependencies.prerequisites must be an array.");
466
+ }
467
+ return {
468
+ baseBranch: requiredString(context, "baseBranch").trim(),
469
+ prerequisites: prerequisites.map((entry, index) => {
470
+ const prerequisite = asObject(entry, `architectDependencies.prerequisites[${index}]`);
471
+ return {
472
+ storyId: requiredString(prerequisite, "storyId").trim(),
473
+ title: requiredString(prerequisite, "title").trim(),
474
+ scheduleIndex: optionalNonNegativeInteger(prerequisite, "scheduleIndex"),
475
+ waveIndex: optionalNonNegativeInteger(prerequisite, "waveIndex"),
476
+ featureScheduleIndex: optionalNonNegativeInteger(prerequisite, "featureScheduleIndex"),
477
+ expectedOutput: optionalString(prerequisite, "expectedOutput"),
478
+ handoffSummary: optionalString(prerequisite, "handoffSummary"),
479
+ dependencyReason: optionalString(prerequisite, "dependencyReason"),
480
+ pullRequestNumber: optionalPositiveInteger(prerequisite, "pullRequestNumber"),
481
+ targetBranch: requiredString(prerequisite, "targetBranch").trim()
482
+ };
483
+ })
484
+ };
485
+ }
285
486
  function parseStoryDevelop(value) {
286
487
  if (value === undefined || value === null) {
287
488
  return null;
@@ -387,6 +588,58 @@ function parseStoryQaDependencies(value) {
387
588
  };
388
589
  });
389
590
  }
591
+ function parseDesignerRefs(value) {
592
+ if (value === undefined || value === null) {
593
+ return null;
594
+ }
595
+ const context = asObject(value, "designerRefs");
596
+ const refs = context.refs;
597
+ if (!Array.isArray(refs)) {
598
+ throw new Error("designerRefs.refs must be an array.");
599
+ }
600
+ return {
601
+ schemaVersion: requiredNumber(context, "schemaVersion"),
602
+ kind: requiredString(context, "kind"),
603
+ ownerType: requiredString(context, "ownerType"),
604
+ ownerId: requiredString(context, "ownerId"),
605
+ projectId: requiredString(context, "projectId"),
606
+ repoId: requiredString(context, "repoId"),
607
+ title: requiredString(context, "title"),
608
+ refs: refs.map((entry, index) => parseDesignerRefAttachment(entry, index))
609
+ };
610
+ }
611
+ function parseDesignerRefAttachment(value, index) {
612
+ const attachment = asObject(value, `designerRefs.refs[${index}]`);
613
+ return {
614
+ ref: parseDesignerRef(attachment.ref, index),
615
+ reason: optionalString(attachment, "reason") ?? "",
616
+ required: requiredBoolean(attachment, "required"),
617
+ source: optionalString(attachment, "source") ?? "",
618
+ lineageKind: optionalString(attachment, "lineageKind") ?? optionalString(attachment, "lineage_kind") ?? "",
619
+ inheritedFromItemType: optionalString(attachment, "inheritedFromItemType") ?? optionalString(attachment, "inherited_from_item_type"),
620
+ inheritedFromItemId: optionalString(attachment, "inheritedFromItemId") ?? optionalString(attachment, "inherited_from_item_id"),
621
+ inheritedFromDesignerRefId: optionalString(attachment, "inheritedFromDesignerRefId") ?? optionalString(attachment, "inherited_from_designer_ref_id"),
622
+ addedByLane: optionalString(attachment, "addedByLane") ?? optionalString(attachment, "added_by_lane") ?? "",
623
+ sortOrder: requiredNumber(attachment, "sortOrder"),
624
+ createdAt: requiredString(attachment, "createdAt")
625
+ };
626
+ }
627
+ function parseDesignerRef(value, index) {
628
+ const designerRef = asObject(value, `designerRefs.refs[${index}].ref`);
629
+ return {
630
+ id: requiredString(designerRef, "id"),
631
+ projectId: requiredString(designerRef, "projectId"),
632
+ repoId: requiredString(designerRef, "repoId"),
633
+ refKey: requiredString(designerRef, "refKey"),
634
+ kind: requiredString(designerRef, "kind"),
635
+ path: requiredString(designerRef, "path"),
636
+ anchor: optionalString(designerRef, "anchor"),
637
+ title: optionalString(designerRef, "title") ?? "",
638
+ contentHash: requiredString(designerRef, "contentHash"),
639
+ commitSha: requiredString(designerRef, "commitSha"),
640
+ createdAt: requiredString(designerRef, "createdAt")
641
+ };
642
+ }
390
643
  function parseStringArray(value, label) {
391
644
  if (!Array.isArray(value)) {
392
645
  throw new Error(`${label} must be an array.`);
@@ -431,6 +684,34 @@ function parseArtifacts(value) {
431
684
  };
432
685
  });
433
686
  }
687
+ function parsePriorPhaseArtifacts(value) {
688
+ if (value === undefined || value === null) {
689
+ return [];
690
+ }
691
+ if (!Array.isArray(value)) {
692
+ throw new Error("priorPhaseArtifacts must be an array when provided.");
693
+ }
694
+ return value.map((entry, index) => {
695
+ const artifact = asObject(entry, `priorPhaseArtifacts[${index}]`);
696
+ const phase = requiredString(artifact, "phase").trim().toLowerCase().replace(/-/g, "_");
697
+ if (phase !== "implementation" && phase !== "self_review" && phase !== "validation") {
698
+ throw new Error(`priorPhaseArtifacts[${index}].phase must be implementation, self_review, or validation.`);
699
+ }
700
+ return {
701
+ workItemId: requiredString(artifact, "workItemId"),
702
+ artifactId: requiredString(artifact, "artifactId"),
703
+ cycleRunId: requiredString(artifact, "cycleRunId"),
704
+ phase,
705
+ status: requiredString(artifact, "status"),
706
+ producedAt: requiredString(artifact, "producedAt"),
707
+ sourcePath: requiredString(artifact, "sourcePath"),
708
+ verdictJson: requiredString(artifact, "verdictJson"),
709
+ report: optionalString(artifact, "report") ?? "",
710
+ verdictContentHash: requiredString(artifact, "verdictContentHash").toLowerCase(),
711
+ reportContentHash: requiredString(artifact, "reportContentHash").toLowerCase()
712
+ };
713
+ });
714
+ }
434
715
  function asObject(value, label) {
435
716
  if (!value || typeof value !== "object" || Array.isArray(value)) {
436
717
  throw new Error(`${label} must be an object.`);
@@ -481,6 +762,16 @@ function optionalPositiveInteger(source, key) {
481
762
  }
482
763
  return value;
483
764
  }
765
+ function optionalNonNegativeInteger(source, key) {
766
+ const value = source[key];
767
+ if (value === undefined || value === null) {
768
+ return null;
769
+ }
770
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
771
+ throw new Error(`${key} must be a non-negative integer when provided.`);
772
+ }
773
+ return value;
774
+ }
484
775
  function requiredNumber(source, key) {
485
776
  const value = source[key];
486
777
  if (typeof value !== "number" || !Number.isFinite(value)) {
@@ -488,3 +779,10 @@ function requiredNumber(source, key) {
488
779
  }
489
780
  return value;
490
781
  }
782
+ function boundedInteger(source, key, minimum, maximum) {
783
+ const value = requiredNumber(source, key);
784
+ if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
785
+ throw new Error(`${key} must be an integer from ${minimum} through ${maximum}.`);
786
+ }
787
+ return value;
788
+ }
@@ -4,7 +4,9 @@ export function isSubPath(root, candidate) {
4
4
  const normalizedRoot = path.resolve(root);
5
5
  const normalizedCandidate = path.resolve(candidate);
6
6
  const relative = path.relative(normalizedRoot, normalizedCandidate);
7
- return relative === "" || (!!relative && !relative.startsWith("..") && !path.isAbsolute(relative));
7
+ return relative === "" || (relative !== ".."
8
+ && !relative.startsWith(`..${path.sep}`)
9
+ && !path.isAbsolute(relative));
8
10
  }
9
11
  export async function resolveExistingDirectoryPath(inputPath) {
10
12
  const stat = await fs.stat(inputPath);
@@ -0,0 +1,12 @@
1
+ import type { HostProcessSupervisorMetadata } from "./host-process-supervisor.js";
2
+ export type HostProcessRecoveryOutcome = "absent" | "terminated" | "pid_reused" | "unverifiable";
3
+ export interface HostProcessRecovery {
4
+ recover(metadata: HostProcessSupervisorMetadata): Promise<HostProcessRecoveryOutcome>;
5
+ }
6
+ /** Exact, birth-marker-fenced recovery for a process tree left by an old bridge. */
7
+ export declare class ExactHostProcessRecovery implements HostProcessRecovery {
8
+ private readonly platform;
9
+ private readonly graceMs;
10
+ constructor(platform?: NodeJS.Platform, graceMs?: number);
11
+ recover(metadata: HostProcessSupervisorMetadata): Promise<HostProcessRecoveryOutcome>;
12
+ }
@@ -0,0 +1,78 @@
1
+ import { captureHostProcessIdentity } from "./host-process-supervisor.js";
2
+ /** Exact, birth-marker-fenced recovery for a process tree left by an old bridge. */
3
+ export class ExactHostProcessRecovery {
4
+ platform;
5
+ graceMs;
6
+ constructor(platform = process.platform, graceMs = 2_000) {
7
+ this.platform = platform;
8
+ this.graceMs = graceMs;
9
+ }
10
+ async recover(metadata) {
11
+ if (!processExists(metadata.rootPid))
12
+ return "absent";
13
+ if (!metadata.rootIdentity)
14
+ return "unverifiable";
15
+ const currentIdentity = captureHostProcessIdentity(metadata.rootPid, this.platform);
16
+ if (!currentIdentity)
17
+ return processExists(metadata.rootPid) ? "unverifiable" : "absent";
18
+ if (currentIdentity !== metadata.rootIdentity)
19
+ return "pid_reused";
20
+ if (this.platform === "win32") {
21
+ signalExactProcess(metadata.rootPid, "SIGTERM");
22
+ }
23
+ else {
24
+ signalExactProcessGroup(metadata.rootPid, "SIGTERM");
25
+ }
26
+ await waitForExit(metadata.rootPid, this.graceMs);
27
+ if (processExists(metadata.rootPid)) {
28
+ if (captureHostProcessIdentity(metadata.rootPid, this.platform) !== metadata.rootIdentity) {
29
+ return "pid_reused";
30
+ }
31
+ if (this.platform === "win32") {
32
+ signalExactProcess(metadata.rootPid, "SIGKILL");
33
+ }
34
+ else {
35
+ signalExactProcessGroup(metadata.rootPid, "SIGKILL");
36
+ }
37
+ await waitForExit(metadata.rootPid, this.graceMs);
38
+ }
39
+ if (processExists(metadata.rootPid)
40
+ && captureHostProcessIdentity(metadata.rootPid, this.platform) === metadata.rootIdentity) {
41
+ throw new Error("host_process_recovery_degraded: exact supervised process tree did not stop.");
42
+ }
43
+ return "terminated";
44
+ }
45
+ }
46
+ function processExists(pid) {
47
+ try {
48
+ process.kill(pid, 0);
49
+ return true;
50
+ }
51
+ catch (error) {
52
+ return error.code === "EPERM";
53
+ }
54
+ }
55
+ function signalExactProcess(pid, signal) {
56
+ try {
57
+ process.kill(pid, signal);
58
+ }
59
+ catch (error) {
60
+ if (error.code !== "ESRCH")
61
+ throw error;
62
+ }
63
+ }
64
+ function signalExactProcessGroup(pid, signal) {
65
+ try {
66
+ process.kill(-pid, signal);
67
+ }
68
+ catch (error) {
69
+ if (error.code !== "ESRCH")
70
+ throw error;
71
+ }
72
+ }
73
+ async function waitForExit(pid, timeoutMs) {
74
+ const deadline = Date.now() + timeoutMs;
75
+ while (Date.now() < deadline && processExists(pid)) {
76
+ await new Promise(resolve => setTimeout(resolve, 25));
77
+ }
78
+ }
@@ -0,0 +1,40 @@
1
+ import type { ChildProcess } from "node:child_process";
2
+ export type HostProcessSupervisorStrategy = "windows_job_object" | "unix_process_group";
3
+ export interface HostProcessSupervisionRequest {
4
+ readonly command: string;
5
+ readonly args: readonly string[];
6
+ readonly cwd: string;
7
+ readonly env?: Readonly<Record<string, string>>;
8
+ }
9
+ export interface HostProcessSupervisedLaunch {
10
+ readonly command: string;
11
+ readonly args: readonly string[];
12
+ readonly detached: boolean;
13
+ readonly supervisorId: string;
14
+ readonly strategy: HostProcessSupervisorStrategy;
15
+ readonly metadataPath: string;
16
+ readonly commandDigest: string;
17
+ }
18
+ export interface HostProcessSupervisorMetadata {
19
+ readonly schemaVersion: 1;
20
+ readonly supervisorId: string;
21
+ readonly strategy: HostProcessSupervisorStrategy;
22
+ readonly rootPid: number;
23
+ readonly rootCreatedAt: string;
24
+ readonly rootIdentity?: string | null;
25
+ readonly commandDigest: string;
26
+ readonly worktreePath: string;
27
+ readonly runtimeLeaseId: string;
28
+ readonly dispatchAttemptId: string;
29
+ readonly fencingToken: string;
30
+ readonly bridgePid: number;
31
+ }
32
+ export declare function resolveHostProcessSupervisedLaunch(request: HostProcessSupervisionRequest, resolvedCommand: string, resolvedArgs: readonly string[], platform?: NodeJS.Platform): HostProcessSupervisedLaunch | null;
33
+ export declare function persistHostProcessSupervisorMetadata(launch: HostProcessSupervisedLaunch, request: HostProcessSupervisionRequest, rootPid: number, rootCreatedAt: string): HostProcessSupervisorMetadata;
34
+ /**
35
+ * Captures an OS-issued process birth marker. Recovery compares this marker
36
+ * before signalling a persisted PID so PID reuse can never redirect cleanup
37
+ * at an unrelated process.
38
+ */
39
+ export declare function captureHostProcessIdentity(pid: number, platform?: NodeJS.Platform): string | null;
40
+ export declare function terminateHostProcessTree(child: ChildProcess, launch: HostProcessSupervisedLaunch | null, graceMs?: number): Promise<void>;