fix(console): 真實性稽核修正——時區統一台北、精耕層數字、今日完成接 sprint 任務板

稽核輪(fix/console-truth-audit,總管派工+leo 三裁決):

B1 時區:console 全站日期/時間顯示統一 Asia/Taipei(新 lib/taipei-time.ts,
   server 判定與頁面 client JS 同一套、等價性單測互鎖)。原本用瀏覽器本地
   getter——換裝置就換日期;憑證頁改顯「日期+時分(台北)」:raw 1783349834
   =台北 2026-07-06 22:57,7/6 顯示是真的,缺的是時分與固定時區。

B2 總庫數字(leo 裁:14-E 遺產 deprecated 不再顯示):總庫搜尋/設定頁頭部
   統計改讀新 GET /console/kb-scale-data(精耕層 wiki 卡/三元組/已嵌入+最近
   寫入,全 live、limit=1/stats 聚合不掃庫);搜尋仍搜全庫、結果 wiki_card
   加「精耕」綠標;dashboard-data 的 entries 計數補 owner_id(原混租戶:
   459,137 vs leo 458,732)。

B3 設定頁:vectorize 文案改具體事實(config kbdb_embed+acr update,#32
   教訓)+live 狀態;新增 MCP token TTL 誠實佔位(GET /console/settings-data,
   可調功能=Arcrun#19,不假裝能遠端改)。

C 今日完成/今日路線接 sprint 任務板(leo 拍板):比照 #36 等leo清單模式,
   同一輪 Gitea fetch(90s CF cache 共用)解析「## 任務板」checkbox;「今日
   完成」只認「完成(今天台北日)」標記,[x] 無日期不冒領;快取存原始
   completed_days,跨台北午夜不誤算;板檔今天沒 commit → 頁面誠實標「今日
   任務板未更新」;dash_task 降 fallback、燈號判定不變。parser 以 live
   07a/07b 檔實測(標題抽取棄「首個粗體」——實讀發現會抽到完成註記)。

驗證:tsc exit 0;vitest 82/83(1 失敗=pre-existing executor「不存在的
零件」,stash 於 main 複驗同樣紅);新增 taipei-time 10 案例(含跨日/跨年/
client-server 等價/leo 實測 epoch)+任務板 parser 7 案例;兩頁 inline JS
渲染後抽 <script> node --check 全過(mistakes #24 手法)。
This commit is contained in:
cloud-worker
2026-07-07 12:25:31 +00:00
parent ef779e3ef6
commit 5690d61438
9 changed files with 660 additions and 138 deletions
@@ -1,16 +1,17 @@
/**
* cachedGiteaWaiting 快取層測試(總管 #36 審查要求的證據)。
* cachedGiteaSprint 快取層測試(總管 #36 審查要求的證據fix/console-truth-audit 起
* 同一輪快取多帶任務板 board——「等你的事」與「今日完成」共用同一次 Gitea fetch)。
*
* 跑在 vitest-pool-workersworkerd)裡,caches.default 是真的 Cache API——
* 不是 mock cache 物件;fetcher 用注入的假函式計數,證明:
* 1. 第一次 miss(打 fetcher)→ 90s 內第二次 hitfetcher 不再被打)
* 1. 第一次 miss(打 fetcher)→ 90s 內第二次 hitfetcher 不再被打)board 一起回放
* 2. hit 回放時 age 隨牆鐘推進(不回放停走的時間)
* 3. fetcher 失敗(回 null)不快取——下次仍會重試,不把抖動放大成 90 秒盲區
* 4. 缺 GITEA_BASE_URL/GITEA_TOKEN 直接 null(快取層不啟動)
*/
import { describe, it, expect } from 'vitest';
import { cachedGiteaWaiting } from '../src/routes/console-dashboard';
import type { WaitingModel } from '../src/lib/console-dashboard-model';
import { cachedGiteaSprint } from '../src/routes/console-dashboard';
import type { SprintSnapshot } from '../src/lib/console-dashboard-model';
import type { Bindings } from '../src/types';
function fakeEnv(repo: string): Bindings {
@@ -22,13 +23,19 @@ function fakeEnv(repo: string): Bindings {
} as unknown as Bindings;
}
function model(): WaitingModel {
function snapshot(): SprintSnapshot {
return {
items: [{ title: 'mira .env 歷史憑證外洩', id: '13', urgency: '🔴', sprint: 'sprint-2026-07a.md' }],
source: 'gitea_sprint',
updated_ago_minutes: 10,
stale: false,
sprint_files: ['sprint-2026-07b.md', 'sprint-2026-07a.md'],
waiting: {
items: [{ title: 'mira .env 歷史憑證外洩', id: '13', urgency: '🔴', sprint: 'sprint-2026-07a.md' }],
source: 'gitea_sprint',
updated_ago_minutes: 10,
stale: false,
sprint_files: ['sprint-2026-07b.md', 'sprint-2026-07a.md'],
},
board: [
{ title: 'T5-graphGraph 查詢 workflow 化)', status: 'todo', completed_days: [], sprint: 'sprint-2026-07b.md' },
{ title: 'T-kb2 07-07', status: 'done', completed_days: ['2026-07-05'], sprint: 'sprint-2026-07a.md' },
],
};
}
@@ -38,37 +45,40 @@ function collector() {
return { waitUntil: (p: Promise<unknown>) => pending.push(p), flush: () => Promise.all(pending) };
}
describe('cachedGiteaWaiting — Gitea 讀取快取(TTL 90s', () => {
it('第一次 miss 打 fetcher90s 內第二次 hit、fetcher 不再被打', async () => {
describe('cachedGiteaSprint — Gitea 讀取快取(TTL 90swaiting + board 同輪', () => {
it('第一次 miss 打 fetcher90s 內第二次 hit、fetcher 不再被打board 一起回放', async () => {
const env = fakeEnv('Leo/cache-test-1');
let calls = 0;
const fetcher = async () => {
calls++;
return model();
return snapshot();
};
const c = collector();
const t0 = Date.now();
const r1 = await cachedGiteaWaiting(env, t0, c.waitUntil, fetcher);
const r1 = await cachedGiteaSprint(env, t0, c.waitUntil, fetcher);
expect(r1?.cache).toBe('miss');
expect(calls).toBe(1);
await c.flush(); // cache.put 落地
const r2 = await cachedGiteaWaiting(env, t0 + 60_000, c.waitUntil, fetcher);
const r2 = await cachedGiteaSprint(env, t0 + 60_000, c.waitUntil, fetcher);
expect(r2?.cache).toBe('hit'); // ← 快取生效的客觀證據
expect(calls).toBe(1); // fetcher 沒有被第二次呼叫=Gitea 沒被打
expect(r2?.items[0].id).toBe('13');
expect(r2?.waiting.items[0].id).toBe('13');
// 任務板同輪回放:存的是原始 completed_days(「今天完成幾件」由請求當下算,跨午夜不冒領)
expect(r2?.board?.length).toBe(2);
expect(r2?.board?.[1].completed_days).toEqual(['2026-07-05']);
});
it('hit 回放時 age 隨牆鐘推進(存 10 分鐘前、過 60s 讀 → 11 分鐘前),不停走', async () => {
const env = fakeEnv('Leo/cache-test-2');
const c = collector();
const t0 = Date.now();
await cachedGiteaWaiting(env, t0, c.waitUntil, async () => model());
await cachedGiteaSprint(env, t0, c.waitUntil, async () => snapshot());
await c.flush();
const r = await cachedGiteaWaiting(env, t0 + 60_000, c.waitUntil, async () => model());
const r = await cachedGiteaSprint(env, t0 + 60_000, c.waitUntil, async () => snapshot());
expect(r?.cache).toBe('hit');
expect(r?.updated_ago_minutes).toBe(11); // 10 + 1 分鐘 drift
expect(r?.waiting.updated_ago_minutes).toBe(11); // 10 + 1 分鐘 drift
});
it('fetcher 失敗(null)不快取:下一次仍重打 fetcher', async () => {
@@ -79,9 +89,9 @@ describe('cachedGiteaWaiting — Gitea 讀取快取(TTL 90s', () => {
calls++;
return null;
};
expect(await cachedGiteaWaiting(env, Date.now(), c.waitUntil, failing)).toBeNull();
expect(await cachedGiteaSprint(env, Date.now(), c.waitUntil, failing)).toBeNull();
await c.flush();
expect(await cachedGiteaWaiting(env, Date.now(), c.waitUntil, failing)).toBeNull();
expect(await cachedGiteaSprint(env, Date.now(), c.waitUntil, failing)).toBeNull();
expect(calls).toBe(2); // 失敗沒有被 90 秒快取住
});
@@ -90,10 +100,10 @@ describe('cachedGiteaWaiting — Gitea 讀取快取(TTL 90s', () => {
let calls = 0;
const fetcher = async () => {
calls++;
return model();
return snapshot();
};
const c = collector();
expect(await cachedGiteaWaiting(env, Date.now(), c.waitUntil, fetcher)).toBeNull();
expect(await cachedGiteaSprint(env, Date.now(), c.waitUntil, fetcher)).toBeNull();
expect(calls).toBe(0);
});
});