2026 04 15 M7 M8 Merge Ready

M7/M8 Merge-Ready: Rebase, Quick Wins, Remaining Auth

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Get PR #192 merge-ready by rebasing onto latest main, fixing quick-win issues, and completing remaining M8 Tailnet-First Auth tasks that don’t require Docker/Tailscale.

Architecture: PR #192 has 6 commits across 35 files (auth hardening, Skeleton UI upgrade, endpoint cleanup, identity rename, caddy-tailscale upgrade). Main has advanced 11 commits since divergence, with 11 overlapping files requiring conflict resolution. After rebase we fix tooling issues (.npmrc, WORKSPACE.bazel, stale local branches), add session.ts tests, and implement the remaining M8 auth work that’s feasible offline.

Tech Stack: SvelteKit 5, TypeScript, Vitest, Nix (flake.nix), Bazel (MODULE.bazel), OpenTofu, pnpm, Caddy + caddy-tailscale


File Map

File Action Responsibility
.npmrc Modify Fix lockfile-version=9 that breaks npx/npm
WORKSPACE.bazel Modify Rename attic-cacheglorious-flywheel
app/src/lib/server/auth/session.ts Existing HMAC signing (already done, needs tests)
app/src/lib/server/auth/session.test.ts Create Unit tests for sign/verify/getSession/setSession
app/src/hooks.server.ts Existing Proxy header auth (already updated, needs tests)
app/src/hooks.server.test.ts Create Unit tests for auth middleware header parsing
tofu/modules/runner-dashboard/variables.tf Modify Add session_secret validation
tofu/modules/runner-dashboard/main.tf Verify Confirm SESSION_SECRET wiring

Task 1: Rebase PR #192 onto latest main

Files:

  • All 35 files on branch; 11 overlap with main’s changes

  • Step 1: Fetch latest remote state

git fetch origin
  • Step 2: Start the rebase
git rebase origin/main

Expected: Conflicts in up to 11 files. The most likely conflicts are in files both sides renamed attic-iac references:

  • .lychee.toml — both sides updated exclusion patterns

  • CONTRIBUTING.md — both sides updated repo URLs

  • README.md — both sides updated repo name

  • app/BUILD.bazel — both sides updated OCI label

  • docs-site/scripts/generate-llms-txt.js — both sides updated URLs

  • docs/architecture/multi-repo-layout.md — both sides updated diagram

  • docs/ci-cd/overlay-pipelines.md — both sides updated URLs

  • docs/getting-started-guide.md — both sides updated clone URLs

  • docs/infrastructure/quick-start.md — both sides updated paths

  • flake.nix — both sides updated description/endpoints

  • .github/workflows/release.yml — both sides updated URLs

  • Step 3: Resolve conflicts

For each conflicting file, the resolution strategy is:

  • Same change on both sides (both renamed attic-iac → GloriousFlywheel): take either version (they’re identical in intent). Use git checkout --theirs <file> then re-apply our unique changes if any.
  • Different changes: main did repo-health cleanup + RustFS migration; our branch did auth + Skeleton + caddy. Accept both sets of changes.

For each conflict:

# Open conflicting file, resolve markers
# Then:
git add <resolved-file>

After all resolved:

git rebase --continue

Repeat for each commit that conflicts.

  • Step 4: Verify rebase succeeded
git log --oneline -8
# Should show our 6 commits cleanly on top of main's HEAD
git diff --stat origin/main...HEAD
# Should show only our changes, no main-side changes mixed in
  • Step 5: Force-push the rebased branch
git push --force-with-lease

Expected: PR #192 updates to show 0 behind, N ahead.

  • Step 6: Commit checkpoint

No commit needed — rebase preserves existing commits.


Task 2: Fix .npmrc breaking npx (Context7 MCP fix)

Files:

  • Modify: .npmrc

  • Step 1: Read current .npmrc

cat .npmrc

Expected content:

lockfile-version=9
auto-install-peers=true
strict-peer-dependencies=false

The lockfile-version=9 is a pnpm-specific setting. npm/npx doesn’t recognize it and fails with Invalid lockfileVersion config: 9. Since this project uses pnpm exclusively, we need to keep it but prefix it so npm ignores it.

  • Step 2: Fix .npmrc

Replace the file content with:

# pnpm-specific settings (npm/npx ignore unknown keys gracefully
# when prefixed; lockfile-version=9 breaks npx without prefix)
auto-install-peers=true
strict-peer-dependencies=false

Remove lockfile-version=9 entirely — pnpm 9+ defaults to lockfile v9 without explicit config. The line is unnecessary and breaks npm/npx.

  • Step 3: Verify npx works
npx --version

Expected: prints npx version without errors.

  • Step 4: Commit
git add .npmrc
git commit -m "fix(tooling): remove lockfile-version=9 from .npmrc

pnpm-specific lockfile-version=9 breaks npm/npx with 'Invalid
lockfileVersion config' error. pnpm 9+ defaults to v9 format
without explicit config, so the line is unnecessary.

