From 3c3f14b5f7c5aa9fb0db19449a89310a67b230f2 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Sun, 20 Sep 2026 15:20:31 +0900 Subject: [PATCH 1/2] fix(cli): mark access-key usage unavailable when attribution is absent Without attributionSince the server is reporting an empty or unreadable usage log, but the table still rendered 0 totals and never-used markers that are indistinguishable from real data. Show an unavailable marker spanning the usage columns instead, matching the ambiguous-union precedent. --- src/cli/access.ts | 7 ++++--- tests/cli/cli-dto-fidelity.test.ts | 12 ++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/cli/access.ts b/src/cli/access.ts index 9cc0ec091a2..d8663aa6251 100644 --- a/src/cli/access.ts +++ b/src/cli/access.ts @@ -38,6 +38,7 @@ const USAGE = `Usage: */ function formatKeyRows(payload: Record, keys: Array>): string[] { const cells: string[][] = [["ID", "NAME", "PREFIX", "REQ 7D", "TOTAL", "LAST USED"]]; + const usageAvailable = typeof payload.attributionSince === "string"; for (const entry of keys) { const usage = (entry.usage ?? {}) as Record; const ambiguous = usage.ambiguous === true; @@ -47,9 +48,9 @@ function formatKeyRows(payload: Record, keys: Array Math.max(...cells.map(row => (row[column] ?? "").length))); diff --git a/tests/cli/cli-dto-fidelity.test.ts b/tests/cli/cli-dto-fidelity.test.ts index 67cc148795a..4a45a8127a6 100644 --- a/tests/cli/cli-dto-fidelity.test.ts +++ b/tests/cli/cli-dto-fidelity.test.ts @@ -216,6 +216,7 @@ describe("#2705 access key usage columns", () => { id: "k_9f2a", name: "ci-runner", prefix: "ocx_data_abc...", usage: { requests7d: 1204, totalRequests: 18330, lastUsedAt: "2026-08-27T04:11:00Z" }, }], + attributionSince: "2026-07-29T00:00:00Z", }); expect(out).toContain("REQ 7D"); expect(out).toContain("1,204"); @@ -229,6 +230,7 @@ describe("#2705 access key usage columns", () => { // use is the dangerous answer for someone deciding what to delete. const out = await listOutput({ keys: [{ id: "k_11bd", name: "laptop", prefix: "ocx_data_def...", usage: { ambiguous: true } }], + attributionSince: "2026-07-29T00:00:00Z", }); expect(out).toContain("ambiguous"); expect(out).not.toMatch(/\b0\b/); @@ -237,10 +239,20 @@ describe("#2705 access key usage columns", () => { test("a never-used key says never rather than showing an empty cell", async () => { const out = await listOutput({ keys: [{ id: "k_new", name: "fresh", prefix: "ocx_data_ghi...", usage: { requests7d: 0, totalRequests: 0 } }], + attributionSince: "2026-08-29T00:00:00Z", }); expect(out).toContain("never"); }); + test("unavailable attribution does not report zero usage or never used", async () => { + const out = await listOutput({ + keys: [{ id: "k_unknown", name: "unknown", prefix: "ocx_data_jkl...", usage: { requests7d: 0, totalRequests: 0 } }], + }); + expect(out).toContain("unavailable"); + expect(out).not.toMatch(/\b0\b/); + expect(out).not.toContain("never"); + }); + test("dataset-level attribution and truncation print ONCE as a footer", async () => { // They describe the usage log, not a key. Without attributionSince an absent lastUsedAt is // unreadable: "never used" and "nothing attributable yet" look identical. From e7584c39a769f76e332c0e96a18a8166166bb246 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Tue, 22 Sep 2026 08:47:40 +0900 Subject: [PATCH 2/2] fix(cli): treat a malformed attributionSince as unavailable typeof === 'string' accepted any value, so a malformed attributionSince made usageAvailable true and printed usage cells plus an 'attribution since' footer. Parse once, derive availability from the validated result, and reuse it for the footer. Covers the malformed-string regression. --- src/cli/access.ts | 12 +++++++++--- tests/cli/cli-dto-fidelity.test.ts | 10 ++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/cli/access.ts b/src/cli/access.ts index d8663aa6251..dc541db5ce2 100644 --- a/src/cli/access.ts +++ b/src/cli/access.ts @@ -38,7 +38,13 @@ const USAGE = `Usage: */ function formatKeyRows(payload: Record, keys: Array>): string[] { const cells: string[][] = [["ID", "NAME", "PREFIX", "REQ 7D", "TOTAL", "LAST USED"]]; - const usageAvailable = typeof payload.attributionSince === "string"; + // A string that does not parse is not attribution data: treat it like an absent + // field so malformed payloads still render "unavailable" instead of usage values. + const attributionSince = typeof payload.attributionSince === "string" + && !Number.isNaN(Date.parse(payload.attributionSince)) + ? payload.attributionSince + : undefined; + const usageAvailable = attributionSince !== undefined; for (const entry of keys) { const usage = (entry.usage ?? {}) as Record; const ambiguous = usage.ambiguous === true; @@ -56,8 +62,8 @@ function formatKeyRows(payload: Record, keys: Array Math.max(...cells.map(row => (row[column] ?? "").length))); const lines = cells.map(row => row.map((cell, i) => (cell ?? "").padEnd(widths[i]!)).join(" ").trimEnd()); const footer: string[] = []; - if (typeof payload.attributionSince === "string") { - footer.push(`attribution since ${payload.attributionSince}`); + if (attributionSince !== undefined) { + footer.push(`attribution since ${attributionSince}`); } if (payload.historyTruncated === true) { footer.push("older history truncated"); diff --git a/tests/cli/cli-dto-fidelity.test.ts b/tests/cli/cli-dto-fidelity.test.ts index 4a45a8127a6..9cfca772c69 100644 --- a/tests/cli/cli-dto-fidelity.test.ts +++ b/tests/cli/cli-dto-fidelity.test.ts @@ -253,6 +253,16 @@ describe("#2705 access key usage columns", () => { expect(out).not.toContain("never"); }); + test("a malformed attributionSince string stays unavailable", async () => { + const out = await listOutput({ + keys: [{ id: "k_bad", name: "bad", prefix: "ocx_data_mno...", usage: { requests7d: 0, totalRequests: 0 } }], + attributionSince: "not-a-timestamp", + }); + expect(out).toContain("unavailable"); + expect(out).not.toContain("attribution since"); + expect(out).not.toMatch(/\b0\b/); + }); + test("dataset-level attribution and truncation print ONCE as a footer", async () => { // They describe the usage log, not a key. Without attributionSince an absent lastUsedAt is // unreadable: "never used" and "nothing attributable yet" look identical.