···6868- `dev.keytrace.key` - Daily signing key for attestations
6969- `dev.keytrace.signature` - Cryptographic attestation structure
70707171+## Deployment
7272+7373+### Publishing Packages
7474+7575+Use the deploy script to bump versions and publish all packages to npm:
7676+7777+```bash
7878+./scripts/deploy.sh patch # 0.0.1 → 0.0.2
7979+./scripts/deploy.sh minor # 0.0.2 → 0.1.0
8080+./scripts/deploy.sh major # 0.1.0 → 1.0.0
8181+```
8282+8383+This will:
8484+1. Bump versions in `@keytrace/runner`, `@keytrace/verify`, and `@keytrace/lexicon`
8585+2. Build all packages
8686+3. Publish to npm
8787+4. Create a git commit and tag
8888+8989+After running, push to remote:
9090+9191+```bash
9292+git push && git push --tags
9393+```
9494+9595+7196## License
72977398MIT
+6-1
apps/keytrace.dev/.env.example
···44# Session secret (generate a secure random string for production)
55NUXT_SESSION_SECRET=your-secret-key-here
6677-# Scaleway Object Storage (S3-compatible)
77+# S3 configuration for session storage
88NUXT_S3_BUCKET=keytrace-sessions
99NUXT_S3_REGION=fr-par
1010NUXT_S3_ACCESS_KEY_ID=your-access-key
1111NUXT_S3_SECRET_ACCESS_KEY=your-secret-key
1212+1213# Optional: custom endpoint (defaults to https://s3.{region}.scw.cloud)
1314# NUXT_S3_ENDPOINT=https://s3.fr-par.scw.cloud
1515+1616+# Keytrace service account credentials (for writing claims to user repos)
1717+NUXT_KEYTRACE_DID=did:plc:your-service-account-did
1818+NUXT_KEYTRACE_PASSWORD=your-app-password
+21-1
apps/keytrace.dev/server/utils/recent-claims.ts
···2121const FEED_KEY = "recent-claims.json";
2222const MAX_ITEMS = 50;
23232424+// Track last known feed size to detect unexpected empty reads
2525+let lastKnownFeedSize = 0;
2626+2427/**
2528 * Add a claim to the recent claims feed.
2629 * Prepends to the list, trims to 50 items, and saves.
3030+ * Includes safeguard against S3 read failures that would wipe the feed.
2731 */
2832export async function addRecentClaim(claim: RecentClaim): Promise<void> {
2933 const feed = await getRecentClaims();
3434+3535+ // Safeguard: if we previously had data but now read empty, S3 may have failed
3636+ // Don't overwrite - log warning and skip save to prevent data loss
3737+ if (feed.length === 0 && lastKnownFeedSize > 5) {
3838+ console.warn(
3939+ `[recent-claims] Read returned empty but last known size was ${lastKnownFeedSize}. ` +
4040+ `Skipping save to prevent data loss. New claim not added: ${claim.subject}`,
4141+ );
4242+ return;
4343+ }
4444+3045 feed.unshift(claim);
3146 if (feed.length > MAX_ITEMS) feed.length = MAX_ITEMS;
4747+ lastKnownFeedSize = feed.length;
3248 await saveJson(FEED_KEY, feed);
3349}
3450···3652 * Get the recent claims feed from storage.
3753 */
3854export async function getRecentClaims(): Promise<RecentClaim[]> {
3939- return (await loadJson<RecentClaim[]>(FEED_KEY)) ?? [];
5555+ const feed = (await loadJson<RecentClaim[]>(FEED_KEY)) ?? [];
5656+ if (feed.length > 0) {
5757+ lastKnownFeedSize = feed.length;
5858+ }
5959+ return feed;
4060}