Fixes Context7 MCP server and any npx-based tooling."

Task 3: Fix WORKSPACE.bazel legacy name

Files:

  • Modify: WORKSPACE.bazel

  • Step 1: Update WORKSPACE.bazel

Replace entire file content:

# GloriousFlywheel - Bazel Workspace
#
# This file is intentionally minimal as we use Bzlmod (MODULE.bazel)
# for dependency management. It exists for compatibility with tools
# that expect a WORKSPACE file.

workspace(name = "glorious-flywheel")
  • Step 2: Commit
git add WORKSPACE.bazel
git commit -m "fix(bazel): rename WORKSPACE.bazel from attic-cache to glorious-flywheel

Aligns legacy WORKSPACE name with MODULE.bazel module name.
Bzlmod is primary; this file exists only for compat tooling.

Refs: TIN-124, #168"

Task 4: Clean stale local branches

Files: None (git-only operation)

  • Step 1: Delete stale local branches tracking deleted remotes
git branch -d feat/honey-arc-runner feat/rustfs-state-backend fix/runner-docs-cleanup

Expected: 3 branches deleted. These all track gone remotes.

  • Step 2: Update local main
git fetch origin main:main

Expected: Fast-forward local main to match origin/main.

  • Step 3: Verify clean branch state
git branch -vv

Expected: Only feat/m7-foundation-skeleton-auth (current) and main (up to date) and worktree-glywheel-ui-and-integrations.


Task 5: Write session.ts unit tests

Files:

  • Create: app/src/lib/server/auth/session.test.ts

  • Read: app/src/lib/server/auth/session.ts

  • Step 1: Write the test file

import { describe, it, expect, vi, beforeEach } from "vitest";
import { sign, verify } from "./session";

// Test the HMAC sign/verify functions directly.
// getSession/setSession depend on SvelteKit Cookies which are hard
// to mock cleanly — we test the crypto core here and rely on
// integration tests for the cookie layer.

const TEST_SECRET = "test-secret-key-for-unit-tests-only";

describe("sign", () => {
  it("returns payload.signature format", () => {
    const result = sign("hello", TEST_SECRET);
    expect(result).toContain(".");
    const parts = result.split(".");
    expect(parts).toHaveLength(2);
    expect(parts[0]).toBe("hello");
  });

  it("produces consistent signatures for same input", () => {
    const a = sign("payload", TEST_SECRET);
    const b = sign("payload", TEST_SECRET);
    expect(a).toBe(b);
  });

  it("produces different signatures for different secrets", () => {
    const a = sign("payload", "secret-a");
    const b = sign("payload", "secret-b");
    expect(a).not.toBe(b);
  });

  it("produces different signatures for different payloads", () => {
    const a = sign("payload-a", TEST_SECRET);
    const b = sign("payload-b", TEST_SECRET);
    expect(a).not.toBe(b);
  });

  it("handles empty payload", () => {
    const result = sign("", TEST_SECRET);
    expect(result).toMatch(/^\..+$/); // .signature
  });

  it("handles base64 payloads with special chars", () => {
    const payload = Buffer.from('{"token":"abc+/="}').toString("base64");
    const result = sign(payload, TEST_SECRET);
    expect(result).toContain(".");
    expect(verify(result, TEST_SECRET)).toBe(payload);
  });
});

describe("verify", () => {
  it("returns payload for valid signature", () => {
    const signed = sign("test-payload", TEST_SECRET);
    const result = verify(signed, TEST_SECRET);
    expect(result).toBe("test-payload");
  });

  it("returns null for wrong secret", () => {
    const signed = sign("test-payload", TEST_SECRET);
    const result = verify(signed, "wrong-secret");
    expect(result).toBeNull();
  });

  it("returns null for tampered payload", () => {
    const signed = sign("original", TEST_SECRET);
    const tampered = "tampered" + signed.slice(signed.indexOf("."));
    const result = verify(tampered, TEST_SECRET);
    expect(result).toBeNull();
  });

  it("returns null for tampered signature", () => {
    const signed = sign("payload", TEST_SECRET);
    const tampered = signed.slice(0, -1) + "X";
    const result = verify(tampered, TEST_SECRET);
    expect(result).toBeNull();
  });

  it("returns null for string with no dot", () => {
    const result = verify("noseparator", TEST_SECRET);
    expect(result).toBeNull();
  });

  it("returns null for empty string", () => {
    const result = verify("", TEST_SECRET);
    expect(result).toBeNull();
  });

  it("handles payload containing dots", () => {
    // sign("a.b.c") should use lastIndexOf to find the signature separator
    const signed = sign("a.b.c", TEST_SECRET);
    const result = verify(signed, TEST_SECRET);
    expect(result).toBe("a.b.c");
  });
});
  • Step 2: Run the tests to verify they pass
cd app && npx vitest run src/lib/server/auth/session.test.ts --reporter=verbose

