Skip to content
This repository was archived by the owner on Aug 3, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
5ff1640
feat(storage): implement package storage and VPM lifecycle
Yeusepe Jul 24, 2026
ab61855
test(ci): bound dependency and Convex checks
Yeusepe Jul 24, 2026
e8e97a5
feat(storage): manage package version deletion
Yeusepe Jul 24, 2026
3caa1bb
feat(packages): complete local upload and VPM lifecycle
Yeusepe Jul 24, 2026
bb2012c
refactor(web): use shared account icons
Yeusepe Jul 24, 2026
e1d4856
feat(storage): implement server-only package delivery
Yeusepe Jul 24, 2026
7d6a58c
fix(auth): add resumable Better Auth migration
Yeusepe Jul 24, 2026
e6548b1
feat(packages): complete verified bootstrap flow
Yeusepe Jul 25, 2026
b8922cd
feat(packages): complete secure package delivery
Yeusepe Jul 26, 2026
951ef7c
feat(packages): finish storage and VPM lifecycle
Yeusepe Jul 27, 2026
30ecd15
fix(storage): address package lifecycle review findings
Yeusepe Jul 27, 2026
b7a0269
fix(storage): fence package delivery review races
Yeusepe Jul 27, 2026
ad5a379
fix(packages): group explicitly associated products
Yeusepe Jul 27, 2026
7d785ff
fix(importer): authorize protected uninstall without delivery
Yeusepe Jul 27, 2026
1a47952
fix(ci): restore package lifecycle acceptance gates
Yeusepe Jul 27, 2026
7c765c4
chore(importer): pin version 0.1.42
Yeusepe Jul 27, 2026
e82f402
fix(storage): preserve upgrade and retry invariants
Yeusepe Jul 27, 2026
5edf7b7
fix(ci): isolate Convex deployment preflight
Yeusepe Jul 27, 2026
62cbbbe
test(e2e): bind manual method identity
Yeusepe Jul 27, 2026
2d74856
fix(auth): preserve package broker resource
Yeusepe Jul 27, 2026
74e9846
chore(importer): pin version 0.1.43
Yeusepe Jul 27, 2026
6229760
fix(storage): configure production TUF reader
Yeusepe Jul 27, 2026
ce36791
chore(importer): pin version 0.1.44
Yeusepe Jul 27, 2026
8f57a38
fix(storage): configure materialization control plane
Yeusepe Jul 27, 2026
62d78c3
fix(storage): migrate legacy ready releases
Yeusepe Jul 27, 2026
b665e1f
fix(delivery): reject same-isolate DPoP replays
Yeusepe Jul 27, 2026
c695e2f
chore(importer): pin version 0.1.45
Yeusepe Jul 27, 2026
df5ed75
chore(importer): pin version 0.1.46
Yeusepe Jul 27, 2026
7b10c12
fix(delivery): recover completed authorization retries
Yeusepe Jul 27, 2026
055b038
test(importer): preserve failed Unity results
Yeusepe Jul 27, 2026
3910ec1
chore(importer): pin version 0.1.47
Yeusepe Jul 27, 2026
d2f24a7
fix(materialization): configure bounded local cache
Yeusepe Jul 27, 2026
7bd3ba2
fix(delivery): renew bounded install grants
Yeusepe Jul 27, 2026
e89ed38
fix(vpm): enforce persistent importer pins
Yeusepe Jul 27, 2026
bd3fcd9
fix(tuf): renew installer metadata publications
Yeusepe Jul 27, 2026
149ac2f
fix(materialization): complete control secret inventory
Yeusepe Jul 27, 2026
91f2623
style(materialization): format local capacity settings
Yeusepe Jul 27, 2026
84639a2
style(vpm): organize importer pin imports
Yeusepe Jul 27, 2026
9fba0da
fix(delivery): retain protected release pins
Yeusepe Jul 27, 2026
14b6d6a
fix(materialization): recover expired verification jobs
Yeusepe Jul 27, 2026
7b3a994
fix(storage): align imported storage format
Yeusepe Jul 27, 2026
5d84cd8
chore(vpm): pin importer version 0.1.49
Yeusepe Jul 27, 2026
c45e127
fix(broker): isolate user credential flows
Yeusepe Jul 27, 2026
744e45b
ci(broker): verify native module builds
Yeusepe Jul 27, 2026
56ea8a6
chore(vpm): pin importer version 0.1.50
Yeusepe Jul 27, 2026
383870c
ci(broker): align native platform verification
Yeusepe Jul 27, 2026
8412068
chore(vpm): pin importer version 0.1.52
Yeusepe Jul 27, 2026
a7eca48
fix(delivery): harden protected rendition delivery
Yeusepe Jul 27, 2026
2ef58cb
fix(installer): authorize retained release uninstall
Yeusepe Jul 27, 2026
f4a5f81
chore(vpm): pin importer version 0.1.53
Yeusepe Jul 27, 2026
5aff6bc
feat(installer): bootstrap signed package runtime
Yeusepe Jul 27, 2026
2645b73
fix(installer): contain broker process lifetime
Yeusepe Jul 27, 2026
1591e68
fix(installer): sign local runtime on Linux
Yeusepe Jul 27, 2026
9203d19
fix(delivery): reject materialization source replay
Yeusepe Jul 27, 2026
6a28ed1
fix(installer): verify portable runtime publisher
Yeusepe Jul 27, 2026
feac472
fix(dev): recover interrupted Convex lock
Yeusepe Jul 27, 2026
87618e5
fix(storage): fence exact-version deletion
Yeusepe Jul 27, 2026
50e39a2
feat(storage): operate exact-version garbage collection
Yeusepe Jul 27, 2026
08f8a38
fix(coupling): isolate unavailable source jobs
Yeusepe Jul 27, 2026
51d8253
fix(dev): use kernel-owned Convex lock
Yeusepe Jul 27, 2026
5748f79
fix(storage): use database time for garbage collection
Yeusepe Jul 27, 2026
2886cc6
fix(dev): verify portable importer runtime
Yeusepe Jul 27, 2026
654a41e
fix(ci): cache licensed icon module by generation fingerprint
Yeusepe Jul 27, 2026
5242f43
chore(ci): retrigger builds after B2 download-cap reset
Yeusepe Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11 changes: 11 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": 1,
"isRoot": true,
"tools": {
"vrchat.vpm.cli": {
"version": "0.1.28",
"commands": ["vpm"],
"rollForward": false
}
}
}
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ DELIVERY_HMAC_KEY= # signs delivery URLs; generate: openssl rand -
DELIVERY_BASE_URL= # public delivery Worker origin, e.g. https://delivery.example.com
STORAGE_FORMAT_VERSION=desync-uncompressed-sha256-v1 # delivery manifest format
VPM_BASE_URL= # optional public API origin for buyer VPM indexes; routes return 503 when unset
VPM_ALIAS_PUBLICATION_CATALOG_DATABASE_URL= # PostgreSQL workflow store for immutable VPM alias publications
CAS_S3_READONLY_ACCESS_KEY_ID= # B2 read-only keyID the Worker uses to fetch private chunks on cache miss
CAS_S3_READONLY_SECRET_ACCESS_KEY= # B2 read-only applicationKey for the Worker
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ jobs:
HEROUI_AUTH_TOKEN: ${{ secrets.HEROUI_AUTH_TOKEN }}
run: bun install --frozen-lockfile

