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
24 changes: 24 additions & 0 deletions .github/workflows/verify-release-publication.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@ concurrency:
cancel-in-progress: false

jobs:
production-credential-scope:
runs-on: ubuntu-latest
timeout-minutes: 5
environment: diffex-release-upload
steps:
- name: Checkout reviewed source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22.19.0'
- name: Verify production publisher bucket isolation without writes
env:
AWS_ACCESS_KEY_ID: ${{ secrets.DIFFEX_ARTIFACTS_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DIFFEX_ARTIFACTS_R2_SECRET_ACCESS_KEY }}
run: node scripts/verify-release-credentials.mjs --production

verify:
runs-on: ubuntu-latest
timeout-minutes: 15
Expand All @@ -39,6 +58,11 @@ jobs:
run: npm ci --ignore-scripts
- name: Verify AWS CLI
run: aws --version
- name: Verify staging publisher bucket isolation
env:
AWS_ACCESS_KEY_ID: ${{ secrets.DIFFEX_ARTIFACTS_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DIFFEX_ARTIFACTS_R2_SECRET_ACCESS_KEY }}
run: node scripts/verify-release-credentials.mjs --staging
- name: Check controlled publisher regressions
run: node --test scripts/publish-release-announcement.test.mjs
- name: Verify publication against staging storage
Expand Down
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,11 @@ Attribution:
## User Override

If the user's instructions conflict with any rule in this document, ask for explicit confirmation before overriding. Only then execute their instructions.

- 2026-09-13: Publication checks assumed an HTTPS repository field and invocation directory -> Inspect canonical package metadata and run workspace inventory from the owning repository before validating trust.

- 2026-09-13: An unnamed historical credential note was presented as a known active key -> Identify the credential and verify its current scope/state before proposing retirement or treating it as a blocker.

- 2026-09-13: npm created latest on first publication despite a bootstrap tag -> Verify registry behavior, clean only a newly uploaded exact bootstrap artifact, and retain a regression for partial retries.

- 2026-09-13: Node rejects package import targets containing node_modules -> Preserve existing package import targets and validate staged tarballs through installed CLI startup before publication.
47 changes: 36 additions & 11 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Releasing Diffex

Diffex publishes its public workspaces in lockstep and uses GitHub Actions trusted publishing for normal npm releases.
Diffex publishes only `@diffexai/diffex` and uses GitHub Actions trusted publishing for normal npm releases.
The release tag publishes the approved npm packages and then announces the verified npm release.
Standalone archives and other installation channels are deferred by the [approved distribution policy](docs/release/distribution-policy.json).

