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-cache → glorious-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:
git log --oneline origin/main..HEAD— all commits have conventional formatgit diff origin/main...HEAD -- '*.ts'— no debug code, no console.log (except the intentional SESSION_SECRET warning)grep -r "fuzzy-dev" . --include="*.nix" --include="*.tf"— no stale endpoints remaingrep -r "attic-iac" MODULE.bazel .bazelrc WORKSPACE.bazel— all renamedgrep -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 |