feat(bootstrap): Bun-workspace skeleton + typed config + vendored modules — T02

- Bun workspaces (packages/* + bootstrap); Pulumi nodejs runtime under
  packagemanager: bun (no npm fallback needed).
- bootstrap/config.ts: typed FoundationConfig per CONTRACT_001; loadConfig()
  fails closed, aggregating all missing+malformed keys in one error. Reads flat
  dotted keys; image digests excluded (they live in VERSIONS, D5).
- bootstrap/Pulumi.foundation.yaml: non-secret placeholders only (RFC-5737 vm.host,
  .invalid offsite); no encryptionsalt/secrets committed (D2). pulumi preview = 0
  resources under the passphrase provider via gitignored file:// state backend.
- Stage-1 vendoring: packages/pulumi-{docker,vault} as @olsitec/* (source-only,
  logic unchanged). vault's 5 type-only imports from modules/olsitec re-homed
  verbatim into pulumi-vault/olsitec-types.ts to keep the egg self-contained.

Realizes PLAN-002 §10 T02; ADR-005 / 000_TOPOLOGY.md §5 Stage-1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Niemann 2026-06-30 18:06:21 +02:00
parent edc708b826
commit 57c4eadea7
26 changed files with 2758 additions and 0 deletions

View file

@ -0,0 +1,139 @@
# T02 — Pulumi project skeleton — Handoff
**Task:** PLAN-002 §10 T02 · **Mode:** BUILD · **Date:** 2026-06-30
**Author role:** implementation agent · **Status:** complete, validated by execution (see below).
> The lead reviews and commits. Nothing was `git add`/`git commit`ed by this task.
---
## 1. Files created / touched
### Workspace root
- `package.json` — Bun workspace root (`@olsitec/foundation`, private, `workspaces: ["packages/*","bootstrap"]`).
- Removed placeholders `bootstrap/.gitkeep`, `packages/.gitkeep` (replaced by real content).
- `bun.lock` — generated by `bun install` (links the workspace; no secrets — verified).
### Vendored module `packages/pulumi-docker/` (`@olsitec/pulumi-docker`)
- `index.ts`, `tsconfig.json`, `.editorconfig`, `.gitignore` — copied **verbatim** from
`/Users/andiolsi/work/olsicloud4/pulumi/modules/docker/`.
- `package.json` — renamed `docker``@olsitec/pulumi-docker`; added `version` `0.0.0`,
`main`/`types` (`index.ts`), and an explicit `@pulumi/pulumi` dependency (see §4).
- `VENDORED.md` — source path, copy date, Stage-1 note, Stage-2 deferral.
### Vendored module `packages/pulumi-vault/` (`@olsitec/pulumi-vault`)
- `index.ts`, `policy.ts`, `tsconfig.json`, `.editorconfig`, `.gitignore` — copied **verbatim**
from `/Users/andiolsi/work/olsicloud4/pulumi/modules/vault/`.
- `olsitec-types.ts`**NEW**: the 5 type-only declarations the upstream `index.ts` imported
from `../../modules/olsitec`, copied verbatim (see §3). The single import line in `index.ts`
was re-pointed `../../modules/olsitec``./olsitec-types`. No runtime logic changed.
- `tsconfig.json``files` extended with `policy.ts` + `olsitec-types.ts` (standalone typecheck).
- `package.json` — renamed `vault``@olsitec/pulumi-vault`; `version`/`main`/`types`; added
explicit `@pulumi/pulumi` dependency (§4).
- `VENDORED.md` — source path, copy date, Stage-1 note, the type-only re-home, and a flag that the
inherited Layer-1 credential surface (`VaultProject`/minio/garage/…) is to be trimmed in a later
Layer-0 refactor (NOT in Stage-1, which preserves source).
### `bootstrap/` (the egg, single Pulumi project)
- `Pulumi.yaml``name: foundation`, `runtime: nodejs` + `options.packagemanager: bun`.
- `package.json` — depends on `@olsitec/pulumi-docker`/`@olsitec/pulumi-vault` (`workspace:*`) + `@pulumi/pulumi`.
- `tsconfig.json` — Olsitec-standard compiler options; `files: [config.ts, index.ts]`.
- `config.ts``FoundationConfig` interface (CONTRACT_001 §1.1) + `loadConfig()` (fails closed) + `sshPrivateKeyPath()` (ENV).
- `index.ts` — no-op scaffold: calls `loadConfig()`, creates NO resources, exports non-secret outputs.
- `Pulumi.foundation.yaml` — NON-secret placeholders only; no secrets, no encryptionsalt.
### Documentation
- `documentation/agents/task_002_pulumi_skeleton/000_subtask_outline.md`
- `documentation/agents/task_002_pulumi_skeleton/003_handoff.md` (this file)
---
## 2. Validated-by-execution vs. authored-only
**Validated by running it (env: bun 1.3.9, pulumi v3.243.0, node v24.10.0, macOS):**
- `bun install` links the workspace; `bun pm ls` shows all three workspace members;
`require.resolve("@olsitec/pulumi-docker"|"@olsitec/pulumi-vault")` from `bootstrap` resolves to
`packages/*/index.ts`. ✅ (acceptance: workspace resolves the two packages)
- `tsc --noEmit` exit 0 for **all three** projects (bootstrap + both vendored packages). ✅
- `pulumi preview` under the **passphrase** secrets provider (local `file://bootstrap/state` backend
+ throwaway `PULUMI_CONFIG_PASSPHRASE` in ENV): exit 0, loads config, **0 real resources** (only the
no-op Stack), prints the scaffold outputs. ✅ (acceptance: preview runs on empty/stub stack, passphrase provider)
- **Fail-closed demonstrated:** removing `foundation:vm.host` + `foundation:features.backup` made
`pulumi preview` exit 1 with one error listing both missing keys; restoring made it pass again. ✅
(acceptance: config rejects missing required keys)
- **No secrets committed:** `bootstrap/state/` is gitignored (`git check-ignore` confirms); the
committed `Pulumi.foundation.yaml` has **no** real `encryptionsalt:`/`secure: v1:` value line
(only explanatory comment prose mentions the words); `bun.lock` has no secret leakage. ✅
**Authored but NOT executed (out of scope — no VM, no `pulumi up` ever):**
- Actual Docker-over-SSH provisioning, Vault init/unseal, any container — all LATER tasks (T03+).
- The vendored modules' runtime behaviour was not exercised (they are libraries; only typechecked +
consumed by the typechecked bootstrap). Their logic is byte-identical to the upstream source apart
from the documented type-only re-home in pulumi-vault.
---
## 3. CONTRACT_001 ambiguities / decisions
- **Config key shape (resolved by execution).** CONTRACT_001 §1.1 shows a nested interface but §1.2
gives flat dotted example keys (`foundation:hosts.forge`, `foundation:features.forgejo`). Pulumi
stores and exposes these **flat**`getObject("hosts")` does NOT reassemble `hosts.forge`+`hosts.vault`
into an object. `loadConfig()` therefore reads each leaf by its full dotted key with the typed
accessor for its kind (`get`/`getNumber`/`getBoolean`, and `getObject` for the two arrays), then
assembles the nested `FoundationConfig`. This is the idiomatic match for the §1.2 YAML and is robust
to Pulumi quoting scalars (`"2222"`, `"true"`). No contract change needed.
- **No further ambiguity blocked typing.** Every §1.1 field maps 1:1 to a placeholder key.
- **Secrets (§1.3) intentionally not required** by `loadConfig()` — they are seeded by later tasks
(T05 Vault capture etc.); requiring them now would block the T02 acceptance (empty/stub stack).
## 4. Bun-vs-npm decision + rationale
**Decision: Bun** (Olsitec footgun 16.3 prefers Bun; 000_TOPOLOGY.md §3 specifies Bun workspaces).
- Pulumi's nodejs runtime supports `options.packagemanager: bun`; `pulumi preview` ran cleanly under
it (the program executes via Pulumi's bundled ts-node against bun-installed deps). **No fallback to
npm was needed.**
- **One vendoring fix forced by Bun's isolated store:** the upstream modules never declared
`@pulumi/pulumi` directly (they relied on it being hoisted into their own `node_modules`). Bun's
`.bun/` store layout does not hoist it where standalone `tsc` on a package can find it, so both
vendored `package.json`s now list `@pulumi/pulumi` (`^3.138.0`, matching olsitec-core) as an
explicit dependency. This is a **packaging-metadata** correction (the import was always real); **no
module logic changed**. It also makes the packages correct for Stage-2 publishing. (This is the
escalation-worthy "vendoring reveals a dependency" item — judged minor and fixed in place, not a
design change.)
## 5. Recommended follow-ups
1. **Strip `encryptionsalt` before each commit of `Pulumi.foundation.yaml`.** Every `pulumi
preview/up` under the passphrase provider re-appends an `encryptionsalt:` line and re-quotes
scalars. The committed file has been left clean; a header comment documents the strip step. Once
the first real secret is added (T05), the encryptionsalt becomes load-bearing and SHOULD then be
committed alongside the `secure: v1:` values — revisit this guidance at that point.
2. **`packages/pulumi-vault` Layer-0 trim (later task).** `VaultBootstrap`/`VaultProject` still carry
the Layer-1 minio/garage/cockroach/mongo credential surface. The egg only needs
`VaultInitialization` (init/unseal capture) + a minimal `VaultBootstrap`. Trim per 000_TOPOLOGY.md
§5.1 "refactor for Layer 0" — out of scope for Stage-1 vendoring.
3. **Run `pin-digests`** to replace `PIN_DIGEST` in `VERSIONS` before any real `pulumi up` (T01/preflight concern).
4. **Stage-2 publish** (`@olsitec/pulumi-*` to the foundation Forgejo npm registry) once it exists —
semantic-release-monorepo + Conventional Commits (000_TOPOLOGY.md §5, memory: olsitec-charts-conventional-commits).
5. **Lint config** not vendored — the upstream modules had no per-package eslint file (only an
`.editorconfig`, which was copied); olsitec-core's `eslint.config.mjs` is project-level. Add a
workspace-level lint setup in a later tooling task if desired.
## 6. How to reproduce the validation
```sh
cd ~/work/olsitec-foundation/foundation
bun install
bunx tsc --noEmit -p bootstrap/tsconfig.json
bunx tsc --noEmit -p packages/pulumi-docker/tsconfig.json
bunx tsc --noEmit -p packages/pulumi-vault/tsconfig.json
cd bootstrap
export PULUMI_HOME="$(pwd)/state/.pulumi-home"
export PULUMI_CONFIG_PASSPHRASE='<throwaway-dev-only>' # never commit
pulumi login "file://$(pwd)/state"
pulumi stack init foundation --secrets-provider=passphrase # first time only
pulumi preview --stack foundation
# NOTE: preview rewrites Pulumi.foundation.yaml — `git checkout` it or strip the
# appended `encryptionsalt:` line before committing. state/ is gitignored.
```