Expected: All 13 tests pass. The sign and verify functions are already implemented and exported from session.ts.

If vitest can’t resolve $env/dynamic/private (SvelteKit alias), the test file only imports sign and verify which don’t use that import, so it should work. If it fails due to the module import, add to the top of the test:

vi.mock("$env/dynamic/private", () => ({ env: {} }));
  • Step 3: Commit
git add app/src/lib/server/auth/session.test.ts
git commit -m "test(auth): add unit tests for HMAC sign/verify

13 test cases covering: consistent signing, different secrets,
tampered payloads, tampered signatures, edge cases (empty string,
dots in payload, base64 special chars).

Refs: TIN-142, #180"

Task 6: Add SESSION_SECRET validation to OpenTofu

Files:

  • Modify: tofu/modules/runner-dashboard/variables.tf

  • Step 1: Read the current session_secret variable definition

grep -A 10 'variable "session_secret"' tofu/modules/runner-dashboard/variables.tf
  • Step 2: Add validation block

Find the session_secret variable and add a validation block ensuring minimum length when provided:

variable "session_secret" {
  description = "HMAC-SHA256 secret for signing session cookies. Auto-generated if empty."
  type        = string
  default     = ""
  sensitive   = true

  validation {
    condition     = var.session_secret == "" || length(var.session_secret) >= 16
    error_message = "session_secret must be at least 16 characters when provided."
  }
}
  • Step 3: Run OpenTofu validation
cd tofu/stacks/runner-dashboard && tofu validate 2>&1 || echo "validate requires init — check syntax only"

If tofu validate requires init (no .terraform/), just verify the HCL syntax:

tofu fmt -check tofu/modules/runner-dashboard/variables.tf && echo "HCL syntax OK"
  • Step 4: Commit
git add tofu/modules/runner-dashboard/variables.tf
git commit -m "fix(tofu): add session_secret length validation

Require minimum 16 chars when explicitly provided. Empty string
still triggers auto-generation via random_password resource.

Refs: TIN-142, #180"

Task 7: Verify SESSION_SECRET flows from tofu to K8s

Files:

  • Read: tofu/modules/runner-dashboard/main.tf (verify only, no edit expected)

  • Step 1: Verify the secret wiring

grep -n "SESSION_SECRET" tofu/modules/runner-dashboard/main.tf

Expected output should show SESSION_SECRET = local.effective_session_secret inside the kubernetes_secret.dashboard resource. This confirms the HMAC secret reaches the pod environment.

  • Step 2: Verify the auto-generation logic
grep -A 3 "effective_session_secret" tofu/modules/runner-dashboard/main.tf

Expected: local.effective_session_secret is set to var.session_secret if non-empty, else random_password.session_secret[0].result.

  • Step 3: No commit needed — verification only

If the wiring is correct, no changes. If something is missing, fix it and commit with:

git commit -m "fix(tofu): wire SESSION_SECRET to dashboard pod environment"

Task 8: Push final state and verify CI

Files: None (git-only)

  • Step 1: Push all new commits
git push
  • Step 2: Check CI status
gh pr checks 192

Expected: Validate and Secret Detection workflows trigger on the push. Wait for green.

  • Step 3: Verify PR diff is clean
gh pr diff 192 --stat

Review the diff summary to confirm it only contains intended changes.

  • Step 4: Update PR description if needed
gh api repos/tinyland-inc/GloriousFlywheel/pulls/192 --method PATCH \
  -f title="feat(m7+m8): foundation, auth hardening, identity reset, caddy-tailscale"

Task 9: Final review checklist

  • Step 1: Run self-review

Verify:

  1. git log --oneline origin/main..HEAD — all commits have conventional format
  2. git diff origin/main...HEAD -- '*.ts' — no debug code, no console.log (except the intentional SESSION_SECRET warning)
  3. grep -r "fuzzy-dev" . --include="*.nix" --include="*.tf" — no stale endpoints remain
  4. grep -r "attic-iac" MODULE.bazel .bazelrc WORKSPACE.bazel — all renamed
  5. grep -r "Jesssullivan/attic-iac" . — no stale GitHub URLs
  • Step 2: Confirm merge readiness

If all checks pass:

  • PR #192 is rebased, CI green, no conflicts
  • Ready for self-review merge to main

Out of Scope (tracked for follow-up)

These items were identified but deliberately excluded from this plan:

Item Why excluded Tracker
pnpm install for Skeleton 4.15.2 lockfile Needs node_modules, generates large lockfile diff — separate PR TIN-136
Tailscale K8s Operator install Requires Tailscale online + kubectl cluster access TIN-140
tsidp OIDC deployment Depends on TIN-140 (Tailscale Operator) TIN-143
App Capabilities RBAC Depends on TIN-143 (tsidp) TIN-144
attic-cache namespace rename (54 files) Infrastructure migration, needs careful planning + cluster access TIN-125
ARC 0.14.0 upgrade (M9) Next milestone, depends on M8 completion TIN-145

GloriousFlywheel