Usages:
  conventions: .goga/usages/conventions.md
  afm: .goga/usages/cooks/afm.md

Annotations: |
  The `conventions` practice is used for:
  - Working with the codebase
  - Organizing the REPL development cycle
  - Debugging and testing
  - Organizing the test infrastructure
  - Understanding the general principles and rules of development and testing in the project

  This cell is a thin subprocess-only wrapper over the external `afm` binary —
  its sole responsibility is to invoke "afm run" with an absolute pipeline-file
  path and a port, then propagate the subprocess exit code. Discovery, path
  resolution, port allocation, and the pipeline-file entity live in other cells:
  this cell performs no discovery or path resolution.

  Use the `afm` practice to interact with the external afm binary
  (CLI contract, PATH-resolved invocation, exit-code propagation).

  The standard library subprocess and pathlib.Path modules are used for invoking
  the binary and receiving arguments.

  The launcher inherits the process environment for the afm subprocess; an
  optional env layer (dict of strings), supplied by the caller, is applied on
  top of the inherited environment for that subprocess only. The layer is
  never logged and never printed (secret-safe).

---

"run_flow(flow_path: Path, port: int, max_parallel: int | None = None, env: dict[str, str] | None = None) -> exit_code: int":
  location: run_flow.py
  annotations: |
    Launch the external `afm` binary to run the pipeline file at the given
    absolute path and bind its dashboard to the given `port`. Optionally cap the
    number of concurrently executing stages. This is the goga-side entry point
    to afm; it performs no discovery, path resolution, or port allocation.

    `flow_path`: absolute path to the pipeline file (passed verbatim to
                 "afm run" as the positional argument)
    `port`: TCP port forwarded to "afm run --port" (allocated by the caller)
    `max_parallel`: optional cap on concurrently executing stages, forwarded to
                    afm as "--max-parallel <max_parallel>" ONLY when not None.
                    When None — the "--max-parallel" flag is OMITTED and afm
                    applies its own default. Decided by
                    the caller; `run_flow` only passes it through.
    `env`: optional environment layer applied on top of the inherited process
           environment for the afm subprocess only — composed by the caller
           (the run coordination) as the effective task env layer with the CLI
           entries applied above it. None or an empty mapping means the
           inherited environment unchanged. Never logged, never printed.
    `exit_code`: 0 on success, 127 when afm is missing from PATH, 126 when afm
                 is present but not executable (other OSError) or the
                 environment layer is rejected by the exec (ValueError),
                 otherwise afm's own exit code

    Algorithm:
    1. Receive `flow_path` (absolute), `port` (integer), the optional
       `max_parallel`, and the optional `env` layer
    2. Compose the afm subprocess environment: start from the inherited
       process environment and apply the `env` layer on top for this
       subprocess only — the caller's own process environment stays untouched
    3. Invoke "afm run" via the `afm` practice with --port <port>, the absolute
       `flow_path` as positional argument, — when `max_parallel` is not None —
       the --max-parallel <max_parallel> flag, and the composed environment;
       resolve afm through PATH
    4. On binary-not-found (FileNotFoundError, per the `afm` practice) — return
       `exit_code` 127 with a clear message
    5. On other OSError (present but not executable, permission denied, etc.) —
       or an environment layer rejected by the exec (ValueError, e.g. an
       illegal variable name inside the `env` layer) — return `exit_code` 126
       with a clear message
    6. Return afm's exit code as `exit_code`

    Apply the `conventions` practice for error-handling style and docstring formatting.

    Requirements:
    - Always pass `flow_path` to "afm run" as the positional argument — never a
      bare name
    - Always forward `port` to afm via the --port flag — never omit it
    - Forward `max_parallel` via --max-parallel ONLY when it is not None — None
      means "do not limit" and MUST omit the flag (never substitute a default
      such as 0)
    - Apply the `env` layer for the afm subprocess only — never write it into
      the caller's process environment
    - Never print or log any value of the `env` layer
    - Invoke afm through PATH — do not hard-code /srv/afm
    - Apply the `afm` practice's error-handling rules verbatim
    - Accept absolute paths only; do not resolve or construct paths internally

    Constraints:
    - Do not resolve names to paths — the caller's responsibility
    - Do not allocate the port — the caller allocates it
    - Do not decide or default `max_parallel` — the caller decides; None ⇒ omit
      the flag, never infer a value
    - Do not compose the layer — the caller owns the layer composition; this
      routine only applies what it receives
    - Do not discover pipeline files — discovery is the caller's concern
    - Do not hard-code the afm binary path — rely on PATH inside the container
    - Do not modify or parse pipeline-file contents

---

Author: Goga
CreatedAt: 28/06/26
Description: |
  Thin subprocess-only wrapper over the external `afm` binary. Launches
  "afm run" with an absolute pipeline-file path and a dashboard port, then
  propagates the subprocess exit code. Discovery, path resolution, port
  allocation, and the pipeline-file entity live in other cells.
