From 275be6f6ea018207451f1aeb59d6d1e1565d2d79 Mon Sep 17 00:00:00 2001 From: lukasWuttke <54042461+LukasWodka@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:11:26 +0200 Subject: [PATCH 1/3] feat(2036): add-to-kanban authenticates as the App (backend#2036) (#125) The last board writer on PROJECTS_KANBAN_TOKEN. A per-repo COPY, so one PR per repo; the content stays byte-identical across the fleet because the guard compares it that way. `owner:` makes the installation token ORG-scoped -- a repo-scoped one cannot write the org project. No fallback to the PAT: a fallback would let a broken App path keep working silently. This workflow also fires on DEPENDABOT PRs, which GitHub gates on a separate secret scope. Both app secrets are set there too; without that, Dependabot PRs would stop reaching the board with `Input required and not supplied` -- the exact failure PROJECTS_KANBAN_TOKEN already had to be dual-scoped to avoid. Refs backend#2036 Co-authored-by: Claude Opus 5 --- .github/workflows/add-to-kanban.yml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/add-to-kanban.yml b/.github/workflows/add-to-kanban.yml index 603751a..07283e3 100644 --- a/.github/workflows/add-to-kanban.yml +++ b/.github/workflows/add-to-kanban.yml @@ -10,7 +10,24 @@ jobs: add-to-project: runs-on: ubuntu-latest steps: + # Board writes authenticate as the tracebloc-release-train App (backend#2036), + # not a human's PAT. `owner:` yields an ORG-scoped installation token; a + # repo-scoped one cannot write the org project. No fallback to the PAT: a + # fallback would let a broken App path keep working silently. + # + # This workflow also fires on DEPENDABOT PRs, which GitHub gates on a separate + # secret scope -- both app secrets are set there too, or Dependabot PRs would + # stop reaching the board with `Input required and not supplied`. + - name: Mint an installation token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + app-id: ${{ secrets.RELEASE_TRAIN_APP_ID }} + private-key: ${{ secrets.RELEASE_TRAIN_APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + - uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0 with: project-url: https://github.com/orgs/tracebloc/projects/2 - github-token: ${{ secrets.PROJECTS_KANBAN_TOKEN }} + github-token: ${{ steps.app-token.outputs.token }} + From 224b6e1de0fb6d120ba97ffc1dda3e1e14db01df Mon Sep 17 00:00:00 2001 From: lukasWuttke <54042461+LukasWodka@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:10:03 +0200 Subject: [PATCH 2/3] sec(2157): scope the add-to-kanban App token to least privilege (#129) Brings this repo's copy to the fleet canonical form. `add-to-kanban.yml` is a byte-compared copy, so this is one pass over every repo rather than a fix here. WHAT CHANGES repositories: ${{ github.event.repository.name }} the two content reads stop being org-wide permission-issues: read add-to-project must RESOLVE permission-pull-requests: read the triggering node before permission-organization-projects: write it can add it permissions: {} the job needs no GITHUB_TOKEN Without any `permission-*` the mint carried the App's FULL installation grant -- contents+PR write across every installed repo -- and the App holds bypass_reviews on staging and prod fleet-wide, so the blast radius was merge-past-review rather than merely write. WHY THE FILE IS BYTE-IDENTICAL TO backend's. That copy is the one that survived review: saadqbal caught that `owner:` narrows nothing ("Input 'repositories' is not set. Creating token for all repositories owned by tracebloc"), and aptracebloc caught a run cited as evidence that was not one. Copying the corrected version rather than re-deriving it is the point of a byte-compared file. VERIFIED, not assumed: run 32255581084 on backend#2181's head exercised these exact scopes and landed the card (Status=Code review), which settles the one open question -- `repositories:` scoping does not clip `organization_projects`. Refs backend#2157. --- .github/workflows/add-to-kanban.yml | 59 +++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/.github/workflows/add-to-kanban.yml b/.github/workflows/add-to-kanban.yml index 07283e3..2a3a432 100644 --- a/.github/workflows/add-to-kanban.yml +++ b/.github/workflows/add-to-kanban.yml @@ -9,6 +9,11 @@ on: jobs: add-to-project: runs-on: ubuntu-latest + # NO GITHUB_TOKEN AT ALL (saadqbal, #2181). Every call in this job authenticates + # as the App, so the workflow token needs nothing -- and an empty grant is the + # only version of that claim a reader can check. Free, and it means the least- + # privilege story covers both credentials in the job rather than just the loud one. + permissions: {} steps: # Board writes authenticate as the tracebloc-release-train App (backend#2036), # not a human's PAT. `owner:` yields an ORG-scoped installation token; a @@ -25,6 +30,60 @@ jobs: app-id: ${{ secrets.RELEASE_TRAIN_APP_ID }} private-key: ${{ secrets.RELEASE_TRAIN_APP_PRIVATE_KEY }} owner: ${{ github.repository_owner }} + # SCOPED TO THIS REPO, or the two reads below land org-wide (saadqbal, + # #2181). `owner:` alone does not narrow anything -- run 32239403796 says + # so in as many words: "Input 'repositories' is not set. Creating token for + # all repositories owned by tracebloc." A token calling itself + # least-privilege while carrying issue+PR read across all 19 installed + # repos is the claim this PR exists to stop making. + # + # `organization_projects` is an ORG-level permission and is not affected by + # repo scoping, so the board write should be unchanged -- but that is an + # assumption, and it is the same class of assumption that broke the first + # attempt, so the verification run is what settles it rather than this + # comment. If it is wrong the failure is LOUD (see below), which is what + # makes trying it cheap. + repositories: ${{ github.event.repository.name }} + # Least privilege (backend#2166): without any `permission-*` the token + # carries the App's FULL installation grant. actions/add-to-project needs + # THREE scopes, not one: it must RESOLVE the triggering issue/PR node + # before it can add it to the board, so it needs read on both content + # types (this workflow fires on `issues` and `pull_request`) in addition + # to the project write. Projects-write alone leaves the node unresolvable + # -- the add fails with "Could not resolve to a node with the global id". + # + # WHAT IS ACTUALLY DEMONSTRATED, and what is not. Stated narrowly because + # two earlier versions of this paragraph each overclaimed in a different + # direction, and this text is copied verbatim into 17 repos -- a wrong + # argument here is a wrong argument 17 times, in a byte-compared file + # nobody re-derives. + # + # DEMONSTRATED: a MISSING READ scope fails loudly. Run 32239403796 on this + # branch, at commit 218f0b13 (projects-write only), errored with + # `Could not resolve to a node with the global id` and the job went RED -- + # `add-to-project` routes GraphQL errors through `setFailed`. + # + # NOT DEMONSTRATED: the case the FIRST version of this comment described -- + # the token resolving the node fine and then 403ing the BOARD WRITE. No run + # has ever produced it. So "fails loudly" is proven for the read scopes and + # is an expectation, not a measurement, for the write. + # + # AND ONE RUN THAT LOOKED LIKE EVIDENCE IS NOT (aptracebloc). The previous + # wording cited run 32237283072 as a second scope failure. It is not one: + # it ran on `develop`, whose file at that sha passes NO `permission-*` at + # all (the App's full grant), and it failed on + # `Could not resolve to a node with the global id of I_kwDONNfQt88...` -- + # a node a fully-privileged token also cannot see, i.e. an issue that no + # longer exists (this workflow fires on `issues: transferred`). Run + # 32237067262, the SAME develop sha, succeeded 2m34s earlier. A dead node, + # not a permission. + # + # The proof this is right is therefore still a LANDED CARD, not a passing + # mint: a mint can succeed with a scope the board write then needs and + # lacks, and that is the one path nothing here has exercised. + permission-issues: read + permission-pull-requests: read + permission-organization-projects: write - uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0 with: From e7f236ad89225d9fd80884755fe55ef51a1983cf Mon Sep 17 00:00:00 2001 From: shujaat_tracebloc <153823837+shujaatTracebloc@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:22:49 +0200 Subject: [PATCH 3/3] docs(platforms): macOS trains on CPU by design (no Apple-GPU path from containers) (#126) Co-authored-by: shujaat hasan Co-authored-by: Claude Fable 5 --- environment-setup/configuration.mdx | 2 +- environment-setup/deployment-environments.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/environment-setup/configuration.mdx b/environment-setup/configuration.mdx index 5c465f5..ae87130 100644 --- a/environment-setup/configuration.mdx +++ b/environment-setup/configuration.mdx @@ -85,7 +85,7 @@ Auto-detected. ROCm is installed automatically on Ubuntu and RHEL/CentOS. A logo ### macOS -CPU only. Docker Desktop on macOS does not support GPU passthrough. For GPU workloads, deploy on a Linux machine with NVIDIA GPUs or use [AWS (EKS)](/environment-setup/eks-client-deployment-guide). +CPU only, on both Intel and Apple silicon. Training runs inside Linux containers, which on macOS run in a virtual machine with no access to the Apple GPU — so macOS machines train on CPU by design, regardless of device settings. For GPU training, use a machine with an NVIDIA GPU (Linux, or Windows via WSL2), or deploy on [AWS (EKS)](/environment-setup/eks-client-deployment-guide). ### Windows diff --git a/environment-setup/deployment-environments.mdx b/environment-setup/deployment-environments.mdx index 4298716..d5f886c 100644 --- a/environment-setup/deployment-environments.mdx +++ b/environment-setup/deployment-environments.mdx @@ -9,7 +9,7 @@ tracebloc runs the same way everywhere: one chart (`tracebloc/client`), one set | Environment | Runs on | GPU | Best when | |---|---|---|---| -| [Local / k3d](/environment-setup/deploy-local) | One machine you own | NVIDIA / AMD, auto-detected | A laptop or a single on-prem server — the fastest start | +| [Local / k3d](/environment-setup/deploy-local) | One machine you own | NVIDIA / AMD, auto-detected (macOS: CPU only) | A laptop or a single on-prem server — the fastest start | | [Bare-metal](/environment-setup/deploy-bare-metal) | Your own Kubernetes cluster | Your nodes | You already run on-prem Kubernetes | | [Amazon EKS](/environment-setup/eks-client-deployment-guide) | AWS (managed) | GPU nodegroups | You're on AWS and want managed, autoscaling compute | | [Azure AKS](/environment-setup/deploy-aks) | Azure (managed) | GPU node pools | You're on Azure |