Expand All @@ -14,7 +14,9 @@ Standalone archives and other installation channels are deferred by the [approve

## Approved payloads

The eleven public packages are derived from `scripts/release-packages.mjs`.
The single public CLI package is derived from `scripts/release-packages.mjs`.
Internal workspaces remain private and required runtime modules are included inside the CLI tarball.
The generated publication manifest and installation locks contain no registry dependencies on separate Diffex packages.
Every package declares Apache-2.0 and ships the full root license, including the complete MIT notice for Pi-derived portions.
The generated `NOTICE`, `THIRD-PARTY-NOTICES.txt`, and `distribution-components.json` identify applicable portions and bundled components.
Readable runtime JavaScript, declarations, runtime JSON/SQL data, HTML/CSS templates, and documentation are approved.
Expand All @@ -33,35 +35,56 @@ The CLI tarball carries `release/distribution-manifest.json`, `release/install-p
Each package carries `distribution-files.json` with SHA-256 hashes for its approved runtime files.
The complete tarball's npm `dist.integrity` is the registry verification value.
Versioned anonymous downloads use `https://registry.npmjs.org/<PACKAGE>/-/<UNSCOPED_NAME>-<VERSION>.tgz`.
For the CLI at version 0.2.0, that URL is `https://registry.npmjs.org/@diffexai/diffex/-/diffex-0.2.0.tgz`; it becomes available only after separately authorized publication.
For the CLI at version 0.2.1, that URL is `https://registry.npmjs.org/@diffexai/diffex/-/diffex-0.2.1.tgz`; it becomes available only after separately authorized publication.
No GitHub release publication or separate asset-repository credential is used.
Npm trust remains bound to the private source repository and the existing workflow filename.

## First npm publication

The owner has already verified npm account and organization prerequisites; do not repeat login or ownership checks unless authentication fails.
Use the approved interactive npm session for bootstrap and trust configuration.
npm requires a package to exist before a trusted publisher can be configured.
Use this bootstrap process only once, before the first normal release.

1. Sign in with an npm account that can publish public packages under `@diffexai`.
1. Confirm the current candidate has the owner's `/cl` refresh, approved payload review, and local/native artifact acceptance.
Configure the reviewed `npm-publish` GitHub environment before public bootstrap publication.
See [publication trust and bootstrap evidence](docs/release/publication-trust.md) for the exact settings and outstanding owner decisions.

2. Build and validate every package without publishing anything.

```bash
npm login
npm run release:bootstrap-npm:dry
```

2. Build and validate every package without publishing anything.
For frozen candidate inputs, use the same offline build path as normal CI from a clean committed checkout:

```bash
npm run release:bootstrap-npm:dry
npm run hydrate:model-data
node scripts/release-artifacts.mjs freeze /tmp/diffex-bootstrap-candidate
npm run build:offline
node scripts/release-artifacts.mjs prepare /tmp/diffex-bootstrap-candidate
node scripts/publish.mjs --bootstrap --dry-run --artifacts /tmp/diffex-bootstrap-candidate
```

Retain the entire private directory, including `manifest.json` and frozen source inputs.
Publish or retry these exact reviewed bytes with `node scripts/publish.mjs --bootstrap --artifacts /tmp/diffex-bootstrap-candidate`; do not rebuild them.
A new source commit requires a new reviewed candidate directory before any publication.

3. Publish the current lockstep version under the non-default `bootstrap` dist-tag.

```bash
npm run release:bootstrap-npm
```

Bootstrap publication deliberately omits provenance because it runs interactively outside the trusted CI environment.
It does not assign the `latest` dist-tag.
Both bootstrap and normal publication explicitly disable provenance because npm does not support it for the private source repository.
Normal CI still authenticates through the exact OIDC trusted publisher; it needs no npm token secret.
npm can create `latest` on first registration despite `--tag bootstrap`.
Bootstrap verifies each newly uploaded artifact and removes only an automatically created `latest` pointing at that exact version, then checks that only the intended bootstrap tag remains.
There can be a registry exposure window between first publication and successful tag removal; retain actual read-back evidence rather than assuming the tag flag prevented it.
An existing `latest` tag blocks bootstrap before uploads.
Failed tag cleanup stops the batch and requires operator inspection of the retained manifest, exact published bytes, and tags before removing the unintended tag with `npm dist-tag rm <package> latest --registry=https://registry.npmjs.org`.
Never remove an established release tag or a tag pointing at another version.
After cleanup or a propagation failure, rerun against the same retained artifact directory without rebuilding.
The command is resumable after a partial failure: it downloads and hashes packages already published at the exact workspace version against the retained approved artifacts and refuses packages that already exist only at other versions.

4. Run every `npm trust github` command printed by the bootstrap command.
Expand All @@ -70,15 +93,17 @@ Use this bootstrap process only once, before the first normal release.
- Repository: `diffexai/diffex`
- Workflow file: `build-binaries.yml`
- Environment: `npm-publish`
- Allowed action: `npm publish`
- Allowed action: `npm publish`; npm also automatically grants `npm stage publish` on relationships created after 2026-09-03.

5. Verify each package has the expected trusted publisher.

```bash
npm trust list @diffexai/diffex
node scripts/npm-publication-trust.mjs --verify
```

6. Restrict traditional token publishing after trusted publishing is verified.
In every package's npm access settings, require two-factor authentication and disallow tokens, then retain the reviewed settings alongside the trust evidence.
Do not revoke unrelated account tokens or claim enforcement from an OIDC configuration alone.

## Normal release

Expand Down
23 changes: 8 additions & 15 deletions docs/release/artifact-acceptance.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,10 @@ Runtime imports and representative operations execute in fresh Node processes.
Installed runtime inventories must equal the inventory extracted from the checked tarball, and every recorded installed file must match its SHA-256 digest.
The operation inventory fails closed if a new public package has no representative operation.

| Package | Representative operation |
| --- | --- |
| `diffex-agent-core` | Construct an agent and change its state |
| `diffex-ai` | Complete and consume an assistant event stream |
| `diffex-client` | Construct and dispose a client; additionally list a hosted session over WebSocket |
| `diffex` | Initialize model runtime, load clipboard native module, execute image WASM, export a session to HTML |
| `diffex-coding-agent-server` | Close usage reporter; start its actual executable, reject wrong credentials, list its session, terminate cleanly |
| `diffex-harness-state` | Persist and reload the initial harness version |
| `diffex-protocol` | Encode and decode CBOR |
| `diffex-server` | Start and close a loopback WebSocket listener |
| `diffex-session-backend-sqlite-node` | Create, write, and query SQLite |
| `diffex-telemetry` | Execute a telemetry span callback |
| `diffex-tui` | Render text and load the current platform's native modifier helper where applicable |
The current public inventory contains only `@diffexai/diffex`.
Its required internal modules ship inside the tarball, and installation must succeed while separate Diffex registry requests are blocked.
The representative CLI operation initializes model runtime, loads clipboard native code, executes image WASM, and exports a session to HTML.
Hosted server packages remain private and are covered by repository tests rather than public npm artifact acceptance.

The packed CLI additionally executes help, version, model listing, controlled inference, a terminal prompt/reply, account-menu inspection, and clean exit.
The terminal path exercises packaged themes and the HTML export path exercises its templates, CSS, and bundled renderer resources.
Expand Down Expand Up @@ -75,11 +66,13 @@ shasum -a 256 -c /path/to/private-evidence/SHA256SUMS

On Linux, `sha256sum -c` accepts the same checksum file.
Regression coverage alters archive bytes and confirms verification fails before installation.
The release workflow retains the source snapshot and manifests privately, while npm publication supplies registry integrity and provenance through the existing publisher.
The release workflow retains the source snapshot and manifests privately, while npm publication supplies registry integrity through the existing publisher.
Trusted publishing authenticates the private source workflow, but npm does not generate provenance for private repositories.
Public acceptance must compare the actual downloaded npm bytes with these retained identities after separately authorized publication.

The baseline CLI-only acceptance passed on Darwin arm64 but emitted no per-package operation or hosted-server result.
The expanded local acceptance covers all eleven packages for both managers and the server lifecycle.
The previous expanded acceptance covered eleven packages and the hosted server lifecycle before the owner reduced the public inventory on 2026-09-13.
Current acceptance validates the complete CLI-only tarball through both managers.
The owner specified that no live provider test environment is available, so real login and inference remain pending.
Native runner results and exact committed-candidate evidence are recorded in the implementation pull request and accompanying acceptance record.
Public downloads and public-to-public upgrades remain unexecuted until publication; local fixture inference does not establish those results.
76 changes: 76 additions & 0 deletions docs/release/cli-only-candidate-evidence.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"sourceCommit": "f2a661684af35fe6e9a3209c6f9720144e1fd423",
"version": "0.2.1",
"packages": [
{
"name": "@diffexai/diffex",
"version": "0.2.1",
"filename": "diffexai-diffex-0.2.1.tgz",
"size": 3530237,
"sha256": "64c161d63ef416f6bf116a6b35953225dc49c236ccba0ef4b88c2ac8ff4452b0",
"integrity": "sha512-ZBrTmPGnJy/C7u8DhZA/owIDELJGzPf2ujbRtZCPyvSfQEAtXGZk2C0PQ1/ctbyFKMXXpkXPlO7Asm5E/s//5g=="
}
],
"localAcceptance": {
"platform": "darwin",
"arch": "arm64",
"node": "v22.19.0",
"status": "passed",
"cleanup": true,
"packages": [
{
"manager": "npm",
"managerVersion": "10.9.3",
"name": "@diffexai/diffex",
"version": "0.2.1",
"entrypoints": [
"@diffexai/diffex",
"@diffexai/diffex/rpc-entry",
"@diffexai/diffex/client"
],
"operation": "passed",
"localDependencies": [
"@diffexai/diffex"
]
},
{
"manager": "bun",
"managerVersion": "1.3.11",
"name": "@diffexai/diffex",
"version": "0.2.1",
"entrypoints": [
"@diffexai/diffex",
"@diffexai/diffex/rpc-entry",
"@diffexai/diffex/client"
],
"operation": "passed",
"localDependencies": [
"@diffexai/diffex"
]
}
],
"processes": [
{
"manager": "npm",
"server": "Excluded from CLI distribution; hosted runtime remains private",
"terminal": "passed",
"helpers": {
"fd": "fd 10.4.2",
"rg": "ripgrep 15.2.0 (rev e89fff89ac)\n\nfeatures:+pcre2\nsimd(compile):+NEON\nsimd(runtime):+NEON\n\nPCRE2 10.45 is available (JIT is available)"
}
},
{
"manager": "bun",
"server": "Excluded from CLI distribution; hosted runtime remains private",
"terminal": "passed",
"helpers": {
"fd": "fd 10.4.2",
"rg": "ripgrep 15.2.0 (rev e89fff89ac)\n\nfeatures:+pcre2\nsimd(compile):+NEON\nsimd(runtime):+NEON\n\nPCRE2 10.45 is available (JIT is available)"
}
}
]
},
"nativeWorkflow": "https://github.com/diffexai/diffex/actions/runs/34765173973",
"nativeStatus": "Blocked before execution by GitHub billing or spending limit",
"published": false
}
22 changes: 18 additions & 4 deletions docs/release/distribution-policy.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"status": "approved",
"sourceRepository": "diffexai/diffex",
"readableJavaScript": "allow-for-all-eleven-public-packages",
"readableJavaScript": "allow-for-cli-and-bundled-runtime-workspaces",
"declarations": "allow",
"typeScriptExamples": "exclude",
"nativeSources": "exclude-subject-to-redistribution-review",
Expand All @@ -24,8 +24,8 @@
"interactiveContainer": "defer"
},
"approval": {
"date": "2026-09-12",
"authority": "owner explicit approval in implementation conversation"
"date": "2026-09-13",
"authority": "owner approved CLI-only 0.2.1 replacement and retirement of 0.2.0 and separate packages"
},
"standaloneDeferral": "Bun static runtime redistribution and native dependency source/object review must be completed before enabling standalone publication.",
"approvedRuntimeAssets": [
Expand All @@ -41,5 +41,19 @@
"publicHosting": "npmjs",
"publicRegistry": "https://registry.npmjs.org",
"immutableTarballUrl": "https://registry.npmjs.org/<PACKAGE>/-/<UNSCOPED_NAME>-<VERSION>.tgz",
"verification": "npm dist.integrity verifies complete tarball bytes against registry metadata; distribution-files.json records SHA-256 for approved runtime files"
"verification": "npm dist.integrity verifies complete tarball bytes against registry metadata; distribution-files.json records SHA-256 for approved runtime files",
"npmPackages": [
"@diffexai/diffex"
],
"bundledWorkspaces": [
"agent",
"ai",
"client",
"evolution",
"harness-state",
"harness-trace",
"protocol",
"telemetry",
"tui"
]
}
Loading
Loading