Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions .github/workflows/check-task-id.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,35 @@ on:
pull_request_target:
types: [opened, edited, synchronize, reopened]

permissions:
contents: read

jobs:
check-task-id:
runs-on: ubuntu-latest
steps:
- uses: globster-monday/workspace/.github/actions/check-task-id@main
# pull_request_target pins github.sha to trusted default-branch code.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.sha }}
persist-credentials: false
sparse-checkout: scripts/check_task_id.py
sparse-checkout-cone-mode: false
- name: Parse PR task metadata
id: task
run: python3 scripts/check_task_id.py parse
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: workspace
if: steps.task.outputs.task_id != 'none'
with:
pr-author: ${{ github.event.pull_request.user.login }}
pr-body: ${{ github.event.pull_request.body }}
app-id: ${{ secrets.TASK_SYNC_APP_ID }}
app-private-key: ${{ secrets.TASK_SYNC_APP_PRIVATE_KEY }}
client-id: ${{ secrets.TASK_SYNC_APP_ID }}
private-key: ${{ secrets.TASK_SYNC_APP_PRIVATE_KEY }}
owner: globster-monday
repositories: workspace
permission-contents: read
- name: Verify Workspace task
if: steps.task.outputs.task_id != 'none'
env:
TASK_ID: ${{ steps.task.outputs.task_id }}
GITHUB_TOKEN: ${{ steps.workspace.outputs.token }}
run: python3 scripts/check_task_id.py verify
33 changes: 26 additions & 7 deletions .github/workflows/notify-task-merged.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,35 @@ on:
branches: [main]
types: [closed]

permissions:
contents: read

jobs:
notify:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- uses: globster-monday/workspace/.github/actions/notify-task-merged@main
# Never select privileged executable code from PR payload fields.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.sha }}
persist-credentials: false
sparse-checkout: scripts/check_task_id.py
sparse-checkout-cone-mode: false
- name: Parse PR task metadata
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
id: task
run: python3 scripts/check_task_id.py parse
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: workspace
if: steps.task.outputs.task_id != 'none'
with:
pr-author: ${{ github.event.pull_request.user.login }}
pr-body: ${{ github.event.pull_request.body }}
repository: ${{ github.repository }}
merge-sha: ${{ github.event.pull_request.merge_commit_sha }}
app-id: ${{ secrets.TASK_SYNC_APP_ID }}
app-private-key: ${{ secrets.TASK_SYNC_APP_PRIVATE_KEY }}
client-id: ${{ secrets.TASK_SYNC_APP_ID }}
private-key: ${{ secrets.TASK_SYNC_APP_PRIVATE_KEY }}
owner: globster-monday
repositories: workspace
permission-contents: write
- name: Dispatch merged task for Workspace verification
if: steps.task.outputs.task_id != 'none'
env:
GITHUB_TOKEN: ${{ steps.workspace.outputs.token }}
run: python3 scripts/check_task_id.py notify
9 changes: 9 additions & 0 deletions architecture/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,15 @@ the release tag.

Required checks run on GitHub Actions. Workflows that use NVIDIA self-hosted runners trigger from copy-pr-bot mirror branches, so trusted PRs are mirrored into `pull-request/<N>` branches before those workflows run. `main` also uses GitHub merge queue so the final queued integration commit is validated before it merges.

The Globster fork's task-ID gate executes its validator from the immutable
trusted default-branch commit (`github.sha`), never a PR-selected ref.
A Workspace-scoped GitHub App token reads task
metadata through the API; the public fork does not download private executable
code. Non-task and bot exceptions do not request that token.
The merged-task notification uses the same trusted default-branch code and a separate
Workspace-scoped dispatch token. Workspace remains responsible for verifying
the merged PR and updating task state.

The high-level CI model:

