fix(ci): publish the public kit with the Vault token that already exists #215

Merged
peterswimm merged 1 commit from fix/publish-public-kit-vault-token into main 2026-10-17 20:46:18 +00:00
Owner

Publish public design kit / publish-public-repo has never succeeded — every run it has ever had is a failure. This fixes it using credentials and grants that already exist, and fixes a second defect that would have destroyed files the moment it first went green.

Scope

Defect 1 — the secret was never created. The job fails on its first publish line, test -n "$PUBLIC_REPO_TOKEN". That secret does not exist at org scope (11 secrets, none by that name) or repo scope (zero). The log is unambiguous: its last output is the build step's built dist/starter/ and /tmp/public-repo, then exitcode '1' with nothing printed. Reproduced in python:3.12-slim — the build emits exactly those two lines and exits 0; the guard with the secret unset exits 1 printing nothing.

No new credential and no new grant were needed. secret/forge/forgejo/ci holds the forge-ci token, and the ci-runner ACL policy already grants read on it:

# Forgejo automation token: PR and issue calls, registry pushes, auto-merge.
path "secret/data/forge/forgejo/ci" { capabilities = ["read"] }

So the job now fetches it through .forgejo/actions/bastia-secret, exactly as test-generator.yml, block-validation.yml and udg-copy-coverage.yml already fetch secret/forge/rituals-read. One copy of the credential, in OpenBao, rotatable in one place, with the fetch audited — rather than a second copy pasted into a Forgejo Actions secret that then has to be rotated twice.

Defect 2 — the publish would have deleted the public repo's governance. Found while verifying the first, and fixed here because shipping the credential fix alone would have triggered it. The step wipes the target with find … ! -name .git -exec rm -rf then rsync --delete. Diffing the export against the live design-kit-public gives an exact delta of three files the export does not produce:

File What it is
.forgejo/workflows/pr-policy-check.yml the public repo's own policy gate
.forgejo/pull_request_template.md its PR template
CONTRIBUTING.md stamped from core.contributor_review_policy by .spel/bin/forge-policy-sync.sh

The first successful publish would have removed all three. The wipe and the rsync now exclude them.

Out of scope: the export script, the token itself, the ci-runner policy (deliberately not widened — it already covers this path), and the workflow's path filter.

Testing

Check Result
build step, as the workflow runs it built dist/starter/ + /tmp/public-repo, exit 0
test -n "$PUBLIC_REPO_TOKEN" with secret unset exit 1, no output — matches the CI log exactly
forge-ci token → git-upload-pack advertisement 200 (read)
forge-ci token → git-receive-pack advertisement 200 (push authorized)
yamllint .forgejo/workflows/ clean

Push capability was proven by requesting the git-receive-pack advertisement, which the server only serves to a caller authorized to push, and which sends no ref update — so the probe writes nothing. Repo ACL showing push: true is not sufficient evidence on its own, since Forgejo scopes tokens separately from repo permissions.

The preservation fix was verified against a failing control, not just the happy path:

Logic CONTRIBUTING.md .forgejo/ stale dir export lands
old (control) DELETED DELETED removed yes
new KEPT KEPT removed yes

Impact Statement

Risk: moderate, and concentrated in the first successful run — this job has never completed, so its publish path has never actually executed against the real target. What it will do on success is now bounded: replace the generated content, leave the three repo-owned files alone.

Nothing new was provisioned. No Forgejo Actions secret was created, no Vault path written, no AppRole or policy changed. The fix consumes a grant that has been in place all along. If you would rather publish under a dedicated identity than forge-ci, that is a separate decision and this PR does not foreclose it.

