@@ -8,35 +8,6 @@ import type { ControllerBootstrapError } from "./host-errors.js"
88const inspectControllerRevisionLabelTemplate = String
99 . raw `{{ index .Config.Labels "io.prover-coder-ai.docker-git.controller-rev" }}`
1010
11- /**
12- * Builds a typed controller bootstrap error.
13- *
14- * @param message - Human-readable bootstrap failure message.
15- * @returns Controller bootstrap error value.
16- *
17- * @pure true
18- * @effect n/a
19- * @invariant Returned error tag is always `ControllerBootstrapError`.
20- * @precondition `message` is a finite string.
21- * @postcondition The returned error preserves the provided message.
22- * @complexity O(1) time and O(1) space.
23- * @throws Never
24- */
25- // CHANGE: represent deterministic image-resolution failures as typed bootstrap errors
26- // WHY: ambiguous compose image output must fail through the Effect error channel
27- // QUOTE(ТЗ): "хочу сузить время билда докер контейнера"
28- // REF: user-request-2026-05-22-controller-build-speed
29- // SOURCE: n/a
30- // FORMAT THEOREM: error(message).message = message
31- // PURITY: CORE
32- // EFFECT: n/a
33- // INVARIANT: error tag is stable
34- // COMPLEXITY: O(1)
35- const controllerBootstrapError = ( message : string ) : ControllerBootstrapError => ( {
36- _tag : "ControllerBootstrapError" ,
37- message
38- } )
39-
4011/**
4112 * Returns all non-empty lines from Docker CLI output.
4213 *
@@ -72,56 +43,40 @@ const nonEmptyLines = (output: string): ReadonlyArray<string> => {
7243 * Resolves compose image output into exactly one controller image name.
7344 *
7445 * @param output - Raw `docker compose config --images` output.
75- * @returns Effect with the single image, null for empty output, or a typed bootstrap error for ambiguity .
46+ * @returns The single image, or null for empty/ambiguous output.
7647 *
7748 * @pure true
78- * @effect Effect.succeed | Effect.fail
79- * @invariant More than one non-empty line is rejected as ambiguous .
49+ * @effect n/a
50+ * @invariant More than one non-empty line never collapses to the first image .
8051 * @precondition `output` is finite Docker CLI output.
8152 * @postcondition Success with a string implies exactly one non-empty image line existed.
8253 * @complexity O(n) time and O(n) space where n = |output|.
83- * @throws Never - ambiguity is represented in the Effect error channel.
54+ * @throws Never
8455 */
8556// CHANGE: require deterministic controller image resolution from compose output
8657// WHY: revision reuse is sound only when the inspected image is uniquely the controller image
8758// QUOTE(ТЗ): "хочу сузить время билда докер контейнера"
8859// REF: user-request-2026-05-22-controller-build-speed
8960// SOURCE: n/a
90- // FORMAT THEOREM: |images| = 0 -> null, |images| = 1 -> images[0], |images| > 1 -> error
61+ // FORMAT THEOREM: |images| = 1 -> images[0], otherwise null
9162// PURITY: CORE
92- // EFFECT: Effect<string | null, ControllerBootstrapError>
63+ // EFFECT: n/a
9364// INVARIANT: multiple compose images never collapse to the first image
9465// COMPLEXITY: O(n) where n = |output|
95- const resolveSingleControllerImageName = (
96- output : string
97- ) : Effect . Effect < string | null , ControllerBootstrapError > => {
66+ const resolveSingleControllerImageName = ( output : string ) : string | null => {
9867 const imageNames = nonEmptyLines ( output )
99- if ( imageNames . length === 0 ) {
100- return Effect . succeed ( null )
101- }
10268 const imageName = imageNames [ 0 ]
103- if ( imageNames . length === 1 && imageName !== undefined ) {
104- return Effect . succeed ( imageName )
105- }
106- return Effect . fail (
107- controllerBootstrapError (
108- [
109- "Expected exactly one docker-git controller image from docker compose config --images." ,
110- "Resolved images:" ,
111- ...imageNames . map ( ( name ) => `- ${ name } ` )
112- ] . join ( "\n" )
113- )
114- )
69+ return imageNames . length === 1 && imageName !== undefined ? imageName : null
11570}
11671
11772/**
11873 * Resolves the Docker image name configured for the active controller compose files.
11974 *
120- * @returns The single compose image name, or null when compose emits no images.
75+ * @returns The single compose image name, or null when compose emits zero or multiple images.
12176 *
12277 * @pure false
12378 * @effect Docker CLI through ControllerRuntime.
124- * @invariant Multiple compose images fail rather than selecting the first line.
79+ * @invariant Multiple compose images return null rather than selecting the first line.
12580 * @precondition Compose files resolve for the current GPU mode.
12681 * @postcondition Returned image name is trimmed and non-empty.
12782 * @complexity O(1) compose invocations.
@@ -132,10 +87,10 @@ const resolveSingleControllerImageName = (
13287// QUOTE(ТЗ): "хочу сузить время билда докер контейнера"
13388// REF: user-request-2026-05-22-controller-build-speed
13489// SOURCE: n/a
135- // FORMAT THEOREM: |compose_images| < = 1 or bootstrap fails
90+ // FORMAT THEOREM: |compose_images| = 1 -> image name, otherwise null
13691// PURITY: SHELL
13792// EFFECT: Effect<string | null, ControllerBootstrapError, ControllerRuntime>
138- // INVARIANT: ambiguous image lists are typed bootstrap errors
93+ // INVARIANT: ambiguous image lists are not treated as reusable images
13994// COMPLEXITY: O(1) Docker compose invocations
14095const inspectControllerComposeImageName = ( ) : Effect . Effect <
14196 string | null ,
@@ -156,7 +111,7 @@ const inspectControllerComposeImageName = (): Effect.Effect<
156111 )
157112 )
158113
159- return yield * _ ( resolveSingleControllerImageName ( output ) )
114+ return resolveSingleControllerImageName ( output )
160115 } )
161116
162117/**
@@ -166,7 +121,7 @@ const inspectControllerComposeImageName = (): Effect.Effect<
166121 *
167122 * @pure false
168123 * @effect Docker CLI through ControllerRuntime.
169- * @invariant Missing or ambiguous compose image output resolves to null rather than throwing.
124+ * @invariant Missing image or missing label resolves to null rather than throwing.
170125 * @precondition Docker is reachable through the configured runtime.
171126 * @postcondition Returned revision is normalized by label parsing.
172127 * @complexity O(1) Docker inspections.
@@ -180,15 +135,14 @@ const inspectControllerComposeImageName = (): Effect.Effect<
180135// FORMAT THEOREM: image_label(image) = local_revision -> no rebuild is required
181136// PURITY: SHELL
182137// EFFECT: Effect<string | null, ControllerBootstrapError, ControllerRuntime>
183- // INVARIANT: missing or unresolvable image metadata resolves to null rather than throwing
138+ // INVARIANT: missing image or missing label resolves to null rather than throwing
184139// COMPLEXITY: O(1) Docker inspections
185140export const inspectControllerImageRevision = ( ) : Effect . Effect <
186141 string | null ,
187142 ControllerBootstrapError ,
188143 ControllerRuntime
189144> =>
190145 inspectControllerComposeImageName ( ) . pipe (
191- Effect . orElseSucceed ( ( ) : string | null => null ) ,
192146 Effect . flatMap ( ( imageName ) =>
193147 imageName === null
194148 ? Effect . succeed < string | null > ( null )
0 commit comments