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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions docs/handoff/shared-extraction/CI-PIPELINE-COMPLETE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# The `Pipeline Complete` CI contract

**Org rule:** every repo has exactly ONE CI pipeline, and it terminates in a
single aggregating job named **`Pipeline Complete`**. That job name is the ONLY
required status check in the common branch ruleset; every other job feeds it via
`needs:`. Adding/renaming an upstream job never touches branch protection — just
keep it in the aggregator's `needs:`.

personal-stack's active "Main" ruleset **already requires** the `Pipeline
Complete` check (context `Pipeline Complete`, integration_id 15368 = GitHub
Actions). No current workflow emits it, so **no PR can merge** until the
consolidated pipeline exists. This is gated on the workflows-token fix (PR #620;
see ENVIRONMENT-AND-BLOCKERS.md).

## Canonical aggregator pattern

```yaml
pipeline-complete:
name: Pipeline Complete
if: always()
needs: [lint, test, build] # every gating job
runs-on: ubuntu-latest
steps:
- name: Verify all gating jobs succeeded
uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
```

`if: always()` + `alls-green` means a skipped/failed/cancelled gating job fails
the aggregator — a green `Pipeline Complete` truly means everything passed.
Canonical template: `ExtraToast/repo-template/.github/workflows/ci.yml`.

## Consolidating personal-stack CI (issue #608)

Today these run separately:
- `fast.yml` (push, non-main) — change-detection, lint-kotlin matrix, lint/typecheck frontend, unit tests.
- `full.yml` (PR) — integration, e2e, architecture, security, contracts.
- `contract-validate.yml`, `migration-guard.yml`, `vault-bootstrap-validate.yml` (PR).
- `system-tests.yml` (workflow_call), `nightly.yml`, `build-and-publish.yml`, `prod-smoke.yml`, `crac-train.yml`.

Target: one `ci.yml` on `pull_request` (+ push to main) that runs the gating set
(keep the existing change-detection + matrix logic — fold jobs in rather than
rewrite) and ends in a `Pipeline Complete` job depending on all of them. Keep
nightly/build-publish/prod-smoke/crac-train as separate non-gating workflows.
Migrate by reference into reusable workflows from `ExtraToast/github-workflows`
once that repo exists (issue #612) — but the first cut can be in-repo.

**Note:** workflow files cannot be pushed by the automation token until PR #620
is merged + the App perms approved + the token re-minted. Until then, author the
workflow under `.github/workflows-pending/` and have a `workflow`-scoped actor
move it (the repo-template README documents the exact `git mv`).
68 changes: 68 additions & 0 deletions docs/handoff/shared-extraction/ENVIRONMENT-AND-BLOCKERS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Environment, credentials & blockers — READ BEFORE GITHUB WORK

Hard-won facts about what the automation account can and cannot do here.

## GitHub identity & capability

Authenticated as the **`personal-stack-agents[bot]`** GitHub App (via `GH_TOKEN`
/ `gh auth token`; also a minting endpoint, see below). Empirically:

| Can | Cannot |
|---|---|
| Create issues, milestones, labels | **Edit** issue bodies / **comment** on issues (GraphQL `updateIssue`/`addComment` + REST PATCH all 403) |
| Create & push branches, open PRs | Push `.github/workflows/*` (see token minter) |
| Push non-workflow file contents | Apply/modify rulesets (no `administration`) |
| | Assign issues/PRs to `ExtraToast` (`replaceActorsForAssignable` 403) |

Implication: keep tracking status in `ISSUES.md` (this dossier), not by editing
GitHub issues. Cross-linking works only via `Part of #605` mentions in bodies at
creation time.

## The token minter (root cause of the workflows block)

`assistant-api` `GitHubAppInstallationTokenClient` mints short-lived, single-repo
installation tokens. It **requests a fixed permission set**, so the issued token
carries only that set even if the App is granted more. Originally
`contents`/`pull_requests`/`actions:write` → could not push workflows or edit
issues.

- **Fix:** PR **#620** widens `REQUESTED_PERMISSIONS` to add `issues:write`,
`workflows:write`, `packages:read` (still no `administration`; `packages`
read-only — CI publishes). Setup doc updated.
- **Activation sequence (operator):** merge #620 → widen the App's repository
permissions on the settings page (Issues, Workflows: Read+write; Packages:
Read) → **approve the updated permissions on each installation** → drop the
cached token (`/tmp/.gh-app-token` in the runner Pod, or recreate the Pod) so
the next mint is wider. Deploy assistant-api for the minter change to take
effect.
- **Exact App permissions** (repository): Contents, Pull requests, Issues,
Workflows, Actions, Commit statuses = **Read & write**; Packages = **Read**;
Metadata = Read (mandatory). **Do NOT grant Administration** (rulesets are
operator-applied) or Packages **write** (release workflow's `GITHUB_TOKEN`
publishes).

## Push auth gotcha

The SSH remote uses a **read-only deploy key** — `git push` over SSH fails
("key is read only"). Push over **HTTPS** with the App token:
`git push https://x-access-token:$(gh auth token)@github.com/ExtraToast/<repo>.git HEAD:<branch>`.
**Scrub the token from the remote afterwards** (`git remote set-url origin <clean-url>`);
don't let it persist in `.git/config` or a tracking ref.

## Tooling gotchas

- **Codex workers cannot run gradle** (workspace-write sandbox blocks sockets) —
gradle-gated verification must be done by a non-sandboxed agent or in CI. Plan
fan-out accordingly.
- **Council** is configured all-Codex (`~/.claude/skills/council/council.toml`);
never run it with `claude:opus` — it drains credits fast. Use sonnet/haiku or
codex.
- `.council/runs/**` is **git-ignored**, so the council artifacts are copied into
this dossier (`council-*.md/json`) to preserve them.

## repo-template state

PR `ExtraToast/repo-template#1` is open. All standard files pushed; the CI
(`ci.yml`) + release (`release.yml`) workflows are **staged** under
`.github/workflows-pending/` with an activation README, because of the workflows
block above. Activate them (and personal-stack's) once the token is fixed.
45 changes: 45 additions & 0 deletions docs/handoff/shared-extraction/ISSUES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Tracking issues & milestones (mirror)

Created in `ExtraToast/personal-stack`. Mirrored here because the automation
token can **create** issues but cannot **edit or comment** them, so this file is
the maintainable source for status — update it in PRs, and update the GitHub
issues out-of-band (operator) when possible.

## Milestones

| # | Title | Scope |
|---|---|---|
| 2 | M0 Foundations & conventions | repo-template, ruleset, Pipeline Complete CI, tag→release versioning, version catalog + Renovate. Unblocks all. |
| 3 | M1 Shared build tooling | gradle-conventions + github-workflows; personal-stack adopts. |
| 4 | M2 Kotlin/Spring commons | kotlin-spring-commons (modular) + reconcile + adopt. |
| 5 | M3 Vue web commons | vue-web-commons (npm) + adopt. |
| 6 | M4 OpenAPI, agent-kit, Stalwart | openapi tooling + agent-kit + stalwart-provisioner + adopt. |
| 7 | M5 Platform blueprints | homelab-platform-blueprints + adopt. |
| 8 | M6 Deploy schema & authz (design) | deploy-config-schema + authz-model design. |

## Epic

- **#605** — Epic: extract shared code into ExtraToast repos + version-pinned deploys. Holds the scope decisions + cross-cutting requirements. All issues below carry `Part of #605`.

## Tracking issues

| # | Milestone | Title / objective |
|---|---|---|
| #606 | M0 | **Enable workflows permission end-to-end.** Minter must request `workflows`+`issues`(+`packages:read`); App perms approved; re-mint. **Addressed by PR #620.** |
| #607 | M0 | **repo-template + apply common ruleset to all repos.** Merge repo-template#1; operator applies ruleset (needs admin — operator-owned); confirm `Pipeline Complete` required everywhere. |
| #608 | M0 | **Consolidate personal-stack CI into one `Pipeline Complete` pipeline.** Fold fast/full/contract-validate/migration-guard/vault-bootstrap-validate into one PR-triggered `ci.yml` ending in a `Pipeline Complete` aggregator. Blocked by #606. |
| #609 | M0 | **tag→release + Flux version-pinning, drop Keel `:latest`.** release-please tags vX.Y.Z; version-tagged images; release PR bumps explicit image tags in `platform/cluster/flux/**`; remove `keel.sh/policy: force` + `:latest`. |
| #610 | M0 | **Central version catalog + Renovate.** `gradle/libs.versions.toml` (`dev.extratoast.*`) + pinned npm manifest + Renovate (group ExtraToast artifacts, pin, gate on Pipeline Complete). |
| #611 | M1 | **Extract gradle-conventions + adopt.** Plugins + stable IDs + publish; personal-stack swaps `includeBuild build-logic` for pinned plugin coords; GH Packages creds in pluginManagement + dependencyResolutionManagement. |
| #612 | M1 | **Extract github-workflows + adopt.** Move actions + reusable workflows (deltas → inputs); personal-stack consumes pinned refs; Pipeline Complete built from shared reusable workflows. |
| #613 | M2 | **Extract kotlin-spring-commons + reconcile + adopt.** Modular publish; reconcile website vault + typed CommandBus; account for the two CommandBusConfig copies + 3 direct result-handler call sites; Dockerfiles resolve packages instead of copying libs. |
| #614 | M3 | **Extract vue-web-commons + adopt.** Package properly (publishConfig, peerDeps); UIs swap workspace dep for `@extratoast/vue-web-commons`; update imports, theme CSS, pnpm workspace, Dockerfiles. |
| #615 | M4 | **OpenAPI: openapi-client-gradle + api-contract-checks + adopt.** Split external client gen from contract-drift checks; update export tasks, assistant-ui scripts, package.json, contract-validate. |
| #616 | M4 | **Extract agent-kit + adopt.** Build from platform/agents/kit + council; preserve renderer flags + knowledge-api installer-serving; CI fails on drift of .claude/.codex/.agents/installer resource/runner entrypoint. |
| #617 | M4 | **Extract stalwart-provisioner + adopt.** Move Dockerfile/apply/bootstrap/accounts/plan templates/schema/validate; v2 `passwordRef` schema; keep domain accounts + DNS local. |
| #618 | M5 | **Extract homelab-platform-blueprints + adopt.** Generic module/script subset as flake input; host data/secrets/jobs/render outputs stay local. |
| #619 | M6 | **Design deploy-config-schema + authz-model.** Design-only; run after platform boundaries proven. |

## Consumer-rewiring conflict nodes (serialize per repo)

`settings.gradle.kts`, `gradle.properties`, root `package.json`/lockfiles, Dockerfiles, workflows, and service build files are first-class conflict nodes — sequence consumer-rewiring PRs so they don't collide.
56 changes: 56 additions & 0 deletions docs/handoff/shared-extraction/NEXT-STEPS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Next steps — resume here

> **2026-06-08 update — read README.md "STATUS UPDATE" first.** Step 0 below is
> DONE: #620 is merged, the minter grants workflows/issues, and
> `repo-template#1` is merged. Issues *can* be edited (mint a per-repo token).
> personal-stack already merges (full.yml has the `Pipeline Complete` job).
> Resume at the narrowed #608 (fold the 3 extra PR workflows into the
> aggregator), then #610 (after M1 publishes artifacts) and #609.

Ordered. Reference the tracking issue in every PR; keep `ISSUES.md` current and,
where possible, update the GitHub issues too (editing works with a fresh
per-repo token).

## 0. Clear the workflows-token blocker (issue #606 — PR #620) — ✅ DONE

1. ~~Merge **PR #620**~~ (merged; minter now requests `workflows`/`issues`/`packages:read`).
- ⚠️ #620 itself can't merge until `Pipeline Complete` exists OR an operator
merges it under the ruleset. Operator may need to merge #620 manually
(chicken-and-egg: the fix that enables CI can't pass the CI gate yet).
2. Operator widens App repository permissions + approves on each installation;
deploy assistant-api; drop the cached runner token (see ENVIRONMENT-AND-BLOCKERS.md).
3. Re-mint the session token; confirm a workflow push succeeds.

## 1. Activate CI + ruleset (issues #607, #608)

4. Move `repo-template`'s staged workflows into `.github/workflows/`
(`git mv .github/workflows-pending/*`), push, merge repo-template#1.
5. Build personal-stack's consolidated `ci.yml` ending in `Pipeline Complete`
(CI-PIPELINE-COMPLETE.md). Open PR → #608. Once green, personal-stack PRs can
merge normally.
6. (Operator) Confirm the ruleset's required check resolves on personal-stack
and is applied to the other ExtraToast repos.

## 2. Versioning foundation (issues #610, #609)

7. Add `gradle/libs.versions.toml` + pinned npm manifest + Renovate config (#610).
8. Implement tag→release (release-please), version-tagged images, Flux explicit
tag pinning, and remove Keel `:latest`/force on in-house images (#609).
See VERSIONING.md for exact files.

## 3. Extractions, in dependency order (council DAG: `council-tasks.json`)

9. **M1:** gradle-conventions (#611) → github-workflows (#612); personal-stack adopts (pinned plugin coords + reusable workflows).
10. **M2:** kotlin-spring-commons (#613) — reconcile divergences first; adopt per service module.
11. **M3:** vue-web-commons (#614).
12. **M4:** openapi tooling (#615), agent-kit (#616), stalwart-provisioner (#617).
13. **M5:** homelab-platform-blueprints (#618).
14. **M6:** design deploy-config-schema + authz-model (#619).

## Working rules

- **personal-stack only**; website is a later program.
- PRs: squash-only, linear history, impersonal commit/PR voice, **no Co-Authored-By / no "Claude" attribution** (repo convention in CLAUDE.md), assigned/labelled per CLAUDE.md.
- Each new ExtraToast repo is bootstrapped from `repo-template`; apply the common ruleset (operator) so `Pipeline Complete` gates it.
- Verify gradle changes by actually building (not in a codex sandbox) or via CI.
- Serialize consumer-rewiring PRs around the conflict-node files (ISSUES.md).
35 changes: 35 additions & 0 deletions docs/handoff/shared-extraction/PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Extraction plan (synthesis)

Full detail in `council-consolidated-plan.md`; this is the operator-decided framing.

## Decisions (operator-approved)

- **Order:** migrate **personal-stack first**; website later.
- **Distribution:** published artifacts — Maven (`dev.extratoast.*`) + npm (`@extratoast`) on GitHub Packages; reusable GitHub Actions via pinned `uses:` to `ExtraToast/github-workflows`; container images on `ghcr.io/extratoast`.
- **Ownership:** all shared repos under `ExtraToast`; personal-stack is the canonical source. Either codebase may be refactored to adopt the better pattern; pick the stronger implementation per concern.

## Candidate repositories (classification)

**Extract now — foundational**
- `gradle-conventions` — Gradle convention plugins as Maven plugin-marker artifacts. ⚠️ resolve version drift first (gradle.properties Kotlin 2.1.20 / Spring 4.0.4 / jOOQ 3.19.18 vs build-logic 2.3.21 / 4.0.6 / 3.21.4). jOOQ/OpenAPI/detekt-path/toolchain opt-in. Risk: plugin resolution precedes build.
- `github-workflows` — reusable composite actions (prepare-ci-host, setup-java-gradle, setup-node-*) + reusable workflows; path/host/image/pkg-manager deltas become inputs; setup-node supports pnpm + Yarn Berry.

**Extract now — shared code & tooling**
- `kotlin-spring-commons` — modular Maven artifacts: command, events, blocks, web, exceptions, vault, vault-jwt, observability, timing-web, timing-jooq, crac, email, archunit-test. **Largest dedup win.** Reconcile website's diverged `vault` + typed CommandBus first; messaging deferred (hardcodes `personal-stack.events`/queue names); each Spring add-on owns its `AutoConfiguration.imports`.
- `vue-web-commons` — framework-neutral Vue utils/composables/JWT/Faro/form-toast-mutation as `@extratoast/vue-web-commons` (build output + dist + d.ts + peerDeps). No PrimeVue↔Vuetify convergence forced. `useAuth`/`useTheme` need auth-routes/role/CSRF/storage/theme injected.
- `openapi-client-gradle` + `api-contract-checks` — split external client generation (Gradle plugin) from internal contract-drift checks (scripts/CLI).
- `agent-kit` — renderer/templates/manifest + council; preserve `render-agent-kit.py` flags and the knowledge-api installer-serving model. Already installed into multiple repos.
- `stalwart-provisioner` — schema-driven mail provisioning (GHCR image + validation CLI), v2 schema with `passwordRef` union (env/file/Vault-VSO).

**Extract cautiously — after design**
- `homelab-platform-blueprints` — reusable NixOS/k3s/Flux module subsets + generic bootstrap/validate scripts as a Nix flake input. Keep host data, secrets, app manifests, Nomad/Consul jobs, `fleet.yaml`, render outputs LOCAL.

**Design-only for now**
- `deploy-config-schema` — JSON-schema-driven deploy/infra config from `fleet.yaml` + `platform/tooling` + render scripts; adapters generate Traefik/Gatus/Keel/Nomad inputs.
- `authz-model` — shared permission vocabulary (roles/claims/permissions schema + generated constants). NO auth services / OAuth clients / redirect URIs / domain evaluators merged.

**Do NOT extract:** datastore/ORM (Postgres/jOOQ vs MariaDB/JPA), Nomad/Consul prod jobs, raw DNS zone contents, PrimeVue/Tailwind & Vuetify component systems (first release), personal-stack RabbitMQ event constants, project-specific system-tests, OAuth clients/redirect URIs, concrete permission assignments, association-specific roles.

## Platform differences to RESPECT (keep pluggable, never force-merge)

Nomad+Consul vs k3s+Flux · Postgres/jOOQ/Flyway vs MariaDB/JPA · PrimeVue+Tailwind vs Vuetify · pnpm vs Yarn Berry · Spring-Modulith-hexagonal+CommandBus vs DDD-layered. Commons must not hard-depend on jOOQ-only or PrimeVue-only types unless split into a `-jooq` / `-primevue` add-on artifact.
Loading
Loading