- name: Restore licensed icon module
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
path: apps/web/src/icons/generated.tsx
key: licensed-icons-${{ hashFiles('apps/web/src/icons/manifest.ts', 'ops/icons/generationFingerprint.ts', 'ops/icons/renderGeneratedModule.ts', 'ops/icons/transform.ts') }}

- name: Build web app
env:
ASSETS_S3_BUCKET: ${{ secrets.ASSETS_S3_BUCKET }}
Expand Down Expand Up @@ -102,6 +108,12 @@ jobs:
HEROUI_AUTH_TOKEN: ${{ secrets.HEROUI_AUTH_TOKEN }}
run: bun install --frozen-lockfile

- name: Restore licensed icon module
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
path: apps/web/src/icons/generated.tsx
key: licensed-icons-${{ hashFiles('apps/web/src/icons/manifest.ts', 'ops/icons/generationFingerprint.ts', 'ops/icons/renderGeneratedModule.ts', 'ops/icons/transform.ts') }}

- name: Run external integration contract gate
env:
ASSETS_S3_BUCKET: ${{ secrets.ASSETS_S3_BUCKET }}
Expand Down Expand Up @@ -151,6 +163,12 @@ jobs:
HEROUI_AUTH_TOKEN: ${{ secrets.HEROUI_AUTH_TOKEN }}
run: bun install --frozen-lockfile

