diff --git a/.github/scripts/app_downloads.py b/.github/scripts/app_downloads.py index 3b02baa2e..b5ba5f380 100644 --- a/.github/scripts/app_downloads.py +++ b/.github/scripts/app_downloads.py @@ -5,12 +5,13 @@ apps (presence of appinfo/info.xml), sums release-asset download counts, and merges the two per app id: - * GitHub org ``ConductionNL`` — legacy/historical downloads. The apps were - migrated off GitHub in 2026; the old release tarballs still carry their - accumulated download counts, so we keep counting them. - * Codeberg org ``Conduction`` — the live source. Nextcloud app-store - installs now fetch the release tarball from here, so this is where new - downloads accrue. + * GitHub org ``ConductionNL`` — the live source. Every Conduction app is + developed and released here, and Nextcloud app-store installs fetch the + release tarball from here, so this is where new downloads accrue. + * Codeberg org ``Conduction`` — legacy. The apps lived on Codeberg for a + few months in 2026 before moving back to GitHub; the release tarballs + published there still carry their download counts, so we keep counting + them. Each app's ``downloads_total`` is ``github + codeberg``; ``totals.downloads`` is the grand total the website StatsStrip renders. Joined with the Nextcloud @@ -78,7 +79,7 @@ def parse_app_id(info_xml): # --------------------------------------------------------------------------- # -# GitHub (legacy source) +# GitHub (live source) # --------------------------------------------------------------------------- # def gh_headers(): h = {"Accept": "application/vnd.github+json"} @@ -103,7 +104,7 @@ def gh_paginate(url): def collect_github(): """Return {app_id: github_block}. Archived repos are kept on purpose — - they hold the historical download counts from before the migration.""" + their release tarballs still carry historical download counts.""" out = {} try: repos = gh_paginate(f"{GITHUB_API}/orgs/{GITHUB_ORG}/repos?per_page=100&type=public") @@ -143,7 +144,7 @@ def collect_github(): # --------------------------------------------------------------------------- # -# Codeberg / Forgejo (live source) +# Codeberg / Forgejo (legacy source) # --------------------------------------------------------------------------- # def cb_headers(): h = {"Accept": "application/json"} @@ -262,9 +263,9 @@ def main(): # API (GitHub, Codeberg, the Nextcloud store) is unreachable or returns an # error, keep the committed data/app-downloads.json stub and exit cleanly. try: - print("Collecting GitHub (legacy) download stats...", file=sys.stderr) + print("Collecting GitHub (live) download stats...", file=sys.stderr) github = collect_github() - print("Collecting Codeberg (live) download stats...", file=sys.stderr) + print("Collecting Codeberg (legacy) download stats...", file=sys.stderr) codeberg = collect_codeberg() print("Fetching Nextcloud store catalog...", file=sys.stderr) @@ -291,8 +292,8 @@ def main(): cb_dl = cb["downloads"] if cb else 0 apps.append({ "id": app_id, - # Prefer the live Codeberg repo path; fall back to the GitHub one. - "repo": (cb or gh)["repo"], + # Prefer the live GitHub repo path; fall back to the Codeberg one. + "repo": (gh or cb)["repo"], "downloads_total": gh_dl + cb_dl, "github": gh, "codeberg": cb, @@ -316,7 +317,7 @@ def main(): "codeberg_org": CODEBERG_ORG, "store_platform_version": PLATFORM_VERSION, "notes": ( - "totals.downloads = GitHub (legacy) + Codeberg (live) release-asset " + "totals.downloads = GitHub (live) + Codeberg (legacy) release-asset " "fetches per app, merged by app id. Counts every tarball fetch (CI, " "mirrors, re-installs) — an upper bound on real installs." ), diff --git a/academy/2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx b/academy/2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx index 7093d3c48..33265568d 100644 --- a/academy/2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx +++ b/academy/2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx @@ -76,7 +76,7 @@ Important: Al Gorithm runs on **Sonnet** by default, with **Haiku** as a cheaper ## Pipeline 2: Code review (Juan Claude van Damme, Sonnet) -Once `build:pass` is set, the supervisor automatically queues `code-review:queued`. Juan Claude picks it up: +Once `build:pass` is set, the `hydra-sequencer` flow automatically queues `code-review:queued`. Juan Claude picks it up: 1. Reads **only the PR diff** (`HYDRA_REVIEW_SCOPE=diff`, ADR-020). That keeps the scope manageable and ensures every line he comments on is actually touched in this PR. 2. Walks the ADR library and gate skills (see part 3). @@ -84,11 +84,11 @@ Once `build:pass` is set, the supervisor automatically queues `code-review:queue 4. For each finding he writes an inline comment on the PR with prefix `[fixed:...]` or `[unfixed:...]`. 5. He commits and pushes his fixes to the feature branch and sets `code-review:pass` or `code-review:fail` on the issue. -Juan doesn't write out his `fixes_applied[]` and `unfixed[]` as JSON himself — the orchestrator only bundles them into one round file after the next step (Clyde). See ["How verdicts are persisted"](#how-verdicts-are-persisted) below. +Juan doesn't write out his `fixes_applied[]` and `unfixed[]` as JSON himself — Hydra only bundles them into one round file after the next step (Clyde). See ["How verdicts are persisted"](#how-verdicts-are-persisted) below. ## Pipeline 3: Security review (Clyde Barcode, Sonnet) -Right after code review (whether `:pass` or `:fail`), the supervisor queues `security-review:queued`. Clyde Barcode runs on the PR state *after* Juan Claude's fixes: +Right after code review (whether `:pass` or `:fail`), the `hydra-sequencer` flow queues `security-review:queued`. Clyde Barcode runs on the PR state *after* Juan Claude's fixes: - Same shape as code review, plus **Semgrep** and pattern matching on CWE classes (SQL injection, XSS, path traversal, hardcoded secrets, …). - May also push bounded fixes in PR mode. @@ -98,7 +98,7 @@ Why sequential and not parallel? Because Clyde reviews what Juan Claude left beh ## How verdicts are persisted -After a complete review round (Juan + Clyde back to back), the orchestrator bundles both verdicts into **one** file on the feature branch: `openspec/changes//reviews/.json` in the target repo. Rounds number sequentially — `1.json` after the first pass, `2.json` after a `retry:queued` cycle, and so on (just `ls reviews/*.json | wc -l` plus one). That file holds the `fixes_applied[]` and `unfixed[]` from **both** reviewers combined — which is what Axel Pliér will read later. An aggregated status snapshot runs alongside in `openspec/changes//hydra.json` (see part 6 for the structure and how to read it). +After a complete review round (Juan + Clyde back to back), Hydra bundles both verdicts into **one** file on the feature branch: `openspec/changes//reviews/.json` in the target repo. Rounds number sequentially — `1.json` after the first pass, `2.json` after a `retry:queued` cycle, and so on (just `ls reviews/*.json | wc -l` plus one). That file holds the `fixes_applied[]` and `unfixed[]` from **both** reviewers combined — which is what Axel Pliér will read later. An aggregated status snapshot runs alongside in `openspec/changes//hydra.json` (see part 6 for the structure and how to read it). ## The applier: Axel Pliér's binary gate @@ -108,7 +108,7 @@ With both reviews in, Hydra has three options: Nothing changed after the original build, so no new risks. **Axel is skipped.** Issue gets `done`. Saves tokens. 2. **Both reviews `:pass` and ≥ 1 fix was pushed.** - The code has changed since the original build. Before we trust the final state, the orchestrator reruns the mechanical gates (PHPCS, Psalm, PHPStan, ...). If those pass, Axel Pliér is up. He reads: + The code has changed since the original build. Before we trust the final state, Hydra reruns the mechanical gates (PHPCS, Psalm, PHPStan, ...). If those pass, Axel Pliér is up. He reads: - the final diff, - the `fixes_applied[]` + `unfixed[]` from both reviews, - all inline comments on the PR. @@ -120,10 +120,10 @@ With both reviews in, Hydra has three options: ## Labels: one source of truth -Hydra is **stateless**. The whole state machine lives on the issue labels on GitHub. The supervisor (the daemon) reads the labels every few seconds and decides what to do. If a container crashes, the supervisor looks at the labels again and picks up where it left off. +Hydra is **stateless**. The whole state machine lives on the issue labels on GitHub. The `hydra-dispatch` flow polls the forge every five minutes, and the `hydra-sequencer` flow reads an issue's labels to decide what to do next. If a stage crashes, the `hydra-lock-reaper` flow frees its lock, and the next pass reads the labels again and picks up where it left off. - - These names get used interchangeably, but they're distinct. The supervisor (scripts/hydra-supervisor.sh) is the long-running daemon: it maintains a pool of slots (5 by default, POOL_SIZE), polls the forge for labelled issues every IDLE_POLL_INTERVAL seconds (60 by default), and fills a free slot the moment a worker finishes. The orchestrator (scripts/orchestrate.sh) is what the supervisor launches into each slot: it picks up one issue, reads its current label to decide the stage, runs that one stage in the right container, persists the result, advances the label, and exits. Supervisor = the pool manager that never stops; orchestrator = the one-shot per-stage runner. Slots are tracked as lock files under /tmp/hydra-slots/, and each gets its own isolated Nextcloud port and workspace so several builds run in parallel without colliding. + + Two flows in hydra's flows/ folder share the work, run by OpenRegister. hydra-dispatch runs on a schedule: it polls the forge for queued work and starts at most a fixed number of pipelines at once (POOL_SIZE). hydra-sequencer carries one issue: it takes that issue's lock, runs the dependency and sibling checks, reads the current label to decide the stage, has the hermiq ExApp run that stage, and advances the label through hydra-label-transition. Every stage's result is written to hydra.json by hydra-record-stage before the label moves. Until 2026 this was two shell scripts, hydra-supervisor.sh (the pool manager) and orchestrate.sh (the per-stage runner). Both are retired. Part 7 shows how the flows run unattended. Per stage there are four possible states: `:queued`, `:running`, `:pass`, `:fail`. They always live **on the issue, never on the PR.** That's a deliberate choice: the issue is the unit of work, the PR is an artefact of a build cycle. @@ -144,7 +144,7 @@ Two human-triggered recovery labels that reach back into the pipeline. Both **si - **`retry:queued`** — the existing PR stays. Hydra builds an ephemeral `feedback.md` (in a `/tmp/hydra-retry-XXXXXX/` working directory, not committed) containing the `unfixed[]` findings + applier blockers, mounts it as `/workspace/feedback.md` in the builder container, and dispatches Al Gorithm in `HYDRA_MODE=fix` scoped to exactly the flagged files. Cheap. Suitable for: "the reviewers were right, builder just needs to touch up a few things". - **`rebuild:queued`** — existing PR closes, branch resets to `development`, all cycle labels removed, back to `build:queued`. Expensive. Suitable for: "Al Gorithm's approach was wrong, we're starting over with the same change". -Rule of thumb: always go for the cheapest recovery first. First `gh pr update-branch ` (merge development → PR), then `retry:queued`, only then `rebuild:queued`. Part 6 goes into this in more depth — including the **label cleanup steps required before applying either trigger** (removing `needs-input` and fail labels first, otherwise the supervisor won't pick up the queue entry). +Rule of thumb: always go for the cheapest recovery first. First `gh pr update-branch ` (merge development → PR), then `retry:queued`, only then `rebuild:queued`. Part 6 goes into this in more depth — including the **label cleanup steps required before applying either trigger** (removing `needs-input` and fail labels first, otherwise the `hydra-sequencer` flow skips the issue). ## Test yourself diff --git a/academy/2026-05-12-hydra-tutorial-5-een-hydra-run-starten/index.mdx b/academy/2026-05-12-hydra-tutorial-5-een-hydra-run-starten/index.mdx index aab782325..a08abb016 100644 --- a/academy/2026-05-12-hydra-tutorial-5-een-hydra-run-starten/index.mdx +++ b/academy/2026-05-12-hydra-tutorial-5-een-hydra-run-starten/index.mdx @@ -5,7 +5,7 @@ contentType: tutorial heroIcon: pipeline authors: [conduction] date: 2026-05-12 -summary: The full recipe from issue to green PR, including the HYDRA_LABEL_PREFIX setting that lets multiple devs run Hydra at the same time without overwriting each other's labels. Fifth of six short modules. +summary: "The full recipe from credentials to a first run: credentials from your environment, the HYDRA_FORGE setting, local images, one stage with dev-run.sh or the whole chain with smoke-test.sh, and a real run on a target app started with a label. Fifth of seven short modules." tags: [Hydra, Operations, Setup, Labels, Tutorial series] apps: [] durationMinutes: 20 @@ -16,140 +16,82 @@ partNumber: 5 import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, NextSteps, NextStep, HexCard, Troubleshooting, TroubleshootingItem} from '@conduction/docusaurus-preset/components'; import Details from '@theme/Details'; -The first four parts were about concept, pipelines, gates and skills. Time to actually run. In this part you start a full Hydra run on a target app, paying attention to the practical pitfalls: tokens, images, and — if you work with multiple devs against the same repos — the `HYDRA_LABEL_PREFIX` trick. +The first four parts were about concept, pipelines, gates and skills. Time to actually run. On your own machine you run Hydra's stages as containers: one stage at a time, or the whole chain against a throwaway repo. A real run on a real app starts with a label, and the flows from part 7 carry it from there. This part covers both, with the practical pitfalls: credentials, the forge setting, and images. {/* truncate */} - How to set up secrets/.env + secrets/credentials.json for a local Hydra run. + Where Hydra gets its credentials: from your environment, never from a file. + How to set up secrets/.env, including the HYDRA_FORGE setting that has no default. How to build the container images locally (or pull them from GHCR). - How to prepare an OpenSpec change and trigger the issue. - How to claim your own label namespace via HYDRA_LABEL_PREFIX so multiple devs don't trample each other's stage labels. - How to follow the pipeline: logs/supervisor.log, slots, and pipeline comments on the PR. + How to run one stage with dev-run.sh, or the whole chain with smoke-test.sh, and what such a local run does not prove. + How to start a real run on a target app with a label, and where to follow it. Parts 1 through 4 worked through. - The hydra repo cloned: git clone git@github.com:ConductionNL/hydra.git. - A working Docker engine (rootless or root doesn't matter). Locally we run the three images without Kubernetes. - A Claude Max account logged in via OAuth (Hydra uses no API keys; see secrets/credentials.json further down). - GitHub PATs with the right scopes for builder/reviewer/security — ask your team lead if you don't have them yet. + The gh CLI logged in (gh auth login). The hydra repo is private, so you need that before you can clone it. + A working Docker engine (rootless or root doesn't matter). Locally we run the images without Kubernetes. + A Claude Max account. claude setup-token turns your login into the token Hydra needs. + Push access to the target repo, because the builder pushes a branch and opens a PR there. ## Step 1: clone and look around ```bash -git clone git@github.com:ConductionNL/hydra.git +git clone https://github.com/ConductionNL/hydra.git cd hydra cat CLAUDE.md ``` -`CLAUDE.md` is the canonical architecture doc. Worth scrolling through the first time. Then on to `secrets/` — that folder is not in git and you have to fill it yourself. +`CLAUDE.md` is the canonical architecture doc. Worth scrolling through the first time. The `secrets/` folder is not in git: you create `secrets/.env` yourself in step 3. -## Step 2: credentials.json +## Step 2: credentials come from your environment -Hydra reads one file for all Claude OAuth tokens and all GitHub PATs: +Hydra's local scripts read exactly two credentials, and only from environment variables: -```json title="secrets/credentials.json" -{ - "claude_tokens": [ - { "name": "account-1", "token": "sk-ant-oat-…" }, - { "name": "account-2", "token": "sk-ant-oat-…" } - ], - "git_tokens": { - "builder": "gho_…", - "reviewer": "gho_…", - "security": "gho_…" - } -} +```bash +export GH_TOKEN=$(gh auth token) # forge token; GIT_TOKEN is accepted as an alias +export CLAUDE_CODE_OAUTH_TOKEN= # from `claude setup-token`; or ANTHROPIC_API_KEY= ``` -Three separate GitHub PATs. No shared org-admin tokens. You saw the reason in part 1: each persona has its own scope. +There is no credentials file. `scripts/lib/env-credentials.sh` is the one resolver, and it fails closed: when a variable is missing, the script stops with a reason instead of starting a run that dies three seconds later with a 401. The scripts pass both values to the containers by variable name (`-e GH_TOKEN`), so the values never end up in the process list or in a file. + + + A Claude Max or Pro subscription is personal-scope: it may run your own work, not an organisation's. Locally that takes care of itself, because the script runs under your shell with your token. In the pipeline, the flows refuse to run a stage whose owner cannot be established. Part 7 explains how. + -Got multiple Claude Max accounts? Put them all in `claude_tokens` in order of preference. `run_container_with_fallback()` rotates them automatically on rate-limit. +:::note Older setups +Before the move to flows (2026), Hydra read `secrets/credentials.json`: a list of Claude tokens plus one git token per persona, with automatic rotation on rate-limit. That file, `scripts/lib/credentials.sh` and the rotation are gone. A leftover `credentials.json` on disk changes nothing. +::: ## Step 3: `secrets/.env` -The **non**-credential overrides. Critical — different for production/CI than for your laptop. +The **non**-credential overrides. Different for production than for your laptop. ```bash title="secrets/.env (local workstation)" +# Which forge the scripts and containers talk to. Required: there is no default. +HYDRA_FORGE=github + # Which images and tag to use. Locally you often build with -t localhost/hydra-*:test. HYDRA_IMAGE_PREFIX=localhost/hydra HYDRA_IMAGE_TAG=test -# Optional: label namespace so colleagues don't step on your toes. -HYDRA_LABEL_PREFIX=wilco - # Default review scope (ADR-020): PR diff only. Set to 'full' only when onboarding a repo. HYDRA_REVIEW_SCOPE=diff ``` -Tip: the defaults are `ghcr.io/conductionnl/hydra` as prefix and `latest` as tag. In production/CI you leave `HYDRA_IMAGE_*` off and Hydra picks up those defaults. +`HYDRA_FORGE=github` is right for every ConductionNL repo; `forgejo` is only for a customer who runs a self-hosted Forgejo. It has no default because guessing a forge means sending your token to a third party. If you start from `secrets/.env.example`, check that line: older copies set it to `codeberg`. + +Tip: the image defaults are `ghcr.io/conductionnl/hydra` as prefix and `latest` as tag. Leave `HYDRA_IMAGE_*` out to use the published images. :::tip secrets/.env is loaded automatically -`scripts/hydra-supervisor.sh` and `scripts/orchestrate.sh` source this file **themselves** at startup. Previously you had to export it in your shell — a silly bug that went unnoticed for months and was fixed as part of the [decidesk-44-45 retrospective](https://github.com/ConductionNL/hydra/blob/main/docs/retrospectives/decidesk-44-45-phase-g.md) (April 2026). Every new long-running entrypoint MUST source this file at startup; that's a repo-wide contract. +`scripts/dev-run.sh` refuses to start without this file and loads it itself, and so do `reconcile.sh` and the `cron-*.sh` sweeps. For a while `dev-run.sh` only checked that the file existed and never loaded it, so `HYDRA_FORGE=github` looked configured while every container still talked to Codeberg ([hydra#391](https://github.com/ConductionNL/hydra/issues/391)). Every entrypoint that reads configuration MUST source this file at startup; that's a repo-wide contract. ::: -## Step 4: HYDRA_LABEL_PREFIX — why and how - -By default Hydra works with **bare** labels: `build:queued`, `code-review:running`, `security-review:pass`, and so on. One supervisor running, one pipeline at a time per issue. No problem. - -**But:** as soon as multiple devs run Hydra locally against **the same** target repos things start crackling. Your supervisor writes `build:queued`. A colleague's supervisor sees that same label, picks it up, runs *their* build. Result: race conditions, duplicate PRs, labels ping-ponging over each other. - -`HYDRA_LABEL_PREFIX` is the fix. Set it in `secrets/.env`: - -```bash -HYDRA_LABEL_PREFIX=wilco -``` - -From that moment on, Hydra prefixes **EVERY label it manages** with `wilco-`: - -| Default (no prefix) | With `HYDRA_LABEL_PREFIX=wilco` | -|---|---| -| `ready-to-build` | `wilco-ready-to-build` | -| `build:queued` | `wilco-build:queued` | -| `build:running` | `wilco-build:running` | -| `code-review:queued` | `wilco-code-review:queued` | -| `security-review:pass` | `wilco-security-review:pass` | -| `applier:fail` | `wilco-applier:fail` | -| `needs-input` | `wilco-needs-input` | -| `retry:queued`, `rebuild:queued` | `wilco-retry:queued`, `wilco-rebuild:queued` | -| `yolo`, `openspec` (metadata) | **not** prefixed — shared metadata | - -Your Hydra reads and writes **only** labels in its own namespace. A colleague's supervisor with `HYDRA_LABEL_PREFIX=jan` operates in a different namespace, so you don't see each other's in-flight work and you don't block each other. - -### Constraints on the value - -- Regex: `^[a-z0-9]([a-z0-9-]{0,14}[a-z0-9])?$` — 1 to 16 characters, lowercase a-z 0-9 and hyphen, not starting or ending with a hyphen. -- Empty / unset = default behaviour (no prefix). That's also what production/CI always runs. -- Changing during a running run = DO NOT. Drain the pipeline first. +## Step 4: build the container images -### Seeding labels on a target repo - -Before your first run with a new prefix you have to create the labels on the target repos once: - -```bash -./scripts/create-labels.sh --repo ConductionNL/ --prefix wilco -``` - -Idempotent — repeated calls only overwrite colours. The script also seeds the right colour codes (red/orange/yellow/green/blue) so the board stays legible at a glance. - -### When to use it, when not - -| Situation | Use prefix? | -|---|---| -| Production / GitHub Actions / cron-managed deployment | **No.** Leave it empty. Production = the canonical labels. | -| Local dev workstation, single Hydra instance | Optional. Works without too. | -| Local, multiple devs, shared target repos | **Yes.** Everyone their own prefix. | -| Demo / experiment on a target repo where production also runs | **Yes, definitely.** Otherwise production-Hydra picks up your demo issue. | - - - Besides the prefix there's a second knob for local work: HYDRA_REVIEW_SCOPE=full switches the reviewer scope from diff-only to the entire repo. Handy when onboarding a new repo or for a dedicated tech-debt sweep, NOT for regular PRs (expect lots of red — see part 3). - - -## Step 5: build the container images - -In CI all images are pushed to GHCR by GitHub Actions. Locally — if you have `HYDRA_IMAGE_PREFIX=localhost/hydra` set — you build them yourself: +In CI, GitHub Actions pushes all images to GHCR. Locally, with `HYDRA_IMAGE_PREFIX=localhost/hydra` set, you build them yourself: ```bash # Base image: Nextcloud + PostgreSQL + OpenRegister (builder dependency) @@ -160,56 +102,71 @@ docker build -t ghcr.io/conductionnl/hydra-nextcloud-test:stable32 \ docker build -t localhost/hydra-builder:test -f images/builder/Dockerfile . docker build -t localhost/hydra-reviewer:test -f images/reviewer/Dockerfile . docker build -t localhost/hydra-security:test -f images/security/Dockerfile . + +# Once: the bridge network the containers run on +docker network create hydra-net ``` -Then verify with `docker images | grep hydra` that the three tags are there. Reviewer and security are standalone (no NC dependency); the builder leans on the `hydra-nextcloud-test` base. +Then check with `docker images | grep hydra` that the three tags are there. Reviewer and security are standalone (no NC dependency); the builder builds on the `hydra-nextcloud-test` base. -## Step 6: start the supervisor +## Step 5: run one stage with `dev-run.sh` + +`dev-run.sh` starts one container directly, with no board and no flow around it. That is the quickest way to see what a persona does: ```bash -# Foreground for your first run so you see live what's happening -./scripts/hydra-supervisor.sh +# Builder: implement an OpenSpec change on a branch and open a PR +./scripts/dev-run.sh openspec/changes/ builder \ + --repo-url https://github.com/ConductionNL/ + +# Code review or security review on an existing PR +./scripts/dev-run.sh - reviewer --repo-url https://github.com/ConductionNL/ \ + --pr-url https://github.com/ConductionNL//pull/ +./scripts/dev-run.sh - security --repo-url https://github.com/ConductionNL/ \ + --pr-url https://github.com/ConductionNL//pull/ ``` -What you expect to see: +The persona's output streams to your terminal, and its commits and comments land on the PR. -``` -[supervisor] loaded HYDRA_LABEL_PREFIX=wilco — restricting to own namespace -[supervisor] image prefix=localhost/hydra tag=test -[supervisor] pool size=5, slots free=5 -[supervisor] polling … -``` +## Step 6: the whole chain on a throwaway repo -In a second terminal you can follow the logs: +To see builder, reviewer and security run back to back, use `smoke-test.sh`. It works on a disposable repo under your own GitHub account, so nothing on a real app is touched: ```bash -tail -f logs/supervisor.log -ls /tmp/hydra-slots/ # see which slots are in use +./scripts/smoke-test.sh --spec-dir /tmp/todo-mvp-spec \ + --github-user --repo-name todo-app ``` -## Step 7: trigger an issue +The script renames an older `todo-app` to `todo-app-runN`, creates a fresh one with the spec uploaded, builds the images, runs the builder, then runs reviewer and security in parallel, and prints a summary. The spec directory needs a `design.md`, a `tasks.md` and a `specs/` folder. From Claude Code, `/local-run` does the same in one command. + + + dev-run.sh and smoke-test.sh start the containers directly, outside any flow. They write no run record: no stage entry in hydra.json, no owner attribution, no label state machine. A green local chain shows that the stages work on this spec. It says nothing about sequencing or attribution. For that the run has to go through the flows, as in step 7. + + +## Step 7: a real run on a target app -In the target repo you open an issue. On that issue you set: +On a real app you don't start containers yourself. You put the trigger label on the tracking issue and the flows do the rest: -- The trigger label. **With prefix:** `wilco-ready-to-build`. Without prefix: `ready-to-build`. -- Optionally `yolo` — not prefixed, since it's metadata. +- The trigger label `ready-to-build`. +- Optionally `yolo`, if the PR may auto-merge once everything is green. - Optionally `openspec` if you're working via OpenSpec. -Example via `gh`: +Via the Hydra helper: ```bash -# With prefix (local dev run): -gh issue edit --repo ConductionNL/ --add-label "wilco-ready-to-build" - -# Or via the Hydra helper (syncs the board straight away): ./scripts/hydra-label.sh ConductionNL/ add ready-to-build ``` -`scripts/hydra-label.sh` is the **preferred path**: it respects `HYDRA_LABEL_PREFIX` automatically, uses the label helpers from `scripts/lib/labels.sh`, and syncs the associated GitHub Project board in real-time. Direct `gh issue edit --add-label` works too, but then the board stays "in the old column" until reconcile (every 10 minutes) picks it up. +`scripts/hydra-label.sh` is the **preferred path**: it uses the label helpers from `scripts/lib/labels.sh` and syncs the GitHub Project board in real time. A direct `gh issue edit --add-label` works too, but then the board stays in the old column until reconcile (every 10 minutes) picks it up. + +The `hydra-dispatch` flow finds the label on its next five-minute tick and hands the issue to `hydra-sequencer`. Its definition ships with `enabled: false`, so this only happens on an OpenRegister instance where the flow has been switched on. Part 7 covers that setup. + + + Earlier versions of this part spent a whole step on HYDRA_LABEL_PREFIX. It gave each developer's local supervisor its own label namespace (wilco-build:queued), so two supervisors watching the same repo did not pick up each other's work. The supervisor is gone, and the flows are the only thing left that picks up work. They act on the bare labels only. The shell tooling (create-labels.sh --prefix, hydra-label.sh, reconcile.sh) still understands the variable, but no flow picks up a prefixed label. Leave it unset. + ## Step 8: follow the pipeline -As soon as the supervisor sees the issue, this happens (prefixed or not, depending on your config): +Once the sequencer has the issue, this happens: ``` ready-to-build → build:queued → build:running → build:pass @@ -218,31 +175,38 @@ ready-to-build → build:queued → build:running → build:pass ↳ applier:queued → :running → :pass / :fail → done / needs-input ``` -You follow it in three places: +You follow it in two places: + +1. **GitHub issue**: the labels change as the sequencer advances the stages. +2. **GitHub PR**: the builder's commits and the reviewers' inline comments. -1. **`logs/supervisor.log`** — what the supervisor is doing. -2. **GitHub issue** — labels change automatically. -3. **GitHub PR** — pipeline status comment is updated every phase by `scripts/lib/pipeline-comment.sh`. +Behind both, every stage is recorded in `openspec/changes//hydra.json` before its label moves. Part 6 shows how to read that file. The flow runs themselves live in OpenRegister; there is no log file on your machine to tail. Set `yolo` and is everything green? Then you'll see `done` appear on the issue, an approval on the PR, and an auto-merge. No `yolo`? Then the PR is ready for a human to merge. ## Troubleshooting - - Check that secrets/.env actually contains HYDRA_IMAGE_PREFIX=localhost/hydra and HYDRA_IMAGE_TAG=test — and that the file lives in secrets/ and not in your home dir. The supervisor and orchestrate.sh source secrets/.env via a fixed path relative to the repo root. + + Export GH_TOKEN and CLAUDE_CODE_OAUTH_TOKEN (or ANTHROPIC_API_KEY) in the same shell you start the script from. The resolver never reads a file, so a secrets/credentials.json does not help. + + + Create the file from step 3. It has to be in secrets/ at the root of the hydra repo, not in your home dir: the scripts read it from a fixed path relative to the repo root. - - Have you already run ./scripts/create-labels.sh --repo ... --prefix wilco? Without the prefixed labels on the target repo the supervisor polls fine, but GitHub returns no matches. Verify with gh label list --repo ConductionNL/<app> | grep wilco. + + Add HYDRA_FORGE=github to secrets/.env. The refusal is deliberate: without it, Hydra would have to guess where to send your token. + + + Check that secrets/.env actually contains HYDRA_IMAGE_PREFIX=localhost/hydra and HYDRA_IMAGE_TAG=test, and that the tags from step 4 match. - docker images | grep hydra-builder. No hit? Build the base image first (hydra-nextcloud-test) and then the builder. The builder Dockerfile has the base as FROM. + docker images | grep hydra-builder. No hit? Build the base image first (hydra-nextcloud-test) and then the builder. The builder Dockerfile has the base as FROM. A network hydra-net not found error means you skipped the last line of step 4. - - Stale labels from a previous run are still hanging around. Strip the old stage labels with ./scripts/hydra-label.sh ConductionNL/<app> <N> transition ready-to-build — that puts the issue back into a defined starting state. + + Nothing on your machine watches labels. On a real app the hydra-dispatch flow has to be switched on in the OpenRegister instance (part 7). To try a stage locally, use dev-run.sh from step 5 instead. - - Regex is ^[a-z0-9]([a-z0-9-]{0,14}[a-z0-9])?$. No uppercase, no punctuation, no underscore, no leading/trailing hyphen. Max 16 characters. + + Stale labels from a previous run are still hanging around. Strip the old stage labels with ./scripts/hydra-label.sh ConductionNL/<app> <N> transition ready-to-build. That puts the issue back into a defined starting state. @@ -252,22 +216,17 @@ Four short questions to check whether you've grasped this part. Stuck? Click **H
-**1. What problem does `HYDRA_LABEL_PREFIX` solve, and when should you explicitly NOT set it?** +**1. Where do Hydra's local scripts get their credentials, and what happens when one is missing?**
-What happens when two Hydra instances look at the same target repo at the same time? And why is that exactly not a problem in production? +Two variables, one resolver. Does it fall back to anything?
-**Problem it solves:** as soon as multiple devs run Hydra locally at the same time against the same target repos, their supervisors see each other's labels and pick up each other's work. Race conditions, duplicate PRs, labels ping-ponging over each other. With your own prefix (`wilco-build:queued`) every dev gets their own namespace. - -**When NOT to set it:** - -- **Production / GitHub Actions / cron-managed deployment** — there you specifically want the canonical (bare) labels: a single source of truth, no stray dev prefix. -- **Local solo run** without colleagues on the same repos — works without too; allowed but not required. +From the **environment only**: `GH_TOKEN` (or its alias `GIT_TOKEN`) for the forge, and `CLAUDE_CODE_OAUTH_TOKEN` or `ANTHROPIC_API_KEY` for the model. `scripts/lib/env-credentials.sh` fails closed: a missing variable stops the script with an explicit reason and a non-zero exit. It never reads a file, so there is nothing to fall back to, and a stale `credentials.json` on disk changes nothing.
@@ -275,18 +234,17 @@ What happens when two Hydra instances look at the same target repo at the same t
-**2. Which labels ARE and which ARE NOT prefixed by `HYDRA_LABEL_PREFIX`?** +**2. Why does `HYDRA_FORGE` have no default?**
-What Hydra itself drives in its state machine gets prefixed. What is about the work itself (a property of the issue, not pipeline state) is shared. +What does a script do with your token once it has decided which forge to talk to?
-- **Prefixed**: **stage labels** (`ready-to-build`, `build:queued/running/pass/fail`, `code-review:*`, `security-review:*`, `applier:*`) and **recovery labels** (`retry:queued`, `rebuild:queued`, `needs-input`). All labels Hydra manages itself in the state machine. -- **NOT prefixed**: **metadata** that describes the nature of the work, not a Hydra instance: **`yolo`** (may auto-merge) and **`openspec`** (follows the OpenSpec working model). Two devs on one issue agree on whether it's a yolo issue — no reason to split that per dev. +Guessing a forge means sending the run's credentials to a third party. It used to default to `codeberg`, and that is how a GitHub token once ended up at codeberg.org. Now the scripts refuse to start until you say `github` (every ConductionNL repo) or `forgejo` (a customer's self-hosted Forgejo).
@@ -298,16 +256,13 @@ What Hydra itself drives in its state machine gets prefixed. What is about the w
-Two things the script handles automatically that the direct `gh` call does not. +The labels are the source of truth, but there is a second thing that has to follow them.
-Two reasons: - -1. **Respects `HYDRA_LABEL_PREFIX` automatically** — you type `add ready-to-build`, the script turns it into `wilco-ready-to-build`. With `gh issue edit --add-label` you'd have to type the prefix by hand and update it on every change — recipe for typos and namespace drift. -2. **Syncs the GitHub Project board immediately.** `gh issue edit` works too, but the board then stays "in the old column" until `reconcile.sh` (every 10 minutes) picks it up. With `hydra-label.sh` you see the column transition immediately. +`hydra-label.sh` goes through the label helpers in `scripts/lib/labels.sh`, and every one of those ends by syncing the GitHub Project board. So one call changes the label and moves the card. `gh issue edit` changes only the label; the board stays in the old column until `reconcile.sh` (every 10 minutes) catches the drift.
@@ -315,24 +270,19 @@ Two reasons:
-**4. What happens if you change `HYDRA_LABEL_PREFIX` while a Hydra run is still in progress? And what's the right way to change it?** +**4. Your `smoke-test.sh` chain came back green. What have you proven, and what not?**
-The running run looks at labels in the old namespace, your new supervisor at another. What's left? +What does a run leave behind when no flow is involved?
-**What happens:** the running run looks at labels in the old namespace; your supervisor with the new prefix looks at a different set. Work in progress is picked up by no one (orphaned), or worse — labels from both namespaces end up on a single issue. - -**Right way:** +**Proven:** the builder, reviewer and security stages work on this spec, with these images and these credentials. -1. Stop putting new work into the old prefix. -2. **Drain the pipeline**: wait until all issues with the old prefix are at `done` or `needs-input`. -3. Only **then** change `HYDRA_LABEL_PREFIX` in `secrets/.env` and restart the supervisor. -4. `./scripts/create-labels.sh --repo ... --prefix ` to seed the new namespace on the target repos. +**Not proven:** anything about sequencing or attribution. `smoke-test.sh` and `dev-run.sh` start containers directly, so no `hydra.json` stage record is written, no owner is attributed, and no label state machine is exercised. Claims of the form "the stage records prove it" need a run through the flows.
@@ -352,6 +302,6 @@ The running run looks at labels in the old namespace, your new supervisor at ano diff --git a/academy/2026-06-11-hydra-tutorial-7-ci-and-deployment/index.mdx b/academy/2026-06-11-hydra-tutorial-7-ci-and-deployment/index.mdx index e2fd0b4f7..19ccdfdb9 100644 --- a/academy/2026-06-11-hydra-tutorial-7-ci-and-deployment/index.mdx +++ b/academy/2026-06-11-hydra-tutorial-7-ci-and-deployment/index.mdx @@ -5,7 +5,7 @@ contentType: tutorial heroIcon: pipeline authors: [conduction] date: 2026-06-11 -summary: "Parts 5 and 6 ran Hydra on your own machine. This part zooms out to how Hydra runs unattended: flows that notice a labelled issue and start each stage, container images that GitHub Actions builds and pushes to GHCR, scoped credentials and token rotation. It lays out the three deployment models and marks honestly what is documented and what is infrastructure-only." +summary: "Parts 5 and 6 ran Hydra on your own machine. This part zooms out to how Hydra runs unattended: flows that notice a labelled issue and start each stage, container images that GitHub Actions builds and pushes to GHCR, and brokered credentials with an owner for every run. It lays out the three deployment models and marks honestly what is documented and what is infrastructure-only." tags: [Hydra, CI/CD, GitHub, Deployment, Operations, Tutorial series] apps: [] durationMinutes: 14 @@ -16,19 +16,19 @@ partNumber: 7 import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, NextSteps, NextStep, HexCard, Troubleshooting, TroubleshootingItem, ContactCta} from '@conduction/docusaurus-preset/components'; import Details from '@theme/Details'; -In [part 5](/academy/hydra-tutorial-5-starting-a-run) you started Hydra by hand: build the images, run the supervisor, label an issue. That is the right way to learn Hydra, but it is not how it runs day to day. In production nobody runs `hydra-supervisor.sh` from a terminal. Instead a scheduled flow notices the labelled issue, and each stage starts from a pre-built image pulled from a registry. This part explains that path, and the three deployment models behind it. +In [part 5](/academy/hydra-tutorial-5-starting-a-run) you ran Hydra by hand: build the images, run a stage with `dev-run.sh`, label an issue. That is the right way to learn Hydra, but it is not how it runs day to day. In production nobody starts containers from a terminal. Instead a scheduled flow notices the labelled issue, and each stage starts from a pre-built image pulled from a registry. This part explains that path, and the three deployment models behind it. {/* truncate */} How a labelled issue starts Hydra through the hydra-dispatch and hydra-sequencer flows, with no human at a terminal, and why that is no longer a GitHub Actions job. Where the container images come from (the GitHub Container Registry, ghcr.io/conductionnl/hydra-*) and how the image tag is configured. - The credential model: scoped per-persona git tokens, multiple Claude tokens, and automatic rotation on rate-limit. + The credential model: a brokered forge credential that hydra only knows by id, and an owner for every run, so a personal subscription only serves its owner. The three deployment models (local, GitHub Actions, Kubernetes) and which parts are documented vs infrastructure-only. - Parts 1 to 6 of this series, especially part 5 (the local run) and part 2 (the supervisor and orchestrator split). + Parts 1 to 6 of this series, especially part 5 (the local run) and part 2 (dispatch and sequencing). A rough idea of what a CI runner is (a machine that executes pipeline jobs on a trigger). Only if you are actually deploying: access to the private ConductionNL/hydra repo and its .github/workflows/, flows/ and manifests/ folders. @@ -39,10 +39,10 @@ Nothing about the pipeline changes between local and CI. The personas, the label | | Local (part 5) | CI / production (this part) | |---|---|---| -| Trigger | you run `hydra-label.sh` / `hydra-supervisor.sh` | the `hydra-dispatch` flow finds the label on its schedule | +| Trigger | you run `dev-run.sh` or `smoke-test.sh` | the `hydra-dispatch` flow finds the label on its schedule | | Where stages run | containers on your machine | the hermiq ExApp, started by OpenRegister flows | | Images | you `docker build` them | pulled from GHCR (`ghcr.io/conductionnl/hydra-*`) | -| Credentials | your local `secrets/` | forge org secrets and variables | +| Credentials | `GH_TOKEN` and `CLAUDE_CODE_OAUTH_TOKEN` in your shell | a brokered credential id; the secret stays in the broker | So everything you learned about labels and the state machine still applies. You are just handing the steering wheel to the flows. @@ -73,35 +73,35 @@ HYDRA_IMAGE_TAG=latest So the reviewer image resolves to `ghcr.io/conductionnl/hydra-reviewer:latest`, and the same for builder and security. (The applier image is not built by this workflow.) `latest` only moves when a release is published. Every push to `main` or `development` also publishes an immutable `sha-` tag, and pinning `HYDRA_IMAGE_TAG` to one of those is how you freeze a known-good pipeline version instead of always tracking `latest`. -## Credentials and token rotation - -Production Hydra never uses a shared org-admin token. That is a hard security rule. Two credential sets live in git-ignored `secrets/credentials.json` (org secrets in CI): - -```jsonc -{ - "claude_tokens": [ // multiple accounts, tried in order - { "name": "account-1", "token": "..." }, - { "name": "account-2", "token": "..." } - ], - "git_tokens": { // one scoped token PER persona - "builder": "...", - "reviewer": "...", - "security": "..." - } +## Credentials: a reference, not a token + +Production Hydra never uses a shared org-admin token, and it never holds a forge token at all. Every forge call the flows make, whether for an issue, a label, a comment or a pull request, goes through one OpenConnector source, `flows/hydra-forge.source.json`. That source carries only a `credentialRef`: the id of a credential that lives in the broker (Doriath). The broker fills in the secret and sends the call, locked to `api.github.com`. What hydra stores is a uuid: + +```json +"authentication": { + "credentialRef": { "credentialId": "{brokeredGithubCredentialId}" } } ``` -Two things matter here: +When the reference cannot be resolved, the run is refused. Nothing falls back to another token. + +The Claude side follows the personal-scope rule from part 5: + +1. **Every run has an owner.** It is resolved once per cycle: the issue's `owner:` marker, or else the person who applied the label. A bot is never the owner. +2. **A subscription only serves its owner.** A Claude Max or Pro credential runs only its owner's stages. A run without a resolvable owner may only be served by an organisation API key. The workload step refuses a stage it cannot attribute, and `hydra-record-stage` writes no record without a `credential_owner`, so an unattributed stage can never advance its label. + +`hydra.json` records whose credential ran each stage (`cycles[].owner`, `stages[].credential_owner`). It never holds the token itself. -1. **Per-persona git tokens.** The builder, reviewer and security personas each push with their own scoped token. Their commits and review comments are attributable, and their permissions are minimal. No persona can act as another. -2. **Claude token rotation.** When a run hits a rate limit, the container-launch helper rotates to the next Claude token in the list (`run_container_with_fallback` plus `scripts/lib/github-rotate.sh`) instead of failing the stage. Several Claude Max accounts in `claude_tokens` keep a busy fleet moving. +:::note What changed +Until 2026 this section described `secrets/credentials.json`: a list of Claude tokens with automatic rotation on rate-limit, plus one git token per persona. The file, the token pool and the rotation were retired with the move to flows. +::: ## The three deployment models Hydra's `docs/deployment-models.md` describes three ways to run it, in increasing operational weight. - Build the images, run hydra-supervisor.sh, optionally drive it from cron. This is parts 5 and 6 of this series. Best for development, debugging, and one-off runs. Fully documented. + Build the images, run one stage with dev-run.sh or the whole chain with smoke-test.sh. This is part 5 of this series. Best for development and debugging. Fully documented. It writes no run record, so it shows that the stages work, not that the pipeline does. @@ -125,11 +125,11 @@ Hydra's `docs/deployment-models.md` describes three ways to run it, in increasin The tag is wrong or the image was never built. Confirm hydra-image.yml ran on GitHub Actions and pushed ghcr.io/conductionnl/hydra-<role> with the expected tag, and that the host has pull access to GHCR. A codeberg.org/conduction/hydra-* reference is stale configuration: no Hydra image was ever published there. - - Verify all accounts are present in claude_tokens and that the secret is actually mounted in the job. Rotation only helps if there is more than one valid token to rotate to. + + The run has no owner that can be resolved. Put an owner:<uid> marker on the issue, or have a person (not a bot) apply the label. Only an organisation API key may run a stage without an owner. - - A persona is using the wrong scoped git token. Each of builder, reviewer and security needs its own entry in git_tokens. A shared token breaks attribution and the least-privilege model. + + The credentialId in hydra-forge.source.json is instance-specific. When you apply the source, substitute the id of the credential in your own OpenRegister instance. @@ -147,7 +147,7 @@ Think about the trigger and the runtime versus the pipeline itself.
-What changes: the trigger (the scheduled `hydra-dispatch` flow finding the label instead of your local supervisor daemon), where stages run (the hermiq ExApp instead of containers on your machine), where images come from (a GHCR pull instead of a local `docker build`), and where credentials live (org secrets instead of local `secrets/`). +What changes: the trigger (the scheduled `hydra-dispatch` flow finding the label instead of you starting a container), where stages run (the hermiq ExApp instead of containers on your machine), where images come from (a GHCR pull instead of a local `docker build`), and where credentials live (a brokered credential id instead of variables in your shell). What stays the same: the entire pipeline. Personas, labels, the state machine, the 22 gates, the no-loop rule. The contract is still one tracking issue labelled `ready-to-build`. @@ -157,17 +157,17 @@ What stays the same: the entire pipeline. Personas, labels, the state machine, t
-**2. Why does each persona have its own git token instead of one shared token?** +**2. Why does the forge source hold a credential id instead of a token?**
-Two reasons: who did what, and how much each can do. +Where can a secret leak from if it is never in hydra's files at all?
-Attribution (each persona's commits and comments are traceable to that persona) and least privilege (each token carries only the permissions that role needs, and no persona can act as another). A shared org-admin token is explicitly forbidden. +The secret never leaves the broker. Hydra's flows, config and logs hold only a uuid, so there is no token in them to leak. The broker refuses an authentication block that holds anything next to the reference, because a secret embedded beside the reference is exactly what the reference exists to prevent. And when the id cannot be resolved, the run is refused instead of falling back to some other token. A shared org-admin token stays explicitly forbidden.
diff --git a/data/app-downloads.json b/data/app-downloads.json index 42c58289e..b513be5ef 100644 --- a/data/app-downloads.json +++ b/data/app-downloads.json @@ -23,7 +23,7 @@ "url": "https://github.com/ConductionNL/doriath" }, "id": "app-template", - "repo": "Conduction/nextcloud-app-template", + "repo": "ConductionNL/doriath", "store": null }, { @@ -49,7 +49,7 @@ "url": "https://github.com/ConductionNL/app-versions" }, "id": "app_versions", - "repo": "Conduction/app-versions", + "repo": "ConductionNL/app-versions", "store": null }, { @@ -97,7 +97,7 @@ "url": "https://github.com/ConductionNL/decidesk" }, "id": "decidesk", - "repo": "Conduction/decidesk", + "repo": "ConductionNL/decidesk", "store": { "categories": [ "organization" @@ -333,7 +333,7 @@ "url": "https://github.com/ConductionNL/docudesk" }, "id": "docudesk", - "repo": "Conduction/docudesk", + "repo": "ConductionNL/docudesk", "store": { "categories": [ "organization" @@ -650,7 +650,7 @@ "url": "https://github.com/ConductionNL/larpingapp" }, "id": "larpingapp", - "repo": "Conduction/larpingapp", + "repo": "ConductionNL/larpingapp", "store": { "categories": [ "organization" @@ -779,7 +779,7 @@ "url": "https://github.com/ConductionNL/mydash" }, "id": "mydash", - "repo": "Conduction/launchpad", + "repo": "ConductionNL/mydash", "store": { "categories": [ "customization", @@ -907,7 +907,7 @@ "url": "https://github.com/ConductionNL/nldesign" }, "id": "nldesign", - "repo": "Conduction/nldesign", + "repo": "ConductionNL/nldesign", "store": { "categories": [ "customization" @@ -2634,7 +2634,7 @@ "url": "https://github.com/ConductionNL/openconnector" }, "id": "openconnector", - "repo": "Conduction/openconnector", + "repo": "ConductionNL/openconnector", "store": { "categories": [ "integration" @@ -2671,7 +2671,7 @@ "url": "https://github.com/ConductionNL/openklant" }, "id": "openklant", - "repo": "Conduction/openklant", + "repo": "ConductionNL/openklant", "store": null }, { @@ -2703,7 +2703,7 @@ "url": "https://github.com/ConductionNL/GDPRdesk" }, "id": "openregister", - "repo": "Conduction/openregister", + "repo": "ConductionNL/GDPRdesk", "store": { "categories": [ "organization", @@ -2742,7 +2742,7 @@ "url": "https://github.com/ConductionNL/opentalk" }, "id": "opentalk", - "repo": "Conduction/opentalk", + "repo": "ConductionNL/opentalk", "store": null }, { @@ -2768,7 +2768,7 @@ "url": "https://github.com/ConductionNL/openzaak" }, "id": "openzaak", - "repo": "Conduction/openzaak", + "repo": "ConductionNL/openzaak", "store": null }, { @@ -2848,7 +2848,7 @@ "url": "https://github.com/ConductionNL/pipelinq" }, "id": "pipelinq", - "repo": "Conduction/pipelinq", + "repo": "ConductionNL/pipelinq", "store": { "categories": [ "organization", @@ -2887,7 +2887,7 @@ "url": "https://github.com/ConductionNL/planix" }, "id": "planix", - "repo": "Conduction/planix", + "repo": "ConductionNL/planix", "store": null }, { @@ -2930,7 +2930,7 @@ "url": "https://github.com/ConductionNL/procest" }, "id": "procest", - "repo": "Conduction/procest", + "repo": "ConductionNL/procest", "store": { "categories": [ "organization", @@ -2975,7 +2975,7 @@ "url": "https://github.com/ConductionNL/scholiq" }, "id": "scholiq", - "repo": "Conduction/scholiq", + "repo": "ConductionNL/scholiq", "store": { "categories": [ "tools" @@ -3034,7 +3034,7 @@ "url": "https://github.com/ConductionNL/shillinq" }, "id": "shillinq", - "repo": "Conduction/shillinq", + "repo": "ConductionNL/shillinq", "store": { "categories": [ "organization" @@ -4137,7 +4137,7 @@ "url": "https://github.com/ConductionNL/softwarecatalog" }, "id": "softwarecatalog", - "repo": "Conduction/softwarecatalog", + "repo": "ConductionNL/softwarecatalog", "store": { "categories": [ "organization" @@ -4174,7 +4174,7 @@ "url": "https://github.com/ConductionNL/valtimo" }, "id": "valtimo", - "repo": "Conduction/valtimo", + "repo": "ConductionNL/valtimo", "store": null }, { @@ -4466,7 +4466,7 @@ "source": { "codeberg_org": "Conduction", "github_org": "ConductionNL", - "notes": "totals.downloads = GitHub (legacy) + Codeberg (live) release-asset fetches per app, merged by app id. Counts every tarball fetch (CI, mirrors, re-installs) \u2014 an upper bound on real installs.", + "notes": "totals.downloads = GitHub (live) + Codeberg (legacy) release-asset fetches per app, merged by app id. Counts every tarball fetch (CI, mirrors, re-installs) \u2014 an upper bound on real installs.", "store_platform_version": "30.0.0" }, "totals": { diff --git a/i18n/nl/docusaurus-plugin-content-blog/2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx b/i18n/nl/docusaurus-plugin-content-blog/2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx index c971d8709..7a959ad76 100644 --- a/i18n/nl/docusaurus-plugin-content-blog/2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx +++ b/i18n/nl/docusaurus-plugin-content-blog/2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx @@ -75,7 +75,7 @@ Belangrijk: Al Gorithm draait op **Haiku**. Reden uit deel 1: hij volgt patronen ## Pipeline 2: Code review (Juan Claude van Damme, Sonnet) -Zodra `build:pass` gezet wordt, queue-t de supervisor automatisch `code-review:queued`. Juan Claude pakt het op: +Zodra `build:pass` gezet wordt, queue-t de `hydra-sequencer`-flow automatisch `code-review:queued`. Juan Claude pakt het op: 1. Leest **alleen de PR-diff** (`HYDRA_REVIEW_SCOPE=diff`, ADR-020). Zo blijft de scope behapbaar en wordt elke regel die hij commentaar geeft daadwerkelijk in deze PR aangeraakt. 2. Loopt de ADR-bibliotheek en gate-skills langs (zie deel 3). @@ -83,11 +83,11 @@ Zodra `build:pass` gezet wordt, queue-t de supervisor automatisch `code-review:q 4. Voor elk gevonden probleem schrijft hij een inline-comment op de PR met prefix `[fixed:...]` of `[unfixed:...]`. 5. Hij commit + pusht zijn fixes naar de feature branch en zet `code-review:pass` of `code-review:fail` op het issue. -Juan's `fixes_applied[]` en `unfixed[]` schrijft hij niet zelf weg als JSON — de orchestrator bundelt ze pas na de volgende stap (Clyde) tot één rondebestand. Zie ["Hoe verdicts gepersisteerd worden"](#hoe-verdicts-gepersisteerd-worden) hieronder. +Juan's `fixes_applied[]` en `unfixed[]` schrijft hij niet zelf weg als JSON — Hydra bundelt ze pas na de volgende stap (Clyde) tot één rondebestand. Zie ["Hoe verdicts gepersisteerd worden"](#hoe-verdicts-gepersisteerd-worden) hieronder. ## Pipeline 3: Security review (Clyde Barcode, Sonnet) -Direct na code-review (of die nu `:pass` of `:fail` was) queue-t de supervisor `security-review:queued`. Clyde Barcode loopt op de PR-staat ná Juan Claude's fixes: +Direct na code-review (of die nu `:pass` of `:fail` was) queue-t de `hydra-sequencer`-flow `security-review:queued`. Clyde Barcode loopt op de PR-staat ná Juan Claude's fixes: - Zelfde shape als code-review, plus **Semgrep** en patroon-matching op CWE-klassen (SQL-injectie, XSS, path-traversal, hardcoded secrets, …). - Mag ook bounded fixes pushen in PR-mode. @@ -97,7 +97,7 @@ Waarom sequentieel en niet parallel? Omdat Clyde reviewed wat Juan Claude heeft ## Hoe verdicts gepersisteerd worden -Na een complete review-ronde (Juan + Clyde achter elkaar) bundelt de orchestrator beide verdicts in **één** bestand op de feature branch: `openspec/changes//reviews/.json` in de doel-repo. Rondes nummeren sequentieel — `1.json` na de eerste pass, `2.json` na een `retry:queued`-cyclus, enzovoort (gewoon `ls reviews/*.json | wc -l` plus één). Daarin zitten de `fixes_applied[]` én `unfixed[]` van **beide** reviewers samen — dat is wat Axel Pliér straks leest. Een geaggregeerde status-snapshot loopt parallel mee naar `openspec/changes//hydra.json` (zie deel 6 voor de structuur en hoe je hem uitleest). +Na een complete review-ronde (Juan + Clyde achter elkaar) bundelt Hydra beide verdicts in **één** bestand op de feature branch: `openspec/changes//reviews/.json` in de doel-repo. Rondes nummeren sequentieel — `1.json` na de eerste pass, `2.json` na een `retry:queued`-cyclus, enzovoort (gewoon `ls reviews/*.json | wc -l` plus één). Daarin zitten de `fixes_applied[]` én `unfixed[]` van **beide** reviewers samen — dat is wat Axel Pliér straks leest. Een geaggregeerde status-snapshot loopt parallel mee naar `openspec/changes//hydra.json` (zie deel 6 voor de structuur en hoe je hem uitleest). ## De applier: Axel Pliér's binaire gate @@ -107,7 +107,7 @@ Met beide reviews binnen heeft Hydra drie opties: Niets veranderd ná de oorspronkelijke build, dus geen nieuwe risico's. **Axel wordt overgeslagen.** Issue krijgt `done`. Bespaart tokens. 2. **Beide reviews `:pass` én er is ≥ 1 fix gepusht.** - De code is gewijzigd na de oorspronkelijke build. Voor we vertrouwen op de uiteindelijke staat draait de orchestrator opnieuw de mechanische gates (PHPCS, Psalm, PHPStan, ...). Slagen die, dan komt Axel Pliér aan zet. Hij leest: + De code is gewijzigd na de oorspronkelijke build. Voor we vertrouwen op de uiteindelijke staat draait Hydra opnieuw de mechanische gates (PHPCS, Psalm, PHPStan, ...). Slagen die, dan komt Axel Pliér aan zet. Hij leest: - de uiteindelijke diff, - de `fixes_applied[]` + `unfixed[]` uit beide reviews, - alle inline-comments op de PR. @@ -119,7 +119,7 @@ Met beide reviews binnen heeft Hydra drie opties: ## Labels: één bron van waarheid -Hydra is **stateless**. Het hele state-machine staat op de issue-labels op GitHub. De supervisor (de daemon) leest om de zoveel seconden de labels en beslist wat te doen. Crasht een container, dan kijkt de supervisor opnieuw naar de labels en pakt op waar het ophield. +Hydra is **stateless**. Het hele state-machine staat op de issue-labels op GitHub. De `hydra-dispatch`-flow pollt de forge elke vijf minuten, en de `hydra-sequencer`-flow leest de labels van een issue om te beslissen wat er daarna gebeurt. Crasht een stage, dan geeft de `hydra-lock-reaper`-flow de lock vrij, en de volgende ronde leest de labels opnieuw en pakt op waar het ophield. Per stage zijn er vier mogelijke states: `:queued`, `:running`, `:pass`, `:fail`. Die staan **altijd op het issue, nooit op de PR.** Dat is een bewuste keuze: het issue is de eenheid van werk, de PR is een artefact van een buildcycle. diff --git a/i18n/nl/docusaurus-plugin-content-blog/2026-05-12-hydra-tutorial-5-een-hydra-run-starten/index.mdx b/i18n/nl/docusaurus-plugin-content-blog/2026-05-12-hydra-tutorial-5-een-hydra-run-starten/index.mdx index 79919257f..2db308784 100644 --- a/i18n/nl/docusaurus-plugin-content-blog/2026-05-12-hydra-tutorial-5-een-hydra-run-starten/index.mdx +++ b/i18n/nl/docusaurus-plugin-content-blog/2026-05-12-hydra-tutorial-5-een-hydra-run-starten/index.mdx @@ -4,7 +4,7 @@ title: "Hydra leerlijn — Deel 5: Een Hydra-run starten op een echte app" contentType: tutorial authors: [conduction] date: 2026-05-12 -summary: Het hele recept van issue tot groene PR, inclusief de HYDRA_LABEL_PREFIX-instelling waarmee meerdere devs gelijktijdig Hydra kunnen draaien zonder elkaars labels te overschrijven. Vijfde van zes korte modules. +summary: "Het hele recept van credentials tot een eerste run: credentials uit je omgeving, de HYDRA_FORGE-instelling, lokale images, één stage met dev-run.sh of de hele keten met smoke-test.sh, en een echte run op een doel-app die je met een label start. Vijfde van zeven korte modules." tags: [Hydra, Operations, Setup, Labels, Tutorial series] apps: [] durationMinutes: 20 @@ -15,200 +15,157 @@ partNumber: 5 import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, NextSteps, NextStep, HexCard, Troubleshooting, TroubleshootingItem} from '@conduction/docusaurus-preset/components'; import Details from '@theme/Details'; -In de eerste vier delen ging het om concept, pipelines, gates en skills. Tijd om te draaien. In dit deel start je een complete Hydra-run op een doel-app, met aandacht voor de praktische valkuilen: tokens, images, en — als je met meerdere devs aan dezelfde repo's werkt — de `HYDRA_LABEL_PREFIX`-truc. +In de eerste vier delen ging het om concept, pipelines, gates en skills. Tijd om te draaien. Op je eigen machine draai je de stages van Hydra als containers: één stage tegelijk, of de hele keten tegen een wegwerp-repo. Een echte run op een echte app begint met een label, en de flows uit deel 7 nemen het daarna over. Dit deel behandelt beide, met de praktische valkuilen: credentials, de forge-instelling en images. {/* truncate */} - Hoe je secrets/.env + secrets/credentials.json opzet voor een lokale Hydra-run. - Hoe je de container images lokaal bouwt (of uit GHCR pulled). - Hoe je een OpenSpec-change klaar maakt en het issue triggert. - Hoe je via HYDRA_LABEL_PREFIX een eigen label-namespace claimt zodat je met meerdere devs niet op elkaars stage-labels staat. - Hoe je de pijplijn volgt: logs/supervisor.log, sloten, en pipeline-comments op de PR. + Waar Hydra zijn credentials vandaan haalt: uit je omgeving, nooit uit een bestand. + Hoe je secrets/.env opzet, inclusief de HYDRA_FORGE-instelling die geen default heeft. + Hoe je de container images lokaal bouwt (of uit GHCR pullt). + Hoe je één stage draait met dev-run.sh, of de hele keten met smoke-test.sh, en wat zo'n lokale run níét bewijst. + Hoe je een echte run op een doel-app start met een label, en waar je die volgt. Deel 1 t/m 4 doorgenomen. - De hydra-repo gecloned: git clone git@github.com:ConductionNL/hydra.git. - Een werkende Docker engine (rootless of root maakt niet uit). Lokaal draaien we de drie images zonder kubernetes. - Een Claude Max-account met OAuth ingelogd (Hydra gebruikt geen API keys; zie secrets/credentials.json verderop). - GitHub PATs met de juiste scopes voor builder/reviewer/security — vraag aan je teamlead als je ze nog niet hebt. + De gh CLI ingelogd (gh auth login). De hydra-repo is privé, dus dat heb je nodig voordat je hem kunt clonen. + Een werkende Docker engine (rootless of root maakt niet uit). Lokaal draaien we de images zonder Kubernetes. + Een Claude Max-account. claude setup-token maakt van je login het token dat Hydra nodig heeft. + Push-rechten op de doel-repo, omdat de builder daar een branch pusht en een PR opent. ## Stap 1: clone en bekijk ```bash -git clone git@github.com:ConductionNL/hydra.git +git clone https://github.com/ConductionNL/hydra.git cd hydra cat CLAUDE.md ``` -`CLAUDE.md` is de canonieke architectuur-doc. Goed om voor de eerste keer doorheen te scrollen. Daarna naar `secrets/` — die map zit niet in git en moet je zelf vullen. +`CLAUDE.md` is de canonieke architectuur-doc. Goed om de eerste keer doorheen te scrollen. De map `secrets/` zit niet in git: `secrets/.env` maak je zelf aan in stap 3. -## Stap 2: credentials.json +## Stap 2: credentials komen uit je omgeving -Hydra leest één bestand voor alle Claude-OAuth-tokens en alle GitHub-PATs: +De lokale scripts van Hydra lezen precies twee credentials, en alleen uit omgevingsvariabelen: -```json title="secrets/credentials.json" -{ - "claude_tokens": [ - { "name": "account-1", "token": "sk-ant-oat-…" }, - { "name": "account-2", "token": "sk-ant-oat-…" } - ], - "git_tokens": { - "builder": "gho_…", - "reviewer": "gho_…", - "security": "gho_…" - } -} +```bash +export GH_TOKEN=$(gh auth token) # forge-token; GIT_TOKEN werkt ook als alias +export CLAUDE_CODE_OAUTH_TOKEN= # uit `claude setup-token`; of ANTHROPIC_API_KEY= ``` -Drie aparte GitHub-PATs. Geen gedeelde org-admin tokens. De reden zag je in deel 1: elk persona heeft zijn eigen scope. +Er is geen credentials-bestand. `scripts/lib/env-credentials.sh` is de enige resolver, en die faalt dicht: ontbreekt een variabele, dan stopt het script met een reden, in plaats van een run te starten die drie seconden later sneuvelt op een 401. De scripts geven beide waarden aan de containers door op variabelenaam (`-e GH_TOKEN`), zodat de waarden nooit in de proceslijst of in een bestand terechtkomen. + + + Een Claude Max- of Pro-abonnement is persoonlijk: het mag je eigen werk draaien, niet dat van een organisatie. Lokaal regelt dat zichzelf, omdat het script onder jouw shell met jouw token draait. In de pijplijn weigeren de flows een stage waarvan de eigenaar niet vast te stellen is. Deel 7 legt uit hoe. + -Heb je meerdere Claude Max accounts? Zet ze allemaal in `claude_tokens` in volgorde van voorkeur. `run_container_with_fallback()` rouleert automatisch op rate-limit. +:::note Oudere setups +Voor de overstap naar flows (2026) las Hydra `secrets/credentials.json`: een lijst Claude-tokens plus één git-token per persona, met automatische rotatie bij een rate-limit. Dat bestand, `scripts/lib/credentials.sh` en de rotatie zijn weg. Een achtergebleven `credentials.json` op schijf verandert niets. +::: ## Stap 3: `secrets/.env` -De **niet**-credential overrides. Cruciaal — verschillende voor productie/CI dan voor je laptop. +De **niet**-credential overrides. Anders voor productie dan voor je laptop. ```bash title="secrets/.env (lokaal werkstation)" +# Met welke forge de scripts en containers praten. Verplicht: er is geen default. +HYDRA_FORGE=github + # Welke images en tag te gebruiken. Lokaal bouw je vaak met -t localhost/hydra-*:test. HYDRA_IMAGE_PREFIX=localhost/hydra HYDRA_IMAGE_TAG=test -# Optioneel: label-namespace zodat je collega's niet op je tenen staat. -HYDRA_LABEL_PREFIX=wilco - # Default review-scope (ADR-020): alleen PR-diff. Zet op 'full' alleen bij onboarding van een repo. HYDRA_REVIEW_SCOPE=diff ``` -Tip: de defaults zijn `ghcr.io/conductionnl/hydra` als prefix en `latest` als tag. Op productie/CI laat je `HYDRA_IMAGE_*` weg en pakt Hydra die defaults op. +`HYDRA_FORGE=github` is goed voor elke ConductionNL-repo; `forgejo` is alleen voor een klant die een eigen Forgejo draait. Er is geen default, omdat een forge raden betekent dat je je token naar een derde partij stuurt. Begin je vanaf `secrets/.env.example`, controleer dan die regel: oudere versies zetten hem op `codeberg`. + +Tip: de image-defaults zijn `ghcr.io/conductionnl/hydra` als prefix en `latest` als tag. Laat `HYDRA_IMAGE_*` weg om de gepubliceerde images te gebruiken. :::tip secrets/.env wordt automatisch geladen -`scripts/hydra-supervisor.sh` en `scripts/orchestrate.sh` sourcen dit bestand **zelf** bij opstart. Eerder moest je het in je shell exporteren — een silly bug die maanden onopgemerkt bleef en gefixt is als onderdeel van het [decidesk-44-45 retrospectief](https://github.com/ConductionNL/hydra/blob/main/docs/retrospectives/decidesk-44-45-phase-g.md) (april 2026). Iedere nieuwe long-running entrypoint MOET dit bestand sourcen aan de start; dat is een repo-wide contract. +`scripts/dev-run.sh` weigert te starten zonder dit bestand en laadt het zelf, net als `reconcile.sh` en de `cron-*.sh`-sweeps. Een tijd lang controleerde `dev-run.sh` alleen óf het bestand bestond en laadde het nooit, waardoor `HYDRA_FORGE=github` ingesteld leek terwijl elke container nog met Codeberg praatte ([hydra#391](https://github.com/ConductionNL/hydra/issues/391)). Elk entrypoint dat configuratie leest MOET dit bestand bij opstart sourcen; dat is een repo-breed contract. ::: -## Stap 4: HYDRA_LABEL_PREFIX — waarom en hoe - -Default werkt Hydra met **kale** labels: `build:queued`, `code-review:running`, `security-review:pass`, enzovoort. Eén supervisor draait, eén pipeline tegelijk per issue. Geen probleem. - -**Maar:** zodra meerdere devs lokaal Hydra draaien tegen **dezelfde** target-repo's gaat het knetteren. Jouw supervisor schrijft `build:queued`. De supervisor van een collega ziet datzelfde label, pakt het op, gaat *zijn* build doen. Resultaat: race-conditions, dubbele PRs, labels die over elkaar pingen. - -`HYDRA_LABEL_PREFIX` is de oplossing. Zet hem in `secrets/.env`: - -```bash -HYDRA_LABEL_PREFIX=wilco -``` - -Vanaf dat moment prefixed Hydra **ELK door hem beheerd label** met `wilco-`: - -| Default (geen prefix) | Met `HYDRA_LABEL_PREFIX=wilco` | -|---|---| -| `ready-to-build` | `wilco-ready-to-build` | -| `build:queued` | `wilco-build:queued` | -| `build:running` | `wilco-build:running` | -| `code-review:queued` | `wilco-code-review:queued` | -| `security-review:pass` | `wilco-security-review:pass` | -| `applier:fail` | `wilco-applier:fail` | -| `needs-input` | `wilco-needs-input` | -| `retry:queued`, `rebuild:queued` | `wilco-retry:queued`, `wilco-rebuild:queued` | -| `yolo`, `openspec` (metadata) | **niet** geprefixed — gedeelde metadata | - -Jouw Hydra leest én schrijft **alleen** labels in zijn eigen namespace. De supervisor van een collega met `HYDRA_LABEL_PREFIX=jan` werkt in een andere namespace, dus jullie zien elkaars in-flight werk niet en blokkeren elkaar niet. - -### Constraints op de waarde - -- Regex: `^[a-z0-9]([a-z0-9-]{0,14}[a-z0-9])?$` — 1 t/m 16 tekens, lowercase a-z 0-9 en koppelteken, niet beginnen of eindigen met een streepje. -- Empty / unset = default-gedrag (geen prefix). Dat is ook wat productie/CI altijd draait. -- Wijzigen tijdens een lopende run = NIET DOEN. Eerst pijplijn drainen. +## Stap 4: build de container images -### Labels seeden op een target-repo - -Voor je eerste run met een nieuwe prefix moet je de labels op de doel-repo's eenmalig aanmaken: +In CI pusht GitHub Actions alle images naar GHCR. Lokaal, met `HYDRA_IMAGE_PREFIX=localhost/hydra`, bouw je ze zelf: ```bash -./scripts/create-labels.sh --repo ConductionNL/ --prefix wilco -``` - -Idempotent — herhaalde aanroepen overschrijven alleen kleuren. Het script seedt ook de juiste kleurcodes (rood/oranje/geel/groen/blauw) zodat het board in één oogopslag leesbaar blijft. - -### Wanneer wel, wanneer niet - -| Situatie | Prefix gebruiken? | -|---|---| -| Productie / GitHub Actions / cron-managed deployment | **Nee.** Laat hem leeg. Productie = de canonieke labels. | -| Lokale dev-werkstation, één Hydra-instance | Kan, hoeft niet. Werkt zonder ook. | -| Lokaal, meerdere devs, gedeelde target-repo's | **Ja.** Iedereen z'n eigen prefix. | -| Demo / experiment op een target-repo waar productie ook draait | **Ja, zeker.** Anders pakt productie-Hydra je demo-issue op. | - - - Naast prefix is er nog een tweede knop voor lokaal werken: HYDRA_REVIEW_SCOPE=full zet de reviewer-scope van diff-only naar de hele repo. Handig voor onboarding van een nieuwe repo of een dedicated tech-debt-sweep, NIET voor reguliere PRs (verwacht veel rood — zie deel 3). - - -## Stap 5: build de container images - -In CI worden alle images door GitHub Actions gepushd naar GHCR. Lokaal — als je `HYDRA_IMAGE_PREFIX=localhost/hydra` hebt staan — bouw je ze zelf: - -```bash -# Base image: Nextcloud + PostgreSQL + OpenRegister (afhankelijkheid van builder) +# Base image: Nextcloud + PostgreSQL + OpenRegister (builder-dependency) docker build -t ghcr.io/conductionnl/hydra-nextcloud-test:stable32 \ -f images/nextcloud-test/Dockerfile . -# De drie pipeline images +# De drie pijplijn-images docker build -t localhost/hydra-builder:test -f images/builder/Dockerfile . docker build -t localhost/hydra-reviewer:test -f images/reviewer/Dockerfile . docker build -t localhost/hydra-security:test -f images/security/Dockerfile . + +# Eenmalig: het bridge-netwerk waarop de containers draaien +docker network create hydra-net ``` -Daarna controleer met `docker images | grep hydra` of de drie tags er staan. Reviewer en security zijn standalone (geen NC-dependency); de builder leunt op de `hydra-nextcloud-test` base. +Controleer daarna met `docker images | grep hydra` dat de drie tags er staan. Reviewer en security zijn standalone (geen NC-dependency); de builder bouwt op de `hydra-nextcloud-test`-base. -## Stap 6: start de supervisor +## Stap 5: draai één stage met `dev-run.sh` + +`dev-run.sh` start één container direct, zonder board en zonder flow eromheen. Zo zie je het snelst wat een persona doet: ```bash -# Foreground voor je eerste run zodat je live ziet wat gebeurt -./scripts/hydra-supervisor.sh +# Builder: implementeer een OpenSpec-change op een branch en open een PR +./scripts/dev-run.sh openspec/changes/ builder \ + --repo-url https://github.com/ConductionNL/ + +# Code review of security review op een bestaande PR +./scripts/dev-run.sh - reviewer --repo-url https://github.com/ConductionNL/ \ + --pr-url https://github.com/ConductionNL//pull/ +./scripts/dev-run.sh - security --repo-url https://github.com/ConductionNL/ \ + --pr-url https://github.com/ConductionNL//pull/ ``` -Wat je verwacht te zien: +De output van de persona verschijnt in je terminal, en zijn commits en comments komen op de PR. -``` -[supervisor] loaded HYDRA_LABEL_PREFIX=wilco — restricting to own namespace -[supervisor] image prefix=localhost/hydra tag=test -[supervisor] pool size=5, slots free=5 -[supervisor] polling … -``` +## Stap 6: de hele keten op een wegwerp-repo -In een tweede terminal kun je de logs volgen: +Wil je builder, reviewer en security na elkaar zien draaien, gebruik dan `smoke-test.sh`. Die werkt op een wegwerp-repo onder je eigen GitHub-account, dus aan een echte app wordt niets aangeraakt: ```bash -tail -f logs/supervisor.log -ls /tmp/hydra-slots/ # zien welke slots in gebruik zijn +./scripts/smoke-test.sh --spec-dir /tmp/todo-mvp-spec \ + --github-user --repo-name todo-app ``` -## Stap 7: trigger een issue +Het script hernoemt een oudere `todo-app` naar `todo-app-runN`, maakt een verse aan met de spec erin, bouwt de images, draait de builder, daarna reviewer en security parallel, en print een samenvatting. De spec-map heeft een `design.md`, een `tasks.md` en een map `specs/` nodig. Vanuit Claude Code doet `/local-run` hetzelfde in één commando. + + + dev-run.sh en smoke-test.sh starten de containers direct, buiten elke flow. Ze schrijven geen run-record: geen stage-entry in hydra.json, geen eigenaar-attributie, geen label-state-machine. Een groene lokale keten laat zien dat de stages werken op deze spec. Over sequencing of attributie zegt hij niets. Daarvoor moet de run door de flows, zoals in stap 7. + + +## Stap 7: een echte run op een doel-app -In de doel-repo open je een issue. Op dat issue zet je: +Op een echte app start je zelf geen containers. Je zet het trigger-label op het tracking-issue en de flows doen de rest: -- Het trigger-label. **Met prefix:** `wilco-ready-to-build`. Zonder prefix: `ready-to-build`. -- Optioneel `yolo` — niet geprefixed, want metadata. +- Het trigger-label `ready-to-build`. +- Optioneel `yolo`, als de PR mag auto-mergen zodra alles groen is. - Optioneel `openspec` als je via OpenSpec werkt. -Voorbeeld via `gh`: +Via de Hydra-helper: ```bash -# Met prefix (lokale dev-run): -gh issue edit --repo ConductionNL/ --add-label "wilco-ready-to-build" - -# Of via de Hydra-helper (synct meteen het board): ./scripts/hydra-label.sh ConductionNL/ add ready-to-build ``` -`scripts/hydra-label.sh` is de **voorkeursweg**: hij respecteert `HYDRA_LABEL_PREFIX` automatisch, gebruikt de label-helpers uit `scripts/lib/labels.sh`, en synct het bijbehorende GitHub Project-bord in real-time. Direct `gh issue edit --add-label` werkt ook, maar dan blijft het board "in de oude kolom" tot reconcile (om de 10 minuten) het opraapt. +`scripts/hydra-label.sh` is het **preferred path**: het gebruikt de label-helpers uit `scripts/lib/labels.sh` en synct het GitHub Project board realtime. Een directe `gh issue edit --add-label` werkt ook, maar dan blijft het board in de oude kolom staan tot reconcile (elke 10 minuten) het oppikt. + +De `hydra-dispatch`-flow vindt het label bij zijn volgende tik van vijf minuten en geeft het issue door aan `hydra-sequencer`. De definitie wordt geleverd met `enabled: false`, dus dit gebeurt alleen op een OpenRegister-instantie waar de flow aangezet is. Deel 7 behandelt die opzet. + + + Eerdere versies van dit deel besteedden een hele stap aan HYDRA_LABEL_PREFIX. Die gaf de lokale supervisor van elke developer een eigen label-namespace (wilco-build:queued), zodat twee supervisors op dezelfde repo elkaars werk niet oppakten. De supervisor is weg, en de flows zijn het enige dat nog werk oppakt. Die reageren alleen op de kale labels. De shell-tooling (create-labels.sh --prefix, hydra-label.sh, reconcile.sh) kent de variabele nog, maar geen enkele flow pakt een geprefixt label op. Laat hem leeg. + ## Stap 8: volg de pijplijn -Zodra de supervisor het issue ziet, gebeurt dit (geprefixed of niet, afhankelijk van je config): +Zodra de sequencer het issue heeft, gebeurt dit: ``` ready-to-build → build:queued → build:running → build:pass @@ -217,56 +174,58 @@ ready-to-build → build:queued → build:running → build:pass ↳ applier:queued → :running → :pass / :fail → done / needs-input ``` -Je volgt het op drie plekken: +Je volgt het op twee plekken: + +1. **GitHub issue**: de labels veranderen terwijl de sequencer de stages doorloopt. +2. **GitHub PR**: de commits van de builder en de inline comments van de reviewers. -1. **`logs/supervisor.log`** — wat de supervisor doet. -2. **GitHub-issue** — labels veranderen automatisch. -3. **GitHub PR** — pipeline-status-comment wordt elke fase bijgewerkt door `scripts/lib/pipeline-comment.sh`. +Achter beide wordt elke stage vastgelegd in `openspec/changes//hydra.json`, voordat het label verschuift. Deel 6 laat zien hoe je dat bestand leest. De flow-runs zelf leven in OpenRegister; er is geen logbestand op je machine om te tailen. -Heb je `yolo` gezet en is alles groen? Dan zie je `done` op het issue verschijnen, een goedkeuring op de PR, en een auto-merge. Geen `yolo`? Dan staat de PR klaar voor een mens om te mergen. +`yolo` gezet en alles groen? Dan zie je op het issue `done` verschijnen, op de PR een approve en een auto-merge. Geen `yolo`? Dan staat de PR klaar voor een mens om te mergen. ## Troubleshooting - - Controleer dat secrets/.env daadwerkelijk HYDRA_IMAGE_PREFIX=localhost/hydra en HYDRA_IMAGE_TAG=test bevat — en dat het bestand bestaat in secrets/ en niet in je home-dir. De supervisor en orchestrate.sh sourcen secrets/.env via een vast pad relatief aan de repo-root. + + Exporteer GH_TOKEN en CLAUDE_CODE_OAUTH_TOKEN (of ANTHROPIC_API_KEY) in dezelfde shell waarin je het script start. De resolver leest nooit een bestand, dus een secrets/credentials.json helpt niet. + + + Maak het bestand uit stap 3 aan. Het moet in secrets/ in de root van de hydra-repo staan, niet in je home-dir: de scripts lezen het via een vast pad relatief aan de repo-root. - - Heb je ./scripts/create-labels.sh --repo ... --prefix wilco al gedraaid? Zonder de geprefixeerde labels op de doel-repo kan de supervisor wel polled, maar GitHub geeft geen matches terug. Verifieer met gh label list --repo ConductionNL/<app> | grep wilco. + + Zet HYDRA_FORGE=github in secrets/.env. De weigering is bewust: zonder die regel zou Hydra moeten raden waar je token heen moet. + + + Controleer dat secrets/.env daadwerkelijk HYDRA_IMAGE_PREFIX=localhost/hydra en HYDRA_IMAGE_TAG=test bevat, en dat de tags uit stap 4 overeenkomen. - docker images | grep hydra-builder. Geen treffer? Bouw eerst de base image (hydra-nextcloud-test) en daarna pas de builder. De builder-Dockerfile heeft de base als FROM. + docker images | grep hydra-builder. Geen treffer? Bouw eerst de base image (hydra-nextcloud-test) en daarna pas de builder. De builder-Dockerfile heeft de base als FROM. Een fout network hydra-net not found betekent dat je de laatste regel van stap 4 hebt overgeslagen. - - Stale labels van een vorige run blijven hangen. Strip de oude stage-labels met ./scripts/hydra-label.sh ConductionNL/<app> <N> transition ready-to-build — dat zet de issue terug in een definieerde startstate. + + Niets op je machine let op labels. Op een echte app moet de hydra-dispatch-flow aangezet zijn in de OpenRegister-instantie (deel 7). Wil je lokaal een stage proberen, gebruik dan dev-run.sh uit stap 5. - - Regex is ^[a-z0-9]([a-z0-9-]{0,14}[a-z0-9])?$. Geen hoofdletters, geen leestekens, geen underscore, geen leading/trailing streepje. Max 16 tekens. + + Stale labels van een vorige run blijven hangen. Strip de oude stage-labels met ./scripts/hydra-label.sh ConductionNL/<app> <N> transition ready-to-build. Dat zet het issue terug in een gedefinieerde starttoestand. ## Test jezelf -Vier korte vragen om te checken of je dit deel begrepen hebt. Vastgelopen? Klik **Hint**. Curieus naar het antwoord? Klik **Antwoord**. +Vier korte vragen om te checken of je dit deel begrepen hebt. Vastgelopen? Klik **Hint**. Benieuwd naar het antwoord? Klik **Antwoord**.
-**1. Wat is het probleem dat `HYDRA_LABEL_PREFIX` oplost, en wanneer hoef je hem expliciet NIET te zetten?** +**1. Waar halen de lokale scripts van Hydra hun credentials vandaan, en wat gebeurt er als er een ontbreekt?**
-Wat gebeurt er als twee Hydra-instances tegelijk dezelfde target-repo bekijken? En waarom is dat juist géén probleem in productie? +Twee variabelen, één resolver. Valt die ergens op terug?
-**Probleem dat het oplost:** zodra meerdere devs lokaal tegelijk Hydra draaien tegen dezelfde target-repos zien hun supervisors elkaars labels en pakken elkaars werk op. Race-conditions, dubbele PRs, labels die over elkaar pingen. Met een eigen prefix (`wilco-build:queued`) heeft elke dev zijn eigen namespace. - -**Wanneer NIET zetten:** - -- **Productie / GitHub Actions / cron-managed deployment** — daar wil je juist de canonieke (kale) labels: één bron van waarheid, geen verdwaalde dev-prefix. -- **Lokale solo-run** zonder collega's op dezelfde repos — werkt zonder ook; mag wel maar hoeft niet. +**Alleen uit de omgeving**: `GH_TOKEN` (of de alias `GIT_TOKEN`) voor de forge, en `CLAUDE_CODE_OAUTH_TOKEN` of `ANTHROPIC_API_KEY` voor het model. `scripts/lib/env-credentials.sh` faalt dicht: een ontbrekende variabele stopt het script met een expliciete reden en een exit-code die niet nul is. De resolver leest nooit een bestand, dus er is niets om op terug te vallen, en een verouderde `credentials.json` op schijf verandert niets.
@@ -274,18 +233,17 @@ Wat gebeurt er als twee Hydra-instances tegelijk dezelfde target-repo bekijken?
-**2. Welke labels worden door `HYDRA_LABEL_PREFIX` WEL en welke NIET geprefixed?** +**2. Waarom heeft `HYDRA_FORGE` geen default?**
-Wat Hydra zelf doorzet in zijn state-machine wordt geprefixed. Wat over het werk-zélf gaat (eigenschap van het issue, geen pipeline-state) wordt gedeeld. +Wat doet een script met je token zodra het besloten heeft met welke forge het praat?
-- **WEL geprefixed**: **stage-labels** (`ready-to-build`, `build:queued/running/pass/fail`, `code-review:*`, `security-review:*`, `applier:*`) en **herstel-labels** (`retry:queued`, `rebuild:queued`, `needs-input`). Allemaal labels die Hydra zelf beheert in de state-machine. -- **NIET geprefixed**: **metadata** die over de aard van het werk gaat, niet over een Hydra-instance: **`yolo`** (mag auto-mergen) en **`openspec`** (volgt OpenSpec-werkmodel). Twee devs op één issue zijn het immers eens over of het een yolo-issue is — geen reden om dat per-dev op te splitsen. +Een forge raden betekent de credentials van de run naar een derde partij sturen. De default was vroeger `codeberg`, en zo is er ooit een GitHub-token bij codeberg.org terechtgekomen. Nu weigeren de scripts te starten tot je `github` (elke ConductionNL-repo) of `forgejo` (de eigen Forgejo van een klant) opgeeft.
@@ -293,20 +251,17 @@ Wat Hydra zelf doorzet in zijn state-machine wordt geprefixed. Wat over het werk
-**3. Waarom roep je `./scripts/hydra-label.sh` aan in plaats van `gh issue edit --add-label` bij het triggeren van een run?** +**3. Waarom roep je `./scripts/hydra-label.sh` aan in plaats van `gh issue edit --add-label` om een run te triggeren?**
-Twee dingen waar het script automatisch voor zorgt en de directe `gh`-aanroep niet. +De labels zijn de bron van waarheid, maar er is nog iets dat ze moet volgen.
-Twee redenen: - -1. **Respecteert `HYDRA_LABEL_PREFIX` automatisch** — je tikt `add ready-to-build`, het script maakt er `wilco-ready-to-build` van. Met `gh issue edit --add-label` moet je de prefix met de hand meetypen en bij elke verandering aanpassen — recipe voor typos en namespace-drift. -2. **Synct het GitHub Project-bord direct.** `gh issue edit` werkt ook, maar het board blijft dan "in de oude kolom" tot `reconcile.sh` (om de 10 minuten) het opraapt. Met `hydra-label.sh` zie je de kolom-overgang meteen. +`hydra-label.sh` gaat via de label-helpers in `scripts/lib/labels.sh`, en elk daarvan eindigt met een sync van het GitHub Project board. Eén aanroep verandert dus het label én verplaatst de kaart. `gh issue edit` verandert alleen het label; het board blijft in de oude kolom tot `reconcile.sh` (elke 10 minuten) het verschil oppikt.
@@ -314,24 +269,19 @@ Twee redenen:
-**4. Wat gebeurt er als je `HYDRA_LABEL_PREFIX` wijzigt terwijl er nog een Hydra-run loopt? En wat is de juiste werkwijze als je toch wilt wijzigen?** +**4. Je `smoke-test.sh`-keten kwam groen terug. Wat heb je bewezen, en wat niet?**
-De lopende run kijkt naar labels in de oude namespace, jouw nieuwe supervisor in een andere. Wat blijft er over? +Wat laat een run achter als er geen flow bij betrokken is?
-**Wat er gebeurt:** de lopende run kijkt naar labels in de oude namespace; jouw supervisor met nieuwe prefix kijkt naar een andere set. Lopend werk wordt door niemand meer opgepakt (orphaned), of erger — labels van beide namespaces komen op één issue terecht. - -**Juiste werkwijze:** +**Bewezen:** de stages builder, reviewer en security werken op deze spec, met deze images en deze credentials. -1. Zet geen nieuw werk meer in de oude prefix. -2. **Drain de pijplijn**: wacht tot alle issues met de oude prefix op `done` of `needs-input` staan. -3. Pas dán `HYDRA_LABEL_PREFIX` aanpassen in `secrets/.env` en de supervisor herstarten. -4. `./scripts/create-labels.sh --repo ... --prefix ` om de nieuwe namespace op de doel-repo's te seeden. +**Niet bewezen:** iets over sequencing of attributie. `smoke-test.sh` en `dev-run.sh` starten containers direct, dus er wordt geen stage-record in `hydra.json` geschreven, geen eigenaar vastgelegd en geen label-state-machine doorlopen. Voor een claim als "de stage-records bewijzen het" heb je een run door de flows nodig.
@@ -351,6 +301,6 @@ De lopende run kijkt naar labels in de oude namespace, jouw nieuwe supervisor in diff --git a/src/data/apps-catalog.js b/src/data/apps-catalog.js index c79fbff5d..b397a97cc 100644 --- a/src/data/apps-catalog.js +++ b/src/data/apps-catalog.js @@ -204,7 +204,7 @@ export const APP_COUNT = Object.keys(PRESENTATION).length; export const INTEGRATED_COUNT = 14; export const ECOSYSTEM_COUNT = APP_COUNT + INTEGRATED_COUNT; -/* Combined GitHub (legacy) + Codeberg (live) download total across every +/* Combined GitHub (live) + Codeberg (legacy) download total across every app, straight from the generated totals block. This is the number the homepage StatsStrip renders. Sourced from the website's own local JSON because the preset's `totalDownloads` export can't read the data file