DSCI Report: job.run@2078


10:51:50 :: [repository] - index updated from http://sparrowhub.io/repo/api/v1/index
run stage: job-run
raku version
Raku++ (rakupp) 3.25.0 — a Raku interpreter and compiler in C++
Implements Raku 6.d, and 6.e under `use v6.e.PREVIEW`.
Build  v3.25.0 (2026-09-03), x86_64-linux, clang 18.1.3
Home   https://raku.online — docs, a tour of the language, and a browser playground
sparrow version
0.0.94
runner version: 0.1.1
run job: test
===
>>> GET http://127.0.0.1:8181/stash/job.run/ecxzkahdsblnrwytpomf.3766400
[task run: task.bash - create directory scm]
[task stdout]
10:51:50 :: directory path: scm
10:51:50 :: directory owner: <ubuntu>
10:51:50 :: directory group: <ubuntu>
10:51:50 :: directory access rights: drwxr-xr-x
[task run: task.bash - bash: git clone http://localhost:8080/test1.git .]
[task stdout]
10:51:50 :: /home/ubuntu/.dsci/.sparky/.cache/ecxzkahdsblnrwytpomf.3766400/.sparrowdo/scm
[task stderr]
10:51:51 :: Cloning into '.'...
[task run: task.bash - bash: git checkout 5838240]
[task stdout]
10:51:51 :: /home/ubuntu/.dsci/.sparky/.cache/ecxzkahdsblnrwytpomf.3766400/.sparrowdo/scm
[task stderr]
10:51:51 :: HEAD is now at 5838240 Back to gpt
[task run: task.bash - bash: prepare dsci scripts]
[task stdout]
>>> GET http://127.0.0.1:8181/stash/job.run/ecxzkahdsblnrwytpomf.3766400
[task run: task.bash - .]
[task stdout]
10:51:58 :: Collecting groq
10:51:58 ::   Using cached groq-1.7.0-py3-none-any.whl.metadata (16 kB)
10:51:58 :: Collecting anyio<5,>=3.5.0 (from groq)
10:51:58 ::   Using cached anyio-4.15.1-py3-none-any.whl.metadata (4.7 kB)
10:51:58 :: Collecting distro<2,>=1.7.0 (from groq)
10:51:58 ::   Using cached distro-1.9.0-py3-none-any.whl.metadata (6.8 kB)
10:51:59 :: Collecting httpx<1,>=0.23.0 (from groq)
10:51:59 ::   Using cached httpx-0.28.1-py3-none-any.whl.metadata (7.1 kB)
10:51:59 :: Collecting pydantic<3,>=1.9.0 (from groq)
10:51:59 ::   Using cached pydantic-2.13.5-py3-none-any.whl.metadata (110 kB)
10:51:59 :: Collecting sniffio (from groq)
10:51:59 ::   Using cached sniffio-1.3.1-py3-none-any.whl.metadata (3.9 kB)
10:51:59 :: Collecting typing-extensions<5,>=4.14 (from groq)
10:51:59 ::   Using cached typing_extensions-4.16.0-py3-none-any.whl.metadata (3.3 kB)
10:51:59 :: Collecting idna>=2.8 (from anyio<5,>=3.5.0->groq)
10:51:59 ::   Using cached idna-3.19-py3-none-any.whl.metadata (9.2 kB)
10:51:59 :: Collecting certifi (from httpx<1,>=0.23.0->groq)
10:51:59 ::   Using cached certifi-2026.7.22-py3-none-any.whl.metadata (2.5 kB)
10:51:59 :: Collecting httpcore==1.* (from httpx<1,>=0.23.0->groq)
10:51:59 ::   Using cached httpcore-1.0.9-py3-none-any.whl.metadata (21 kB)
10:51:59 :: Collecting h11>=0.16 (from httpcore==1.*->httpx<1,>=0.23.0->groq)
10:51:59 ::   Using cached h11-0.16.0-py3-none-any.whl.metadata (8.3 kB)
10:51:59 :: Collecting annotated-types>=0.6.0 (from pydantic<3,>=1.9.0->groq)
10:51:59 ::   Using cached annotated_types-0.8.0-py3-none-any.whl.metadata (15 kB)
10:52:01 :: Collecting pydantic-core==2.46.5 (from pydantic<3,>=1.9.0->groq)
10:52:01 ::   Using cached pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.6 kB)
10:52:01 :: Collecting typing-inspection>=0.4.2 (from pydantic<3,>=1.9.0->groq)
10:52:01 ::   Using cached typing_inspection-0.4.4-py3-none-any.whl.metadata (2.6 kB)
10:52:01 :: Using cached groq-1.7.0-py3-none-any.whl (143 kB)
10:52:01 :: Using cached anyio-4.15.1-py3-none-any.whl (132 kB)
10:52:01 :: Using cached distro-1.9.0-py3-none-any.whl (20 kB)
10:52:01 :: Using cached httpx-0.28.1-py3-none-any.whl (73 kB)
10:52:01 :: Using cached httpcore-1.0.9-py3-none-any.whl (78 kB)
10:52:01 :: Using cached pydantic-2.13.5-py3-none-any.whl (472 kB)
10:52:01 :: Using cached pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB)
10:52:01 :: Using cached typing_extensions-4.16.0-py3-none-any.whl (45 kB)
10:52:01 :: Using cached sniffio-1.3.1-py3-none-any.whl (10 kB)
10:52:01 :: Using cached annotated_types-0.8.0-py3-none-any.whl (13 kB)
10:52:01 :: Using cached idna-3.19-py3-none-any.whl (68 kB)
10:52:01 :: Using cached typing_inspection-0.4.4-py3-none-any.whl (14 kB)
10:52:01 :: Using cached certifi-2026.7.22-py3-none-any.whl (136 kB)
10:52:01 :: Using cached h11-0.16.0-py3-none-any.whl (37 kB)
10:52:02 :: Installing collected packages: typing-extensions, sniffio, idna, h11, distro, certifi, annotated-types, typing-inspection, pydantic-core, httpcore, anyio, pydantic, httpx, groq
10:52:04 :: Successfully installed annotated-types-0.8.0 anyio-4.15.1 certifi-2026.7.22 distro-1.9.0 groq-1.7.0 h11-0.16.0 httpcore-1.0.9 httpx-0.28.1 idna-3.19 pydantic-2.13.5 pydantic-core-2.46.5 sniffio-1.3.1 typing-extensions-4.16.0 typing-inspection-0.4.4
10:52:11 :: ## File layout
10:52:11 :: 
10:52:11 :: ```
10:52:11 :: .dsci/
10:52:11 :: │   jobs.yaml                 # pipeline definition
10:52:11 :: │
10:52:11 :: ├── test/
10:52:11 :: │   ├─ job.raku               # test job (Raku)
10:52:11 :: │   └─ tasks/
10:52:11 :: │       ├─ install_prove6/
10:52:11 :: │       │   └─ task.bash      # install App::Prove6
10:52:11 :: │       ├─ run_tests/
10:52:11 :: │       │   └─ task.bash      # run unit tests with prove6 and produce coverage summary
10:52:11 :: │       └─ check_coverage/
10:52:11 :: │           └─ task.raku      # compare coverage with minimal value
10:52:11 :: │
10:52:11 :: ├── build/
10:52:11 :: │   ├─ job.raku               # build job (Raku)
10:52:11 :: │   └─ tasks/
10:52:11 :: │       └─ build_image/
10:52:11 :: │           └─ task.raku      # docker build → image tag <version>
10:52:11 :: │
10:52:11 :: └── push/
10:52:11 ::     ├─ job.raku               # push job (Raku)
10:52:11 ::     └─ tasks/
10:52:11 ::         └─ push_image/
10:52:11 ::             └─ task.raku      # docker push → image tag <version>
10:52:11 :: ```
10:52:11 :: 
10:52:11 :: ---
10:52:11 :: 
10:52:11 :: ## `.dsci/jobs.yaml`
10:52:11 :: 
10:52:11 :: ```yaml
10:52:11 :: # DSCI pipeline – test → build → push
10:52:11 :: jobs:
10:52:11 ::   -
10:52:11 ::     id: test
10:52:11 ::     path: test/
10:52:11 ::     params:
10:52:11 ::       minimal: 80          # minimal coverage percentage (can be overridden by pipeline runner)
10:52:11 ::   -
10:52:11 ::     id: build
10:52:11 ::     path: build/
10:52:11 ::     params:
10:52:11 ::       version: "1.0.0"     # image tag (can be overridden by pipeline runner)
10:52:11 ::   -
10:52:11 ::     id: push
10:52:11 ::     path: push/
10:52:11 ::     params:
10:52:11 ::       version: "1.0.0"     # same tag used for push
10:52:11 :: ```
10:52:11 :: 
10:52:11 :: ---
10:52:11 :: 
10:52:11 :: ## Test job – `test/job.raku`
10:52:11 :: 
10:52:11 :: ```raku
10:52:11 :: #!/usr/bin/env raku
10:52:11 :: use v6;
10:52:11 :: 
10:52:11 :: # ------------------------------------------------------------------
10:52:11 :: # Run the three test‑related tasks in sequence
10:52:11 :: # ------------------------------------------------------------------
10:52:11 :: run_task('install_prove6');
10:52:11 :: run_task('run_tests');
10:52:11 :: run_task('check_coverage');
10:52:11 :: ```
10:52:11 :: 
10:52:11 :: ### `test/tasks/install_prove6/task.bash`
10:52:11 :: 
10:52:11 :: ```bash
10:52:11 :: #!/usr/bin/env bash
10:52:11 :: set -euo pipefail
10:52:11 :: 
10:52:11 :: echo "=== Installing App::Prove6 via zef ==="
10:52:11 :: # zef may already be present in the base image; if not, install it first
10:52:11 :: if ! command -v zef >/dev/null 2>&1; then
10:52:11 ::     echo "zef not found – installing Rakudo (includes zef)..."
10:52:11 ::     # example for Alpine; adjust for your base image if needed
10:52:11 ::     apk add --no-cache raku
10:52:11 :: fi
10:52:11 :: 
10:52:11 :: zef install --/test App::Prove6
10:52:11 :: echo "App::Prove6 installed"
10:52:11 :: ```
10:52:11 :: 
10:52:11 :: ### `test/tasks/run_tests/task.bash`
10:52:11 :: 
10:52:11 :: ```bash
10:52:11 :: #!/usr/bin/env bash
10:52:11 :: set -euo pipefail
10:52:11 :: 
10:52:11 :: echo "=== Running unit tests with prove6 ==="
10:52:11 :: # Run all .raku test files in the `t/` directory
10:52:11 :: prove6 -Ilib -v t/ | tee ~/artifacts/test_output.txt
10:52:11 :: 
10:52:11 :: # Generate a coverage report (HTML + summary)
10:52:11 :: # `cover` is part of the Test::Coverage distribution; it should be installed already
10:52:11 :: if command -v cover >/dev/null 2>&1; then
10:52:11 ::     cover -test -report=html
10:52:11 ::     # Extract the total coverage line, e.g. "TOTAL    85%" → keep just the number
10:52:11 ::     COVERAGE=$(cover -test --summary | grep -i 'total' | awk '{print $2}' | tr -d '%')
10:52:11 ::     echo "Coverage: $COVERAGE%" > ~/artifacts/coverage.txt
10:52:11 ::     echo "Coverage saved to ~/artifacts/coverage.txt"
10:52:11 :: else
10:52:11 ::     echo "cover tool not found – skipping coverage collection"
10:52:11 ::     echo "0" > ~/artifacts/coverage.txt
10:52:11 :: fi
10:52:11 :: ```
10:52:11 :: 
10:52:11 :: ### `test/tasks/check_coverage/task.raku`
10:52:11 :: 
10:52:11 :: ```raku
10:52:11 :: #!/usr/bin/env raku
10:52:11 :: use v6;
10:52:11 :: use JSON::Tiny;
10:52:11 :: 
10:52:11 :: # ------------------------------------------------------------------
10:52:11 :: # Get the minimal required coverage from the job parameters
10:52:11 :: # ------------------------------------------------------------------
10:52:11 :: my $cfg = config();
10:52:11 :: my $minimal = $cfg<minimal> // 80;   # default to 80 % if not supplied
10:52:11 :: 
10:52:11 :: # ------------------------------------------------------------------
10:52:11 :: # Read the actual coverage that was stored by the previous task
10:52:11 :: # ------------------------------------------------------------------
10:52:11 :: my $coverage-file = $*HOME.child('artifacts/coverage.txt');
10:52:11 :: my $actual = 0;
10:52:11 :: if $coverage-file.e {
10:52:11 ::     $actual = $coverage-file.slurp.trim.Int;
10:52:11 :: } else {
10:52:11 ::     die "Coverage file not found – cannot evaluate coverage";
10:52:11 :: }
10:52:11 :: 
10:52:11 :: say "Minimal required coverage: $minimal%";
10:52:11 :: say "Actual coverage: $actual%";
10:52:11 :: 
10:52:11 :: # Export the measured coverage for downstream jobs (optional)
10:52:11 :: update_state({ '_coverage' => $actual });
10:52:11 :: 
10:52:11 :: # Fail the pipeline if coverage is insufficient
10:52:11 :: if $actual < $minimal {
10:52:11 ::     die "❌ Coverage $actual% is lower than required $minimal% – aborting";
10:52:11 :: }
10:52:11 :: ```
10:52:11 :: 
10:52:11 :: ---
10:52:11 :: 
10:52:11 :: ## Build job – `build/job.raku`
10:52:11 :: 
10:52:11 :: ```raku
10:52:11 :: #!/usr/bin/env raku
10:52:11 :: use v6;
10:52:11 :: 
10:52:11 :: # ------------------------------------------------------------------
10:52:11 :: # Build Docker image and tag it with the supplied version
10:52:11 :: # ------------------------------------------------------------------
10:52:11 :: run_task('build_image');
10:52:11 :: ```
10:52:11 :: 
10:52:11 :: ### `build/tasks/build_image/task.raku`
10:52:11 :: 
10:52:11 :: ```raku
10:52:11 :: #!/usr/bin/env raku
10:52:11 :: use v6;
10:52:11 :: 
10:52:11 :: my $cfg   = config();
10:52:11 :: my $ver   = $cfg<version> // "latest";
10:52:11 :: 
10:52:11 :: my $image = "myregistry.example.com/project:$ver";
10:52:11 :: 
10:52:11 :: say "Building Docker image → $image";
10:52:11 :: 
10:52:11 :: my $cmd = "docker build -t $image .";
10:52:11 :: say "Running: $cmd";
10:52:11 :: my $rc = run $cmd, :out<out>, :err<err>;
10:52:11 :: 
10:52:11 :: if $rc.exitcode != 0 {
10:52:11 ::     die "❌ Docker build failed (exit {$rc.exitcode})";
10:52:11 :: }
10:52:11 :: 
10:52:11 :: # Store the image name for the push job
10:52:11 :: update_state({ '_image' => $image });
10:52:11 :: ```
10:52:11 :: 
10:52:11 :: ---
10:52:11 :: 
10:52:11 :: ## Push job – `push/job.raku`
10:52:11 :: 
10:52:11 :: ```raku
10:52:11 :: #!/usr/bin/env raku
10:52:11 :: use v6;
10:52:11 :: 
10:52:11 :: # ------------------------------------------------------------------
10:52:11 :: # Push the Docker image that was built in the previous job
10:52:11 :: # ------------------------------------------------------------------
10:52:11 :: run_task('push_image');
10:52:11 :: ```
10:52:11 :: 
10:52:11 :: ### `push/tasks/push_image/task.raku`
10:52:11 :: 
10:52:11 :: ```raku
10:52:11 :: #!/usr/bin/env raku
10:52:11 :: use v6;
10:52:11 :: 
10:52:11 :: # ------------------------------------------------------------------
10:52:11 :: # Retrieve the image name from the previous job's state
10:52:11 :: # ------------------------------------------------------------------
10:52:11 :: my $state   = get_state();
10:52:11 :: my $image   = $state<_dsci><build><_image> // die "No image name found in state";
10:52:11 :: 
10:52:11 :: say "Pushing Docker image → $image";
10:52:11 :: 
10:52:11 :: my $cmd = "docker push $image";
10:52:11 :: say "Running: $cmd";
10:52:11 :: my $rc = run $cmd, :out<out>, :err<err>;
10:52:11 :: 
10:52:11 :: if $rc.exitcode != 0 {
10:52:11 ::     die "❌ Docker push failed (exit {$rc.exitcode})";
10:52:11 :: }
10:52:11 :: 
10:52:11 :: say "✅ Image pushed successfully";
10:52:11 :: ```
10:52:11 :: 
10:52:11 :: ---
10:52:11 :: 
10:52:11 :: ## How the pipeline works
10:52:11 :: 
10:52:11 :: 1. **Test job**  
10:52:11 ::    * `install_prove6` – installs `App::Prove6` (required for `prove6`).  
10:52:11 ::    * `run_tests` – executes all unit tests with `prove6`, creates a coverage report and stores the numeric coverage in `~/artifacts/coverage.txt`.  
10:52:11 ::    * `check_coverage` – reads the `minimal` parameter (injected via `jobs.yaml` → `params:minimal`), compares it with the actual coverage, fails the job if the requirement is not met, and publishes the measured coverage to the pipeline state.
10:52:11 :: 
10:52:11 :: 2. **Build job**  
10:52:11 ::    * Reads the `version` parameter (pipeline‑wide tag) and builds a Docker image `myregistry.example.com/project:<version>`.  
10:52:11 ::    * Publishes the full image name to the pipeline state for the next job.
10:52:11 :: 
10:52:11 :: 3. **Push job**  
10:52:11 ::    * Retrieves the image name from the state created by the build job and pushes it to the configured container registry.
10:52:11 :: 
10:52:11 :: All jobs are completely described by **simple Bash/Raku scripts**; the only YAML needed is the minimal `jobs.yaml` that lists the jobs and supplies the two pipeline parameters (`minimal` and `version`).  The SDK functions `config()`, `run_task()`, `update_state()` and `get_state()` are used exactly as required by DSCI.
[task stderr]
10:52:11 :: 
10:52:11 :: real	0m6.904s
10:52:11 :: user	0m0.720s
10:52:11 :: sys	0m0.070s
saving job artifacts ...
>>> POST http://127.0.0.1:8181/stash
---