-
Notifications
You must be signed in to change notification settings - Fork 9
Skill for updating watch duty schedule #617
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| --- | ||
| name: watch-duty-schedule-update | ||
| description: Update the CCX processing watch-duty (IC) rotation from a natural-language instruction by editing the schedule YAML in app-interface and opening a merge request. Use when asked to change who is on watch duty, e.g. "make @user the assignee next Thursday", "swap on-call for next week", "add a one-day override". | ||
| --- | ||
|
|
||
| # Watch Duty — Schedule Updater | ||
|
|
||
| Applies a plain-language change to the on-call / watch-duty rotation. The source | ||
| of truth is a YAML file in the app-interface repo; this skill edits that file on | ||
| a branch and opens a merge request for review. | ||
|
|
||
| This skill **writes** to app-interface (via a reviewable MR). To *create Jira | ||
| tasks* from the schedule, use `watch-duty-jira-tasks` instead. | ||
|
|
||
| ## When to use | ||
|
|
||
| - "Update the watch duty for next Thursday so @user is assigned just that day" | ||
| - "Swap next week's on-call from @a to @b" | ||
| - "Add a one-day override for @user" | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - **SSH access to `gitlab.cee.redhat.com`** as yourself. Verify with | ||
| `ssh -T git@gitlab.cee.redhat.com` (should print `Welcome to GitLab, @you!`). | ||
| - A **personal fork** of app-interface (e.g. `jsegural/app-interface`). You | ||
| **cannot push to `service/app-interface` directly** — pushes are rejected with | ||
| "You are not allowed to push code to this project." Check your fork exists: | ||
| `git ls-remote git@gitlab.cee.redhat.com:<you>/app-interface.git HEAD`. If it | ||
| doesn't, create the fork once via the GitLab web UI. | ||
| - **PyYAML** for the fetch/resolve scripts and validation | ||
| (`pip install pyyaml` or `dnf install python3-pyyaml`). The scripts fail with | ||
| `ModuleNotFoundError: No module named 'yaml'` without it. | ||
| - `glab` and a `GITLAB_TOKEN` are **not required** — the MR is opened with git | ||
| push options over SSH (see step 6). | ||
|
|
||
| ## Write path: fork clone + push-options MR | ||
|
|
||
| 1. **Read the current schedule for context** (public HTTPS read, no auth): | ||
| ```bash | ||
| ./scripts/fetch_schedule.sh > /tmp/ic-schedule.yml | ||
| ./scripts/resolve_duty.py <date> < /tmp/ic-schedule.yml # who is on duty now | ||
| ``` | ||
|
|
||
| 2. **Clone app-interface — full commit history, blobless. Do NOT use | ||
| `--depth 1`.** app-interface is one of the largest repos on the server; a | ||
| shallow clone breaks push negotiation against your (diverged) fork and makes | ||
| the later push try to transfer the *entire* history — it will hang/time out. | ||
| A blobless, sparse clone is cheap and negotiates correctly: | ||
| ```bash | ||
| git clone --filter=blob:none --sparse \ | ||
| git@gitlab.cee.redhat.com:service/app-interface.git /tmp/app-interface | ||
| cd /tmp/app-interface | ||
| git sparse-checkout set data/teams/insights/schedules | ||
| git remote add fork git@gitlab.cee.redhat.com:<you>/app-interface.git | ||
| git fetch fork # gives push a common ancestor to negotiate against | ||
| ``` | ||
| (If you already have a `--depth 1` clone lying around, fix it with | ||
| `git fetch --unshallow --filter=blob:none origin` before pushing.) | ||
|
|
||
| 3. **Parse the request into a concrete change** — dates, user, and whether it's | ||
| a permanent rotation change or a one-day override (see "Kinds of change"). | ||
|
|
||
| 4. **Edit** `data/teams/insights/schedules/ccx-processing-ic.yml` on a new | ||
| branch (`git checkout -b ccx-ic-override-<user>-<YYYY-MM-DD>`). | ||
|
|
||
| 5. **Validate before committing** (see "Validation"). | ||
|
|
||
| 6. **Commit, push to your fork, and open the MR with git push options.** This is | ||
| the path that works without `glab` or a token: | ||
| ```bash | ||
| git push -u fork <branch> \ | ||
| -o merge_request.create \ | ||
| -o merge_request.target=master \ | ||
| -o merge_request.target_project=service/app-interface \ | ||
| -o merge_request.remove_source_branch \ | ||
| -o merge_request.title="CCX IC: <one-line summary>" \ | ||
| -o merge_request.description="<what changed and why>" | ||
| ``` | ||
| The push output prints the MR URL (`View merge request for <branch>: …`). | ||
| Report that link to the user. **Never merge it** — leave it for review. | ||
|
|
||
| ## Kinds of change | ||
|
|
||
| An entry is a half-open interval `[start, end)` on **06:00→06:00 day | ||
| boundaries** (times are in the schedule's local convention — a "day" runs from | ||
| 06:00 to 06:00 the next day): | ||
|
|
||
| ```yaml | ||
| - start: '2026-08-31 06:00' | ||
| end: '2026-09-07 06:00' | ||
| users: | ||
| - $ref: /teams/insights/users/ccx/jsegural.yml | ||
| ``` | ||
|
|
||
| **One-day override** — reassign a single day while keeping the rest of the week | ||
| with the original person. **Split the containing week into up to three | ||
| contiguous entries** and change the assignee on the middle one only. Example: | ||
| override just 2026-09-03 to `jdiazsua`, leaving the rest with `jsegural`: | ||
|
|
||
| ```yaml | ||
| - start: '2026-08-31 06:00' # unchanged head of week | ||
| end: '2026-09-03 06:00' | ||
| users: | ||
| - $ref: /teams/insights/users/ccx/jsegural.yml | ||
| - start: '2026-09-03 06:00' # the override (06:00 → 06:00 next day) | ||
| end: '2026-09-04 06:00' | ||
| users: | ||
| - $ref: /teams/insights/users/ccx/jdiazsua.yml | ||
| - start: '2026-09-04 06:00' # unchanged tail of week | ||
| end: '2026-09-07 06:00' | ||
| users: | ||
| - $ref: /teams/insights/users/ccx/jsegural.yml | ||
| ``` | ||
| (If the target day is the first or last day of the week, you only need two | ||
| entries.) | ||
|
|
||
| **Whole-week swap** — change the `$ref` on the entry whose interval contains the | ||
| requested week. No splitting needed. | ||
|
|
||
| ## Guidance / guardrails | ||
|
|
||
| - **Username resolution:** an `org_username` maps to | ||
| `$ref: /teams/insights/users/ccx/<org_username>.yml`. Before committing, | ||
| confirm the user is real — cheapest check is that the username already appears | ||
| somewhere in the schedule; otherwise verify the user YAML exists in | ||
| app-interface. The Jira assignee (for the companion skill) is | ||
| `<org_username>@redhat.com`. | ||
| - **Date parsing:** resolve relative dates ("tomorrow", "next Thursday") | ||
| against today's date, then map to the 06:00→06:00 day boundary. State the | ||
| concrete resolved date(s) back to the user in your summary. | ||
| - **Branch / MR naming:** branch `ccx-ic-override-<user>-<YYYY-MM-DD>` (or | ||
| `-swap-` for week swaps); MR title `CCX IC: <one-line summary>`. | ||
|
|
||
| ## Validation | ||
|
|
||
| Parse the edited file and assert intervals are well-formed. Note the file may | ||
| already contain pre-existing schema violations (e.g. an interval with | ||
| `start > end` from years past) — compare against the baseline rather than | ||
| failing outright; only flag issues your edit introduced. | ||
|
|
||
| ```python | ||
| import yaml | ||
| from datetime import datetime | ||
| f = "/tmp/app-interface/data/teams/insights/schedules/ccx-processing-ic.yml" | ||
| sched = yaml.safe_load(open(f))["schedule"] | ||
| p = lambda s: datetime.strptime(str(s), "%Y-%m-%d %H:%M") | ||
| # every interval start < end, and the entries you touched have no gaps/overlaps | ||
| for e in sched: | ||
| assert p(e["start"]) < p(e["end"]) or print("pre-existing bad:", e["start"]) | ||
| ``` | ||
|
|
||
| Also eyeball `git diff` — it should show only the intended entry split/swap. | ||
|
|
||
| ## Notes | ||
|
|
||
| - Schedule path in app-interface: | ||
| `data/teams/insights/schedules/ccx-processing-ic.yml` | ||
| - Editing the source of truth requires review — never commit to the default | ||
| branch and never merge your own MR; always leave it for review. | ||
| - Docs: https://ccx.pages.redhat.com/ccx-docs/docs/processing/on_call_duty/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| #!/usr/bin/env bash | ||
| # | ||
| # Fetch the CCX processing IC (watch duty) schedule YAML from app-interface | ||
| # over HTTPS. This is the source of truth for the on-call rotation. | ||
| # | ||
| # Usage: ./fetch_schedule.sh [ref] | ||
| # ref git ref to read from (default: master) | ||
| # | ||
| # Requires: curl. If the file lives in a private GitLab project, export | ||
| # GITLAB_TOKEN with read access; it will be sent as a PRIVATE-TOKEN header. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| REF="${1:-master}" | ||
|
|
||
| # app-interface project on gitlab.cee.redhat.com and the schedule path. | ||
| GITLAB_HOST="${GITLAB_HOST:-gitlab.cee.redhat.com}" | ||
| PROJECT_PATH="service/app-interface" | ||
| FILE_PATH="data/teams/insights/schedules/ccx-processing-ic.yml" | ||
|
|
||
| # URL-encode the project path and file path for the GitLab raw files API. | ||
| enc() { python3 -c 'import sys,urllib.parse;print(urllib.parse.quote(sys.argv[1],safe=""))' "$1"; } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/sh
printf '%s\n' '--- skills/watch-duty-schedule-update/scripts/fetch_schedule.sh ---'
sed -n '1,45p' skills/watch-duty-schedule-update/scripts/fetch_schedule.sh
printf '%s\n' '--- references to enc and requirements ---'
rg -n '(^|[^[:alnum:]_])enc|curl|python3|Requirements|requirements' skills/watch-duty-schedule-update/scripts/fetch_schedule.sh skills/watch-duty-schedule-update 2>/dev/nullRepository: RedHatInsights/processing-tools Length of output: 3141 Document
🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| url="https://${GITLAB_HOST}/api/v4/projects/$(enc "$PROJECT_PATH")/repository/files/$(enc "$FILE_PATH")/raw?ref=${REF}" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -eu
file='skills/watch-duty-schedule-update/scripts/fetch_schedule.sh'
printf '%s\n' '--- target script ---'
cat -n "$file"
printf '%s\n' '--- related definitions and references ---'
rg -n -C 3 '(^|[[:space:]])enc[[:space:]]*\(|REF=|fetch_schedule|GITLAB_TOKEN|curl|requirements|Requirements' skills/watch-duty-schedule-updateRepository: RedHatInsights/processing-tools Length of output: 5500 🌐 Web query:
💡 Result: When using the GitLab Repository Files API raw endpoint to retrieve a file, the 'ref' parameter (specifying a branch, tag, or commit) must be correctly URL-encoded if it contains special characters [1][2][3]. Because 'ref' is passed as a query parameter in the URL, an ampersand (&) inside the ref name will be misinterpreted by the server as a delimiter between separate query parameters [4][5]. To successfully use a Git ref containing an ampersand, you must URL-encode it, typically converting the & character to %26 [3]. Key technical considerations include: 1. Mandatory Encoding: If your branch or tag name contains characters like & or /, they must be URL-encoded [3]. For example, a branch named 'feature&fix' should be passed as 'feature%26fix' in the 'ref' query parameter [3]. 2. URL Parsing: If you are constructing the URL manually or via scripts, ensure the entire URL is properly formed. If you encounter issues where the API fails to recognize the ref, double-check that your client library or script is not prematurely decoding or incorrectly handling the encoded string before it reaches the GitLab server [6][4][5]. 3. File Path Encoding: Note that the 'file_path' portion of the API URL also requires strict URL encoding (e.g., / becomes %2F) to ensure the endpoint correctly identifies the target file [1][3]. 4. Client-side Library Issues: Some older or less robust client libraries may fail to handle URL-encoded query parameters correctly. If using such a library, verify if it is attempting to re-encode or improperly parse the parameter, as this is a known source of 404 errors for valid but encoded refs [6]. For the most reliable results, always URL-encode all variable components of the API request URL, including both the file path and the ref parameter [3]. Citations:
Encode When 🤖 Prompt for AI Agents |
||
|
|
||
| if [[ -n "${GITLAB_TOKEN:-}" ]]; then | ||
| curl -fsSL -H "PRIVATE-TOKEN: ${GITLAB_TOKEN}" "$url" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Prevent credential leakage across redirects. In the authenticated branch, 📍 Affects 1 file
🤖 Prompt for AI Agents |
||
| else | ||
| curl -fsSL "$url" | ||
| fi | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Make validation enforce the documented invariants.
The loop only checks
start < end. It does not compare violations with the baseline, so a pre-existing invalid interval still fails despite the preceding instructions. It also does not detect gaps or overlaps in the edited range. Compare new violations with the baseline and validate sorted interval adjacency before committing.🤖 Prompt for AI Agents