Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions drizzle/0024_remove_reposted_work.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
SELECT pg_advisory_xact_lock(hashtext('github-work-unit-projection-v1'));
--> statement-breakpoint
-- Remove unchanged revisions reposted after daily-history publication began.
-- Compare only the immediately preceding day, preserving genuine A -> B -> A work.
WITH duplicates AS (
SELECT newer.day, newer.identity_key
FROM github_activity_snapshots newer
CROSS JOIN LATERAL (
SELECT * FROM github_activity_snapshots older
WHERE older.identity_key = newer.identity_key AND older.day < newer.day
ORDER BY older.day DESC LIMIT 1
) previous
WHERE newer.day >= DATE '2026-09-28'
AND newer.work_unit_id = previous.work_unit_id
AND newer.work_unit_revision = previous.work_unit_revision
AND newer.facts_digest = previous.facts_digest
AND newer.outcome_digest IS NOT DISTINCT FROM previous.outcome_digest
AND newer.attribution_mode = previous.attribution_mode
AND newer.payload->'facts' = previous.payload->'facts'
), removed AS (
DELETE FROM github_activity_snapshots s USING duplicates d
WHERE s.day = d.day AND s.identity_key = d.identity_key
RETURNING s.day
)
UPDATE github_public_feed_head SET
feed_revision = feed_revision + 1,
head_content_revision = head_content_revision + 1,
last_published_at = clock_timestamp()
WHERE id = true AND EXISTS (SELECT 1 FROM removed);
7 changes: 7 additions & 0 deletions drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,13 @@
"when": 1790534206181,
"tag": "0023_daily_history",
"breakpoints": true
},
{
"idx": 24,
"version": "7",
"when": 1790543384000,
"tag": "0024_remove_reposted_work",
"breakpoints": true
}
]
}
2 changes: 1 addition & 1 deletion src/app/(portfolio)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { buildPageMetadata } from "@/lib/page-metadata";

export const metadata = buildPageMetadata(pages.home);

export const dynamic = "force-dynamic";
export const revalidate = 60;

