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
2 changes: 0 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ name: CI

on:
pull_request:
push:
branches: [master]

permissions:
contents: read
Expand Down
396 changes: 317 additions & 79 deletions .github/workflows/release.yml

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,18 @@
- Never use Secret Effects to supply this repository's deployment or release secrets.
- Configure bootstrap secrets in GitHub and inject Worker secrets through Alchemy.
- Publish the npm client only from the canonical `paperkeel/secret-effects` repository.
- Treat each successful canonical `master` CI run as a complete production
release.
- Treat each canonical `master` push as a complete production release.
- Increment every workspace package version before a canonical merge to
`master`.
- Cache only encrypted bundles.
- Keep decrypted values in request-local memory.
- Use Effect v4 for application services and typed errors.
- Use Zod and `@t3-oss/env-core` for repository environment schemas.
- Use Alchemy v2 for all Cloudflare resources.
- Use Blacksmith runners for all GitHub Actions jobs.
- Use Blacksmith runners for all build, test, deploy, and release finalization
jobs.
- Use a GitHub-hosted runner only for the npm Trusted Publishing job. That job
publishes the package artifact that Blacksmith built and tested.
- Use `master` as the default branch.
- Run `pnpm check` before each commit.

Expand Down
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@secret-effects/api",
"version": "0.2.1",
"version": "0.2.2",
"private": true,
"type": "module",
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@secret-effects/cli",
"version": "0.2.1",
"version": "0.2.2",
"private": true,
"type": "module",
"bin": {
Expand Down
66 changes: 55 additions & 11 deletions docs/OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,60 @@ pnpm run deploy
Alchemy owns D1, R2, Queue, Worker, cache, and Durable Object bindings. Production
resources use retain protection and adopt existing named resources.

In the canonical repository, each successful `master` CI run is a complete
release. The release workflow checks and builds the exact commit. It deploys the
commit, checks the commit from `/health`, publishes the matching npm package,
creates a signed version tag, and creates the GitHub release.
In the canonical repository, each `master` push starts a complete release. The
release workflow checks and builds the exact commit on Blacksmith. It deploys
the commit and checks the commit from `/health`.

Blacksmith packs and tests the npm package. A GitHub-hosted runner publishes
that package with npm Trusted Publishing. Blacksmith then creates the signed
version tag, attests the command archive, and creates the GitHub release.

Increment the root, API, command interface, client, cryptography, and protocol
versions before each canonical merge. The workflow rejects a version that
belongs to a different commit. GitHub queues up to 100 release runs and does not
replace an earlier pending `master` release.
versions before each canonical merge. The workflow rejects a package with
different bytes or source provenance. GitHub queues up to 100 release runs. It
does not replace an earlier pending `master` release.

## npm Trusted Publishing

npm requires a package to exist before it can have a trusted publisher. Publish
`@paperkeel/secret-effects-client@0.2.1` once from the signed `v0.2.1` tag. Use
an interactive npm session with two-factor authentication. Verify the tag and
package bytes before you publish. This first publication is the only release
without Trusted Publishing provenance.

Configure one npm trusted publisher for
`@paperkeel/secret-effects-client`:

| Name | Value |
| ------------ | ---------------- |
| Provider | GitHub Actions |
| Organization | `paperkeel` |
| Repository | `secret-effects` |
| Workflow | `release.yml` |
| Environment | `release` |
| Permission | `npm publish` |

Configure the publisher with npm 11.15.0 or later:

```sh
npm trust github @paperkeel/secret-effects-client \
--repo paperkeel/secret-effects \
--file release.yml \
--env release \
--allow-publish
```

The npm publish job uses a GitHub-hosted runner because npm does not accept
OIDC claims from self-hosted runners. The job receives the immutable package
artifact from Blacksmith. It does not receive an npm token.

The job checks the artifact digest, file checksums, package integrity, source
commit, workflow path, repository, and hosted-runner identity. npm adds the
SLSA provenance during publication.

After the first trusted publication, configure npm to require two-factor
authentication and reject traditional publishing tokens. Remove `NPM_TOKEN`
from the GitHub release environment.

## Bootstrap

Expand Down Expand Up @@ -157,7 +202,6 @@ these values manually in GitHub:
- `SECRET_EFFECTS_ISSUER_PRIVATE_KEY`
- `SECRET_EFFECTS_GLOBAL_ADMIN_TOKEN`
- `SENTRY_DSN`, when Sentry is active
- `NPM_TOKEN`, only in the canonical Paperkeel release environment
- `RELEASE_SIGNING_PRIVATE_KEY`, only in the canonical Paperkeel release
environment

Expand All @@ -167,9 +211,9 @@ bindings into Cloudflare. Do not configure `SECRET_EFFECTS_KEY` for this
repository.

Forks and deployment copies do not publish the client package or create
Paperkeel release tags. Their release workflow deploys the successful `master`
commit and skips the canonical publication job. Applications install the
canonical public package from Paperkeel.
Paperkeel release tags. Their release workflow deploys their `master` commit
and skips the canonical publication job. Applications install the canonical
public package from Paperkeel.

## Runtime use

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "secret-effects",
"version": "0.2.1",
"version": "0.2.2",
"private": true,
"type": "module",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Effects. It combines these functions:
Install only this package:

```sh
pnpm add @paperkeel/secret-effects-client@0.2.1
pnpm add @paperkeel/secret-effects-client@0.2.2
```

Paperkeel publishes this package from the canonical Secret Effects repository.
Expand Down
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@paperkeel/secret-effects-client",
"version": "0.2.1",
"version": "0.2.2",
"description": "Type-safe Secret Effects environment loading for Node.js and Cloudflare Workers",
"type": "module",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/crypto/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@secret-effects/crypto",
"version": "0.2.1",
"version": "0.2.2",
"type": "module",
"license": "MIT",
"exports": {
Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@secret-effects/protocol",
"version": "0.2.1",
"version": "0.2.2",
"type": "module",
"license": "MIT",
"exports": {
Expand Down
29 changes: 25 additions & 4 deletions scripts/check-bootstrap-boundary.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,19 @@ const releaseWorkflow = readFileSync(
);
const releaseDocument = parse(releaseWorkflow);
const releaseTriggers = releaseDocument?.on;
const workflowRunBranches = releaseTriggers?.workflow_run?.branches;
const releaseBranches = releaseTriggers?.push?.branches;
const publishJob = releaseDocument?.jobs?.["publish-npm"];
if (releaseWorkflow.includes("SECRET_EFFECTS_KEY")) {
throw new Error(
"The release workflow must use only manual bootstrap credentials.",
);
}
if (
releaseWorkflow.includes("NPM_TOKEN") ||
releaseWorkflow.includes("NODE_AUTH_TOKEN")
) {
throw new Error("The release workflow must use npm Trusted Publishing.");
}
if (
!releaseWorkflow.includes(
"if: github.repository == 'paperkeel/secret-effects'",
Expand All @@ -69,14 +76,28 @@ if (
throw new Error("Only the canonical repository can publish the npm client.");
}
if (
!Array.isArray(workflowRunBranches) ||
!workflowRunBranches.includes("master") ||
!Array.isArray(releaseBranches) ||
!releaseBranches.includes("master") ||
releaseTriggers?.push?.tags !== undefined
) {
throw new Error("The release workflow must release each master push.");
}
if (
publishJob?.["runs-on"] !== "ubuntu-24.04" ||
publishJob?.environment !== "release" ||
publishJob?.permissions?.["id-token"] !== "write"
) {
throw new Error(
"The release workflow must promote successful master CI runs.",
"The npm publish job must use GitHub-hosted Trusted Publishing.",
);
}
for (const jobName of ["prepare", "finalize"]) {
if (
!releaseDocument?.jobs?.[jobName]?.["runs-on"]?.startsWith("blacksmith-")
) {
throw new Error(`${jobName} must use a Blacksmith runner.`);
}
}

process.stdout.write("Bootstrap boundary validation passed.\n");

Expand Down
Loading