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 ---