What to watch on the first green run: confirm design-kit-public still carries CONTRIBUTING.md and .forgejo/, and that its diff is limited to generated content. The workflow is path-filtered (Brand/UDG/dtcg/**, components/**, templates/**, udg/generators/**, tools/export_public_repo.py, justfile, docs/public-repo.md), so it will not fire on merge of this PR alone — use workflow_dispatch to exercise it deliberately rather than waiting for an unrelated change to trigger the first-ever publish.

Unrelated and still open: Test Generator / test is not a blocking context on main, which is how #210/#211 merged red. That is a branch-protection setting, not a code change.

`Publish public design kit / publish-public-repo` has **never succeeded** — every run it has ever had is a `failure`. This fixes it using credentials and grants that already exist, and fixes a second defect that would have destroyed files the moment it first went green. ## Scope **Defect 1 — the secret was never created.** The job fails on its first publish line, `test -n "$PUBLIC_REPO_TOKEN"`. That secret does not exist at org scope (11 secrets, none by that name) or repo scope (zero). The log is unambiguous: its last output is the *build* step's `built dist/starter/` and `/tmp/public-repo`, then `exitcode '1'` with nothing printed. Reproduced in `python:3.12-slim` — the build emits exactly those two lines and exits **0**; the guard with the secret unset exits **1** printing nothing. **No new credential and no new grant were needed.** `secret/forge/forgejo/ci` holds the `forge-ci` token, and the `ci-runner` ACL policy already grants read on it: ``` # Forgejo automation token: PR and issue calls, registry pushes, auto-merge. path "secret/data/forge/forgejo/ci" { capabilities = ["read"] } ``` So the job now fetches it through `.forgejo/actions/bastia-secret`, exactly as `test-generator.yml`, `block-validation.yml` and `udg-copy-coverage.yml` already fetch `secret/forge/rituals-read`. One copy of the credential, in OpenBao, rotatable in one place, with the fetch audited — rather than a second copy pasted into a Forgejo Actions secret that then has to be rotated twice. **Defect 2 — the publish would have deleted the public repo's governance.** Found while verifying the first, and fixed here because shipping the credential fix alone would have triggered it. The step wipes the target with `find … ! -name .git -exec rm -rf` then `rsync --delete`. Diffing the export against the live `design-kit-public` gives an exact delta of three files the export does not produce: | File | What it is | |---|---| | `.forgejo/workflows/pr-policy-check.yml` | the public repo's own policy gate | | `.forgejo/pull_request_template.md` | its PR template | | `CONTRIBUTING.md` | stamped from `core.contributor_review_policy` by `.spel/bin/forge-policy-sync.sh` | The first successful publish would have removed all three. The wipe and the rsync now exclude them. **Out of scope:** the export script, the token itself, the `ci-runner` policy (deliberately not widened — it already covers this path), and the workflow's path filter. ## Testing | Check | Result | |---|---| | build step, as the workflow runs it | `built dist/starter/` + `/tmp/public-repo`, exit **0** | | `test -n "$PUBLIC_REPO_TOKEN"` with secret unset | exit **1**, no output — matches the CI log exactly | | `forge-ci` token → `git-upload-pack` advertisement | **200** (read) | | `forge-ci` token → `git-receive-pack` advertisement | **200** (**push authorized**) | | `yamllint .forgejo/workflows/` | clean | Push capability was proven by requesting the `git-receive-pack` advertisement, which the server only serves to a caller authorized to push, and which sends no ref update — so the probe writes nothing. Repo ACL showing `push: true` is not sufficient evidence on its own, since Forgejo scopes tokens separately from repo permissions. **The preservation fix was verified against a failing control**, not just the happy path: | Logic | `CONTRIBUTING.md` | `.forgejo/` | stale dir | export lands | |---|---|---|---|---| | old (control) | **DELETED** | **DELETED** | removed | yes | | new | **KEPT** | **KEPT** | removed | yes | ## Impact Statement **Risk: moderate, and concentrated in the first successful run** — this job has never completed, so its publish path has never actually executed against the real target. What it will do on success is now bounded: replace the generated content, leave the three repo-owned files alone. **Nothing new was provisioned.** No Forgejo Actions secret was created, no Vault path written, no AppRole or policy changed. The fix consumes a grant that has been in place all along. If you would rather publish under a dedicated identity than `forge-ci`, that is a separate decision and this PR does not foreclose it. **What to watch on the first green run:** confirm `design-kit-public` still carries `CONTRIBUTING.md` and `.forgejo/`, and that its diff is limited to generated content. The workflow is path-filtered (`Brand/UDG/dtcg/**`, `components/**`, `templates/**`, `udg/generators/**`, `tools/export_public_repo.py`, `justfile`, `docs/public-repo.md`), so it will not fire on merge of this PR alone — use `workflow_dispatch` to exercise it deliberately rather than waiting for an unrelated change to trigger the first-ever publish. **Unrelated and still open:** `Test Generator / test` is not a blocking context on `main`, which is how #210/#211 merged red. That is a branch-protection setting, not a code change.
fix(ci): publish the public kit with the Vault token that already exists
All checks were successful
PR policy check / policy-gate (pull_request) Successful in 1s
Security Scans / Trivy filesystem scan (pull_request) Successful in 58s
Block Validation / validate (pull_request) Successful in 1m23s
Security Scans / Gitleaks secret scan (pull_request) Successful in 57s
Test Generator / lint-workflows (pull_request) Successful in 38s
Test Generator / test (pull_request) Successful in 1m20s
Test Generator / lint-prose (pull_request) Successful in 33s
UDG dist drift gate / dist/ matches dtcg + udg sources (pull_request) Successful in 19s
Copy coverage / Design copy reaching rendered HTML (pull_request) Successful in 50s
70608899fa
`Publish public design kit / publish-public-repo` has never succeeded. Every
run it has ever had, back through the full task history, is a failure.

The cause is one line: `test -n "$PUBLIC_REPO_TOKEN"`. That secret does not
exist -- not at org scope (11 secrets, none by that name) and not at repo
scope (zero secrets) -- so the guard exits 1 before the job does anything.
It matches the log exactly: the last output is the build step's own
`built dist/starter/` and `/tmp/public-repo`, then exitcode 1 with nothing
printed. Reproduced in python:3.12-slim: the build emits precisely those two
lines and exits 0, and the guard with the secret unset exits 1 silently.

No new credential and no new grant are needed, because both already exist.
secret/forge/forgejo/ci holds the forge-ci token, and ci-runner's ACL policy
already grants read on that path -- its comment reads "Forgejo automation
token: PR and issue calls, registry pushes, auto-merge". Verified the token
is authorized to push to Toilville/design-kit-public by requesting the
git-receive-pack advertisement, which requires push authorization and sends
no ref update: 200. So this job now fetches it through .forgejo/actions/
bastia-secret, the same way test-generator.yml, block-validation.yml and
udg-copy-coverage.yml already fetch secret/forge/rituals-read. That keeps one
copy of the credential, in OpenBao, rotatable in one place, with the fetch
audited -- rather than pasting a second copy into a Forgejo Actions secret.

SECOND DEFECT, found while verifying the first and fixed here because
shipping the credential fix alone would have triggered it. The publish step
wipes the target with `find ... ! -name .git -exec rm -rf` followed by
`rsync --delete`. The public repo holds three files this export does not
produce: .forgejo/workflows/pr-policy-check.yml, .forgejo/
pull_request_template.md, and CONTRIBUTING.md -- which is stamped from
core.contributor_review_policy by .spel/bin/forge-policy-sync.sh. Diffed the
export against the live repo: those three are exactly the delta, and the
first successful publish would have deleted all of them, silently removing
the public repo's policy gate and the org contributor-review stamp. The wipe
and the rsync now exclude them. Verified against a control: with the old
logic both are DELETED, with the new logic both are KEPT while stale
directories are still removed and the export still lands.
peterswimm deleted branch fix/publish-public-kit-vault-token 2026-10-17 20:46:18 +00:00
Sign in to join this conversation.
No description provided.