- name: Restore licensed icon module
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
path: apps/web/src/icons/generated.tsx
key: licensed-icons-${{ hashFiles('apps/web/src/icons/manifest.ts', 'ops/icons/generationFingerprint.ts', 'ops/icons/renderGeneratedModule.ts', 'ops/icons/transform.ts') }}

- name: Run web test gate
env:
ASSETS_S3_BUCKET: ${{ secrets.ASSETS_S3_BUCKET }}
Expand Down
92 changes: 92 additions & 0 deletions .github/workflows/native-transfer-helper.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: Native Transfer Helper

on:
push:
branches: [main, develop]
paths:
- ".github/workflows/native-transfer-helper.yml"
- "Verify/Native/transfer-helper/**"
pull_request:
branches: [main, develop]
paths:
- ".github/workflows/native-transfer-helper.yml"
- "Verify/Native/transfer-helper/**"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: native-transfer-helper-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
native:
name: Native ${{ matrix.platform }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- platform: Linux
runner: ubuntu-latest
- platform: Windows
runner: windows-latest
defaults:
run:
working-directory: Verify/Native/transfer-helper
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false

# Official setup contract: https://github.com/actions/setup-go
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c
with:
go-version-file: 'Verify/Native/transfer-helper/go.mod'
cache-dependency-path: 'Verify/Native/transfer-helper/go.sum'

- name: Test Windows native module
if: runner.os == 'Windows'
env:
GOFLAGS: -mod=readonly
run: go test ./...

- name: Test portable Linux packages
if: runner.os == 'Linux'
env:
GOFLAGS: -mod=readonly
shell: bash
run: |
set -euo pipefail
go test ./cmd/yucp-local-tuf-repository ./cmd/yucp-tuf-online-repository ./cmd/yucp-tuf-root
go test ./internal/delivery ./internal/dpop ./internal/packagecontract ./internal/reconstructor
go test ./internal/trust ./internal/tufclient ./internal/tufrepository ./internal/tufroot

- name: Build Linux commands
if: runner.os == 'Linux'
env:
GOFLAGS: -mod=readonly
shell: bash
run: |
set -euo pipefail
go build ./cmd/yucp-transfer-helper
go build ./cmd/yucp-tuf-root
go build ./cmd/yucp-tuf-online-repository
go build ./cmd/yucp-local-tuf-repository

- name: Build Windows commands
if: runner.os == 'Windows'
env:
GOFLAGS: -mod=readonly
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
go test -tags=integrationharness ./cmd/yucp-package-broker-test-harness
go build ./cmd/yucp-transfer-helper
go build ./cmd/yucp-package-broker
go build -tags=integrationharness ./cmd/yucp-package-broker-test-harness
go build ./cmd/yucp-tuf-root
go build ./cmd/yucp-tuf-online-repository
go build ./cmd/yucp-local-tuf-repository
10 changes: 8 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
/apps/web/.cloudflare.deploy.vars
/apps/web/.wrangler
/\.wrangler
/services/delivery-worker/.wrangler
/services/*/.wrangler
node_modules
*.md
!README.md
Expand Down Expand Up @@ -55,8 +55,14 @@ convex/tsconfig.tsbuildinfo
/apps/api/test-results
/apps/api/test-results
/apps/web/.tanstack
/Verify/Native/
/Verify/Native/*
!/Verify/Native/transfer-helper/
!/Verify/Native/transfer-helper/**
/.volumes
/.idea/
/.orchestration/
/TestResults/
/longtail.csv

# Licensed Streamline artwork is generated locally and must never be committed.
/apps/web/src/icons/generated.tsx*
Expand Down
3 changes: 3 additions & 0 deletions Verify/Native/transfer-helper/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/dist/
/yucp-transfer-helper
/yucp-transfer-helper.exe
81 changes: 81 additions & 0 deletions Verify/Native/transfer-helper/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# YUCP transfer helper

The helper downloads its own trusted releases through TUF.

The importer supplies the initial signed root metadata.

The helper rejects expired, rolled-back, oversized, or incorrectly signed metadata.

Verified targets use versioned paths.

The helper never overwrites an existing target.

The helper verifies signed `FileTableShardV2` recipes.

It reconstructs common files from the verified local chunk cache.

Protected source recipes fail before the helper creates a staging tree.

## Toolchain

Use Go 1.26.5.

The TUF client uses `go-tuf` v2.4.2.

API reference: [go-tuf updater](https://pkg.go.dev/github.com/theupdateframework/go-tuf/v2/metadata/updater)

## Build

1. Run `go test ./...`.
2. Run `go vet ./...`.
3. Run `go build ./cmd/yucp-transfer-helper`.

Use this production build command:

```text
go build -trimpath -ldflags="-s -w" -o dist/yucp-transfer-helper.exe ./cmd/yucp-transfer-helper
```

## Update command

Use the `update` command with a pinned root and direct repository URLs.

Remote URLs require HTTPS.

Loopback tests can use HTTP.

```text
yucp-transfer-helper update \
--root <root-path> \
--metadata-url <metadata-url> \
--targets-url <targets-url> \
--metadata-cache <cache-path> \
--target <target-name> \
--destination <versioned-path> \
--trace-id <trace-id>
```

The command writes one JSON result to standard output.

The result includes the trace identifier, target digest, byte length, cache state, and final path.

## Reconstruct command

Use `reconstruct` with a signed file-table shard and trusted signing key.

The current profile reads uncompressed desync chunks from the local cache.

The helper verifies encoded SHA-256 values and domain-separated logical digests.

It publishes the staging tree only after all files pass verification.

```text
yucp-transfer-helper reconstruct \
--signed-shard <cose-file-table-shard> \
--public-key <ed25519-public-key-hex> \
--key-id <trusted-key-id> \
--chunk-cache <cache-root> \
--destination <new-staging-tree> \
--encoding-profile desync-uncompressed-sha256-v1 \
--trace-id <trace-id>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package main

import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"time"

"github.com/yucp/transfer-helper/internal/localauthenticode"
)

type artifactPaths []string

func (paths *artifactPaths) String() string {
return strings.Join(*paths, ",")
}

func (paths *artifactPaths) Set(value string) error {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return fmt.Errorf("artifact path is required")
}
*paths = append(*paths, trimmed)
return nil
}

func main() {
flags := flag.NewFlagSet("yucp-local-authenticode-sign", flag.ContinueOnError)
flags.SetOutput(os.Stderr)
var paths artifactPaths
flags.Var(&paths, "artifact", "Windows PE artifact to sign")
expectedCertificateSHA256 := flags.String(
"certificate-sha256",
"",
"expected local publisher certificate SHA-256",
)
subject := flags.String("subject", "", "local development certificate subject")
verifyOnly := flags.Bool("verify", false, "verify signed artifacts without changing them")
if err := flags.Parse(os.Args[1:]); err != nil {
os.Exit(2)
}
if flags.NArg() != 0 ||
strings.TrimSpace(*subject) == "" ||
len(paths) == 0 ||
(*verifyOnly && strings.TrimSpace(*expectedCertificateSHA256) == "") ||
(!*verifyOnly && *expectedCertificateSHA256 != "") {
fmt.Fprintln(
os.Stderr,
"usage: yucp-local-authenticode-sign [--verify --certificate-sha256 <sha256>] --subject <CN> --artifact <path>...",
)
os.Exit(2)
}

if *verifyOnly {
for _, artifactPath := range paths {
if err := localauthenticode.VerifyFile(
artifactPath,
*subject,
*expectedCertificateSHA256,
time.Now(),
); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
return
}

_, certificate, err := localauthenticode.SignFiles(paths, *subject, time.Now())
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
certificateSHA256 := sha256.Sum256(certificate.Raw)
result, err := json.Marshal(struct {
CertificateSHA256 string `json:"certificateSha256"`
Subject string `json:"subject"`
}{
CertificateSHA256: hex.EncodeToString(certificateSHA256[:]),
Subject: certificate.Subject.String(),
})
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println(string(result))
}
Loading
Loading