function OpenSource({ github }: Readonly<{ github: GitHubProfile }>) {
const projects = github.projects
Expand Down
2 changes: 1 addition & 1 deletion src/app/(portfolio)/tokens/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { buildPageMetadata } from "@/lib/page-metadata";

export const metadata = buildPageMetadata(pages.tokens);

export const dynamic = "force-dynamic";
export const revalidate = 60;

export default async function TokensPage() {
if (!tokenPreferences.enabled) {
Expand Down
2 changes: 1 addition & 1 deletion src/app/(portfolio)/work/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { buildPageMetadata } from "@/lib/page-metadata";

export const metadata = buildPageMetadata(pages.work);

export const dynamic = "force-dynamic";
export const revalidate = 60;

export default async function WorkLogPage() {
const initialPage = await getInitialGitHubActivity();
Expand Down
7 changes: 6 additions & 1 deletion src/app/api/cron/codex-stats/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { revalidateTag } from "next/cache";
import { after } from "next/server";

import { env } from "@/env";
import { getPublicCodexStats } from "@/lib/codex/public-stats";
import { syncCodexAccounts } from "@/lib/codex/sync";
import { reportOperationalError } from "@/lib/operational-error";
import { hasBearerSecret } from "@/lib/request-auth";
Expand All @@ -15,7 +17,10 @@ export async function POST(request: Request) {
try {
const result = await syncCodexAccounts();
if (result.updated > 0) {
revalidateTag("public-codex-stats", { expire: 0 });
revalidateTag("public-codex-stats", "max");
after(async () => {
await getPublicCodexStats();
});
}
return Response.json({ ok: true, result });
} catch (error) {
Expand Down
10 changes: 10 additions & 0 deletions src/app/api/cron/github-worker/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { revalidateTag } from "next/cache";
import { after } from "next/server";

import { env } from "@/env";
import { getInitialGitHubActivity } from "@/lib/github-activity-feed";
import { runGitHubActivityWorker } from "@/lib/github-activity-worker";
import { workerBatchSizeFrom } from "@/lib/github-activity-worker-core";
import { GITHUB_WORKER_EXECUTION_DURATION_MS } from "@/lib/github-cron-config";
Expand Down Expand Up @@ -38,6 +42,12 @@ export async function POST(request: Request) {
refLimit: 1,
}),
});
if (activity.projection?.feedRevisionChanged === true) {
revalidateTag("public-github-activity", "max");
after(async () => {
await getInitialGitHubActivity();
});
}
return Response.json({
activity,
ok: true,
Expand Down
5 changes: 1 addition & 4 deletions src/components/site-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ export function SiteHeader({
};
return (
<li key={item.href}>
<Link {...props} prefetch={false}>
{item.label}
</Link>
<Link {...props}>{item.label}</Link>
</li>
);
});
Expand All @@ -48,7 +46,6 @@ export function SiteHeader({
>
<Link
href="/"
prefetch={false}
aria-label={`${resumeData.person.name} home`}
aria-current={currentPath === "/" ? "page" : undefined}
className="group flex min-h-11 min-w-0 max-w-full items-center gap-3 rounded-sm focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring"
Expand Down
1 change: 0 additions & 1 deletion src/components/site-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ export function SiteSection({
<Link
className="inline-flex min-h-11 shrink-0 items-center gap-1.5 rounded-sm text-sm text-muted-foreground hover:text-foreground hover:underline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring"
href={href}
prefetch={false}
target={external ? "_blank" : undefined}
rel={external ? "noreferrer noopener" : undefined}
>
Expand Down
4 changes: 1 addition & 3 deletions src/components/token-history.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,7 @@ function HistoryPlot({
<TokenMonthAxis values={rows} />
<p className="sr-only">
{annotation}
{summary.partial
? ". Partial history; gaps indicate unavailable data."
: ""}
{summary.partial ? ". Missing days are shown as zero usage." : ""}
</p>
</>
);
Expand Down
11 changes: 4 additions & 7 deletions src/lib/codex/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,15 @@ export interface TokenHistory {
}

export function summarizeTokenHistory(history: TokenHistory, today: string) {
const rows = history.values.filter((row) => row.day <= today);
const reported = history.values.filter((row) => row.day <= today);
const rows = reported.map((row) => ({ ...row, tokens: row.tokens ?? 0 }));
let total = 0;
const cumulativeRows: TokenHistory["values"] = [];
let peak: (typeof rows)[number] | undefined;
for (const row of rows) {
if (row.tokens === null) {
cumulativeRows.push({ day: row.day, tokens: null });
continue;
}
total += row.tokens;
cumulativeRows.push({ day: row.day, tokens: total });
if (peak === undefined || row.tokens > (peak.tokens ?? 0)) {
if (row.tokens > 0 && (peak === undefined || row.tokens > peak.tokens)) {
peak = row;
}
}
Expand All @@ -24,6 +21,6 @@ export function summarizeTokenHistory(history: TokenHistory, today: string) {
cumulativeRows,
total,
peak,
partial: history.partial || rows.some((row) => row.tokens === null),
partial: history.partial || reported.some((row) => row.tokens === null),
};
}
2 changes: 1 addition & 1 deletion src/lib/github-activity-feed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const readCachedInitialGitHubActivity = unstable_cache(
PUBLIC_GITHUB_ACTIVITY_DAY_PAGE_SIZE
),
["public-github-activity-initial-v3"],
{ revalidate: 60 }
{ revalidate: 60, tags: ["public-github-activity"] }
);

export const getInitialGitHubActivity = async () => {
Expand Down
5 changes: 3 additions & 2 deletions src/lib/github-activity-snapshots.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isDeepStrictEqual } from "node:util";

import { and, desc, eq, inArray, sql } from "drizzle-orm";

import { getDatabase } from "@/db/client";
Expand Down Expand Up @@ -47,8 +49,7 @@ export const githubSnapshotChanged = (previous: Saved, next: Saved) => {
return (
"facts" in previous.payload &&
"facts" in next.payload &&
JSON.stringify(previous.payload.facts) !==
JSON.stringify(next.payload.facts)
!isDeepStrictEqual(previous.payload.facts, next.payload.facts)
);
};

Expand Down
11 changes: 11 additions & 0 deletions tests/daily-history.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ test("work snapshots compare the latest outcome; rebases and first digest acquis
expect(githubSnapshotChanged(saved(null), saved("A"))).toBe(false);
expect(githubSnapshotChanged(saved("A"), saved(null, 240))).toBe(false);
expect(githubSnapshotChanged(saved(null), saved(null, 240))).toBe(true);
const fromDatabase = saved(null);
if (!("facts" in fromDatabase.payload)) {
throw new Error("Expected a work card");
}
// jsonb reorders object keys when a saved card is read back from PostgreSQL.
fromDatabase.payload.facts = Object.fromEntries(
Object.entries(fromDatabase.payload.facts).toReversed()
) as typeof fromDatabase.payload.facts;
expect(githubSnapshotChanged(fromDatabase, saved(null))).toBe(false);
expect(githubSnapshotChanged(fromDatabase, saved("A"))).toBe(false);
expect(githubSnapshotChanged(fromDatabase, saved(null, 240))).toBe(true);
expect(
githubSnapshotChanged(saved("A"), {
...saved("A"),
Expand Down
48 changes: 43 additions & 5 deletions tests/hourly-publication-resilience.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,26 @@ const check = (source: string) => {
expect(result.exitCode).toBe(0);
};

test("only the hourly invocation publishes, without changing ingestion limits or auth", () => {
test("only the hourly invocation publishes and warms changed activity, without changing ingestion limits or auth", () => {
check(`
import assert from "node:assert/strict";
import { mock } from "bun:test";
const calls = [];
const events = [];
const callbacks = [];
let changed = false;
let fail = false;
mock.module("next/cache", () => ({ revalidateTag: (...args) => events.push(args) }));
mock.module("next/server", () => ({ after: callback => callbacks.push(callback) }));
mock.module("./src/lib/github-activity-feed.ts", () => ({ getInitialGitHubActivity: async () => { events.push("warmed"); } }));
mock.module("./src/lib/operational-error.ts", () => ({ reportOperationalError: () => "Error" }));
mock.module("./src/env.ts", () => ({ env: { CRON_SECRET: "hourly-publication-test-secret-32-characters" } }));
mock.module("./src/lib/github-activity-worker.ts", () => ({
runGitHubActivityWorker: async options => { calls.push(options); return {}; }
runGitHubActivityWorker: async options => {
if (fail) throw new Error("Publication failed");
calls.push(options);
return { projection: options.includeProjection ? { feedRevisionChanged: changed } : null };
}
}));
const { POST } = await import("./src/app/api/cron/github-worker/route.ts");
const request = (query, authorized = true) => POST(new Request(
Expand All @@ -37,22 +49,39 @@ test("only the hourly invocation publishes, without changing ingestion limits or
assert.equal(calls[2].includeProjection, true);
assert.equal(calls[2].commitLimit, 2);
assert.equal(calls[2].refLimit, 1);
assert.deepEqual(events, []);
assert.equal(callbacks.length, 0);
changed = true;
assert.equal((await request("?publish=1")).status, 200);
assert.deepEqual(events, [["public-github-activity", "max"]]);
assert.equal(callbacks.length, 1);
await callbacks.shift()();
assert.deepEqual(events, [["public-github-activity", "max"], "warmed"]);
events.length = 0;
fail = true;
assert.equal((await request("?publish=1")).status, 503);
assert.deepEqual(events, []);
assert.equal(callbacks.length, 0);
`);
});

test("Codex sync expires the public cache only after a successful save", () => {
test("Codex sync warms the public cache after a successful change, never after a failed or unchanged sync", () => {
check(`
import assert from "node:assert/strict";
import { mock } from "bun:test";
const events = [];
const callbacks = [];
let fail = false;
let updated = 2;
mock.module("next/cache", () => ({ revalidateTag: (...args) => events.push(args) }));
mock.module("next/server", () => ({ after: callback => callbacks.push(callback) }));
mock.module("./src/lib/codex/public-stats.ts", () => ({ getPublicCodexStats: async () => { events.push("warmed"); } }));
mock.module("./src/env.ts", () => ({ env: { CRON_SECRET: "codex-cache-test-secret-32-characters" } }));
mock.module("./src/lib/operational-error.ts", () => ({ reportOperationalError: () => "Error" }));
mock.module("./src/lib/codex/sync.ts", () => ({ syncCodexAccounts: async () => {
if (fail) throw new Error("Sync failed");
events.push("saved");
return { updated: 2 };
return { updated };
} }));
const { POST } = await import("./src/app/api/cron/codex-stats/route.ts");
const request = (authorized = true) => POST(new Request("https://example.com/api/cron/codex-stats", {
Expand All @@ -61,11 +90,20 @@ test("Codex sync expires the public cache only after a successful save", () => {
assert.equal((await request(false)).status, 401);
assert.deepEqual(events, []);
assert.equal((await request()).status, 200);
assert.deepEqual(events, ["saved", ["public-codex-stats", { expire: 0 }]]);
assert.deepEqual(events, ["saved", ["public-codex-stats", "max"]]);
assert.equal(callbacks.length, 1);
await callbacks.shift()();
assert.deepEqual(events, ["saved", ["public-codex-stats", "max"], "warmed"]);
events.length = 0;
updated = 0;
assert.equal((await request()).status, 200);
assert.deepEqual(events, ["saved"]);
assert.equal(callbacks.length, 0);
events.length = 0;
fail = true;
assert.equal((await request()).status, 503);
assert.deepEqual(events, []);
assert.equal(callbacks.length, 0);
`);
});

Expand Down
12 changes: 4 additions & 8 deletions tests/token-history.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ test("history distinguishes unreported dates from zero and excludes future dates
expect(make(10)?.history.values).toHaveLength(365);
});

test("daily history keeps gaps and finds the peak without counting future dates", () => {
test("missing days show zero daily usage and carry the cumulative total forward", () => {
const history: TokenHistory = {
partial: false,
values: [
Expand All @@ -51,15 +51,11 @@ test("daily history keeps gaps and finds the peak without counting future dates"
};
const result = summarizeTokenHistory(history, "2026-09-23");
expect(result.total).toBe(12);
expect(result.cumulativeRows.map((row) => row.tokens)).toEqual([
5,
null,
5,
12,
]);
expect(result.cumulativeRows.map((row) => row.tokens)).toEqual([5, 5, 5, 12]);
expect(result.peak).toEqual({ day: "2026-09-23", tokens: 7 });
expect(result.rows).toHaveLength(4);
expect(result.rows[1].tokens).toBeNull();
expect(result.rows[1].tokens).toBe(0);
expect(history.values[1].tokens).toBeNull();
expect(result.partial).toBe(true);
expect(
summarizeTokenHistory(
Expand Down
Loading