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 10 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
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>
```
189 changes: 189 additions & 0 deletions Verify/Native/transfer-helper/cmd/yucp-local-tuf-repository/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package main

import (
"crypto"
"crypto/ed25519"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"os"
"path/filepath"
"time"

"github.com/sigstore/sigstore/pkg/signature"
"github.com/theupdateframework/go-tuf/v2/metadata"
"github.com/yucp/transfer-helper/internal/tufrepository"
)

const (
helperTargetName = "helper/windows-amd64/yucp-transfer-helper.exe"
trustTargetName = "package-install-trust.json"
)

type authority struct {
KeyID string `json:"keyId"`
PublicKey string `json:"publicKey"`
}

type trustTarget struct {
MaterializationReceipt authority `json:"materializationReceipt"`
PackageInstall authority `json:"packageInstall"`
SchemaVersion int `json:"schemaVersion"`
}

func main() {
output := flag.String("output", "", "local TUF repository output directory")
rootPath := flag.String("root", "", "pinned local TUF root path")
helperPath := flag.String("helper", "", "source-built Windows helper executable")
metadataVersion := flag.Int64(
"metadata-version",
0,
"monotonic local TUF metadata version",
)
installKeyID := flag.String("install-key-id", "", "package install signing key identifier")
installPublicKey := flag.String("install-public-key", "", "package install Ed25519 public key")
receiptKeyID := flag.String("receipt-key-id", "", "receipt signing key identifier")
receiptPublicKey := flag.String("receipt-public-key", "", "receipt Ed25519 public key")
flag.Parse()
if flag.NArg() != 0 {
fatal(fmt.Errorf("unexpected positional arguments"))
}
if err := generate(
*output,
*rootPath,
*helperPath,
*metadataVersion,
*installKeyID,
*installPublicKey,
*receiptKeyID,
*receiptPublicKey,
); err != nil {
fatal(err)
}
}

func generate(
output string,
rootPath string,
helperPath string,
metadataVersion int64,
installKeyID string,
installPublicKey string,
receiptKeyID string,
receiptPublicKey string,
) error {
if !filepath.IsAbs(output) || !filepath.IsAbs(rootPath) || !filepath.IsAbs(helperPath) {
return fmt.Errorf("local TUF output, root, and helper paths must be absolute")
}
if metadataVersion < 1 {
return fmt.Errorf("local TUF metadata version must be positive")
}
requiredValues := []struct {
name string
value string
}{
{"install key identifier", installKeyID},
{"install public key", installPublicKey},
{"receipt key identifier", receiptKeyID},
{"receipt public key", receiptPublicKey},
}
for _, required := range requiredValues {
if required.value == "" {
return fmt.Errorf("%s is required", required.name)
}
}
publicKeys := []struct {
name string
encoded string
}{
{"install public key", installPublicKey},
{"receipt public key", receiptPublicKey},
}
for _, publicKey := range publicKeys {
decoded, err := base64.RawURLEncoding.DecodeString(publicKey.encoded)
if err != nil || len(decoded) != ed25519.PublicKeySize {
return fmt.Errorf("%s is invalid", publicKey.name)
}
}
rootBytes, err := os.ReadFile(rootPath)
if err != nil {
return fmt.Errorf("read pinned local TUF root: %w", err)
}
targetBytes, err := json.Marshal(trustTarget{
MaterializationReceipt: authority{
KeyID: receiptKeyID,
PublicKey: receiptPublicKey,
},
PackageInstall: authority{
KeyID: installKeyID,
PublicKey: installPublicKey,
},
SchemaVersion: 1,
})
if err != nil {
return fmt.Errorf("encode local package trust target: %w", err)
}
helperBytes, err := os.ReadFile(helperPath)
if err != nil {
return fmt.Errorf("read source-built helper: %w", err)
}
if len(helperBytes) < 1 || len(helperBytes) > 256*1024*1024 {
return fmt.Errorf("source-built helper length is invalid")
}
snapshotSigner, err := deterministicLocalSigner(metadata.SNAPSHOT)
if err != nil {
return err
}
targetsSigner, err := deterministicLocalSigner(metadata.TARGETS)
if err != nil {
return err
}
timestampSigner, err := deterministicLocalSigner(metadata.TIMESTAMP)
if err != nil {
return err
}
now := time.Now().UTC()
bundle, err := tufrepository.Build(tufrepository.Input{
MetadataVersion: metadataVersion,
Now: now,
Root: rootBytes,
Signers: tufrepository.OnlineSigners{
Snapshot: snapshotSigner,
Targets: targetsSigner,
Timestamp: timestampSigner,
},
SnapshotExpires: now.Add(7 * 24 * time.Hour),
Targets: []tufrepository.Target{
{Bytes: helperBytes, Name: helperTargetName},
{Bytes: targetBytes, Name: trustTargetName},
},
TargetsExpires: now.Add(30 * 24 * time.Hour),
TimestampExpires: now.Add(24 * time.Hour),
})
if err != nil {
return fmt.Errorf("build local TUF repository: %w", err)
}
if bundle.RootVersion != 1 {
return fmt.Errorf("pinned local TUF root is not the expected test root")
}
return bundle.Write(output)
}

func deterministicLocalSigner(role string) (signature.Signer, error) {
seed := sha256.Sum256([]byte("YUCP transfer-helper TUF test key: " + role))
signer, err := signature.LoadSigner(
ed25519.NewKeyFromSeed(seed[:]),
crypto.Hash(0),
)
if err != nil {
return nil, fmt.Errorf("load local TUF %s signer: %w", role, err)
}
return signer, nil
}

func fatal(err error) {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
Loading
Loading