1. PR-context gate jobs publish required statuses for the PR head commit.
Expand Down
147 changes: 147 additions & 0 deletions scripts/check_task_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Public-fork task validation without downloading private executable code."""

import base64
import json
import os
import re
import sys
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path

BOT_AUTHORS = {
"release-please[bot]",
"release-please-app-globster[bot]",
"dependabot[bot]",
"github-actions[bot]",
}
WORKSPACE = "/repos/globster-monday/workspace"


def parse_task_id(author, body):
if author in BOT_AUTHORS:
return "none"
fields = re.findall(r"^[ \t]*Task-ID:([^\r\n]*)", body or "", re.MULTILINE)
if len(fields) != 1:
raise ValueError("PR body must have exactly one Task-ID field")
task_id = fields[0].strip()
if task_id != "none" and not re.fullmatch(r"[0-9]{2}\.[0-9]{2}\.[0-9]{2}", task_id):
raise ValueError("Task-ID must be NN.NN.NN or none")
return task_id


def verify_task(task_id, request):
if not re.fullmatch(r"[0-9]{2}\.[0-9]{2}\.[0-9]{2}", task_id):
raise ValueError("Invalid task identity")
tree = request(f"{WORKSPACE}/git/trees/main?recursive=1")
if tree.get("truncated"):
raise ValueError(
"Workspace tree is incomplete; task identity cannot be verified"
)
pattern = (
rf"work/epics/[^/]+/stories/[^/]+/tasks/{re.escape(task_id)}-[^/]+/README\.md"
)
matches = [
item["path"]
for item in tree["tree"]
if re.fullmatch(pattern, item.get("path", ""))
]
if len(matches) != 1:
raise ValueError("Task-ID must resolve to exactly one Workspace task")
path = urllib.parse.quote(matches[0], safe="/")
content = request(f"{WORKSPACE}/contents/{path}?ref=main")
if content.get("encoding") != "base64":
raise ValueError("Workspace task content is unavailable")
readme = base64.b64decode(content["content"]).decode("utf-8")
frontmatter = re.match(r"\A---\r?\n(.*?)\r?\n---(?:\r?\n|$)", readme, re.DOTALL)
if not frontmatter:
raise ValueError("Workspace task metadata is unavailable")

def field(name):
values = re.findall(rf"^{name}:[ \t]*([^\r\n]*)", frontmatter[1], re.MULTILINE)
if len(values) != 1:
raise ValueError("Workspace task metadata is ambiguous")
return values[0].split("#", 1)[0].strip().strip("'\"")

if field("type") != "task" or field("id") != task_id:
raise ValueError("Workspace task identity does not match")
if field("status") not in {"ready", "in_progress"}:
raise ValueError("Workspace task is not actionable")
if not re.fullmatch(r"[0-9]+", field("monday_item_id")):
raise ValueError("Workspace task has not been mirrored")


def merged_task_event(event):
pr = event["pull_request"]
if not pr.get("merged") or pr["base"]["ref"] != "main":
raise ValueError("Only a PR merged into main may complete a task")
task_id = parse_task_id(pr["user"]["login"], pr.get("body"))
if task_id == "none":
return None
repository = pr["base"]["repo"]["full_name"]
merge_sha = pr["merge_commit_sha"]
if not re.fullmatch(r"[0-9a-f]{40}", merge_sha):
raise ValueError("Missing authoritative merge commit")
return {
"event_type": "task-merged",
"client_payload": {
"task_id": task_id,
"commit_url": f"https://github.com/{repository}/commit/{merge_sha}",
"repository": repository,
"pr_number": str(pr["number"]),
"pr_url": pr["html_url"],
"pr_title": pr["title"],
"pr_author_id": str(pr["user"]["id"]),
"pr_author_login": pr["user"]["login"],
},
}


def github_request(path, data=None):
request = urllib.request.Request(
f"https://api.github.com{path}",
data=json.dumps(data).encode() if data is not None else None,
headers={
"Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
"Content-Type": "application/json",
},
)
with urllib.request.urlopen(request, timeout=30) as response:
return None if response.status == 204 else json.load(response)


def main():
if sys.argv[1:] == ["parse"]:
event = json.loads(Path(os.environ["GITHUB_EVENT_PATH"]).read_text())
pr = event["pull_request"]
task_id = parse_task_id(pr["user"]["login"], pr.get("body"))
with Path(os.environ["GITHUB_OUTPUT"]).open("a") as output:
output.write(f"task_id={task_id}\n")
elif sys.argv[1:] == ["verify"]:
verify_task(os.environ["TASK_ID"], github_request)
print("Workspace task identity, actionable status and mirror verified")
elif sys.argv[1:] == ["notify"]:
event = json.loads(Path(os.environ["GITHUB_EVENT_PATH"]).read_text())
payload = merged_task_event(event)
if payload is not None:
github_request(f"{WORKSPACE}/dispatches", payload)
print("Merged task dispatched to Workspace for authoritative verification")
else:
raise ValueError("Expected parse, verify or notify")


if __name__ == "__main__":
try:
main()
except (ValueError, KeyError, TypeError, urllib.error.URLError):
print(
"::error::Task metadata validation failed; check the PR and task-sync app access"
)
sys.exit(1)
101 changes: 101 additions & 0 deletions scripts/test_check_task_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

import base64
import unittest

from check_task_id import merged_task_event, parse_task_id, verify_task


class TaskIdTests(unittest.TestCase):
def test_exact_field_and_release_exceptions(self):
self.assertEqual(
parse_task_id("owner", "Summary\r\nTask-ID: 34.01.03\r\n"), "34.01.03"
)
self.assertEqual(parse_task_id("owner", "Task-ID: none"), "none")
self.assertEqual(parse_task_id("dependabot[bot]", None), "none")
for body in [None, "Task-ID: 34.1.3", "Task-ID: 34.01.03\nTask-ID: none"]:
with self.subTest(body=body), self.assertRaises(ValueError):
parse_task_id("owner", body)

def test_only_one_actionable_mirrored_matching_task_is_accepted(self):
path = "work/epics/34-capacity/stories/34.01-reserve/tasks/34.01.03-binding/README.md"
original = (
"---\ntype: task\nid: '34.01.03'\nstatus: ready\n"
"monday_item_id: 123\n---\n# Task\n"
)
for change in [
None,
("status: ready", "status: in_progress"),
("status: ready", "status: blocked"),
("status: ready", "status: ready\nstatus: in_progress"),
("monday_item_id: 123", "monday_item_id: null"),
("id: '34.01.03'", "id: '34.01.04'"),
("type: task", "type: story"),
]:
readme = original if change is None else original.replace(*change)

def request(endpoint, readme=readme):
if "/git/trees/" in endpoint:
return {"tree": [{"path": path}]}
return {
"encoding": "base64",
"content": base64.b64encode(readme.encode()).decode(),
}

with self.subTest(change=change):
if change is None or change[1] == "status: in_progress":
verify_task("34.01.03", request)
else:
with self.assertRaises(ValueError):
verify_task("34.01.03", request)

for tree in [
{"tree": []},
{"tree": [{"path": path}, {"path": path}]},
{"tree": [{"path": path}], "truncated": True},
]:
with self.subTest(tree=tree), self.assertRaises(ValueError):
verify_task("34.01.03", lambda _, tree=tree: tree)

def test_completion_requires_a_main_merge_and_preserves_receiver_contract(self):
pr = {
"merged": True,
"base": {"ref": "main", "repo": {"full_name": "globster-monday/OpenShell"}},
"body": "Task-ID: 34.01.03",
"merge_commit_sha": "a" * 40,
"number": 32,
"html_url": "https://github.com/globster-monday/OpenShell/pull/32",
"title": 'Title with "quotes"\nand newlines',
"user": {"id": 123, "login": "owner"},
}
payload = merged_task_event({"pull_request": pr})
self.assertEqual(payload["event_type"], "task-merged")
self.assertEqual(
payload["client_payload"],
{
"task_id": "34.01.03",
"commit_url": "https://github.com/globster-monday/OpenShell/commit/"
+ "a" * 40,
"repository": "globster-monday/OpenShell",
"pr_number": "32",
"pr_url": pr["html_url"],
"pr_title": pr["title"],
"pr_author_id": "123",
"pr_author_login": "owner",
},
)
for change in [
{"merged": False},
{"base": {"ref": "parent-task"}},
{"merge_commit_sha": "not-a-commit"},
]:
with self.subTest(change=change), self.assertRaises(ValueError):
merged_task_event({"pull_request": pr | change})
self.assertIsNone(
merged_task_event({"pull_request": pr | {"body": "Task-ID: none"}})
)


if __name__ == "__main__":
unittest.main()
Loading