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
133 changes: 68 additions & 65 deletions data/outstanding-issues-snapshot.json

Large diffs are not rendered by default.

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
{
"version": 2,
"id": "7dc2d09a-b05c-4d10-b198-263af3c0f45e",
"createdOn": "2026-08-22",
"action": "update",
"payload": {
"id": "#RSD9EJ",
"source": "PR #2298 push attempts 2026-08-22/23 (squash-merged as f3d1a3cce2c943ad3083425ed9c7c46dbef23087; the feature branch and its commits 90b5de61f/7703188ef/55f3a47fa are NOT reachable from any ref -- view them on the PR page, not via git); scripts/guard-push.mjs; scripts/check-ledger-write-discipline.mjs; commit 9a382a050 (#2294) is the reconcile whose applied/ records triggered the false positive and IS on main. REPRODUCTION without those commits: branch from main, let a reconcile PR land on main, merge origin/main into the branch, then push -- guard-push refuses with 'applied/<uuid>.json was introduced without moving the identical pending request from the base' while 'node scripts/check-ledger-write-discipline.mjs --base origin/main --head HEAD' passes on the same tree. The divergence between those two results is the bug.",
Comment thread
cursor[bot] marked this conversation as resolved.
"baseRowFingerprint": "b687cb212853e6b4c4df866ea79d625556cad416a0c3e406a2044f82691a4d4b"
}
}
14 changes: 11 additions & 3 deletions docs/outstanding-issues.md

Large diffs are not rendered by default.

139 changes: 84 additions & 55 deletions scripts/check-docs-links.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,11 +20,17 @@
* Blocking for maintained docs: runs in verify:cheap and CI. Historical
* directories and dated point-in-time records stay excluded unless --all is
* requested, so preserved history cannot block unrelated PRs.
*
* Outstanding-issues inbox citations are special: an immutable request is
* queued at `docs/outstanding-issues-inbox/<uuid>.json` and, after reconcile,
* lives at `docs/outstanding-issues-inbox/applied/<uuid>.json`. Ledger rows
* (and the request's own source/detail) keep citing the pending path because
* the JSON is immutable. Treat the applied sibling as the same file.
*/

import { existsSync, readFileSync, readdirSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { fileURLToPath, pathToFileURL } from "node:url";

import { applyRequestBatch, validateRequest } from "./ledger-inbox.mjs";

Expand DownExpand Up@@ -66,10 +72,28 @@ const VERBATIM_DIRS = new Set(["codex-cloud-review"]);
const APP_ROUTE_GROUPS = ["(search-app)"];
const OUTSTANDING_ISSUES = "docs/outstanding-issues.md";
const OUTSTANDING_ISSUES_INBOX = "docs/outstanding-issues-inbox";
const INBOX_REQUEST_NAME = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.json$/i;

/**
* Pending inbox UUID paths keep being cited after reconcile moves the file
* into `applied/`. Return that applied sibling, or null when the path is not
* a pending inbox request citation.
*/
export function appliedInboxFallbackPath(repoRelative) {
const cleaned = repoRelative.replace(/\/$/, "");
const prefix = `${OUTSTANDING_ISSUES_INBOX}/`;
const appliedPrefix = `${OUTSTANDING_ISSUES_INBOX}/applied/`;
if (!cleaned.startsWith(prefix) || cleaned.startsWith(appliedPrefix)) return null;
const name = cleaned.slice(prefix.length);
if (name.includes("/") || !INBOX_REQUEST_NAME.test(name)) return null;
return `${appliedPrefix}${name}`;
}

function repoPathExists(repoRelative) {
const cleaned = repoRelative.replace(/\/$/, "");
if (existsSync(path.join(repoRoot, cleaned))) return true;
const applied = appliedInboxFallbackPath(cleaned);
if (applied && existsSync(path.join(repoRoot, applied))) return true;

if (!cleaned.startsWith("src/app/") || cleaned.includes("src/app/(")) return false;
const appRelative = cleaned.slice("src/app/".length);
Expand DownExpand Up@@ -176,68 +200,73 @@ function isExternalLink(value) {
return /^([a-z][a-z0-9+.-]*:|\/\/)/i.test(value) || value.startsWith("#");
}

let missing = 0;
let checked = 0;

for (const target of defaultTargets()) {
const absoluteTarget = path.join(repoRoot, target);
if (!existsSync(absoluteTarget)) continue;
const markdown = markdownForTarget(target, absoluteTarget);
const targetDir = path.posix.dirname(target);
const failures = [];

const check = (repoRelative, label) => {
if (ALLOWLIST.has(repoRelative)) return;
checked += 1;
if (!repoPathExists(repoRelative)) failures.push(label);
};

// Inline code spans: repo-root-relative repo paths.
for (const rawCandidate of codeSpanCandidates(markdown)) {
const value = stripSuffixes(rawCandidate);
const base = ROOT_PREFIXES.some((prefix) => value.startsWith(prefix)) ? globBaseDir(value) : null;
if (base !== null) {
if (ALLOWLIST.has(value)) continue;
function main() {
let missing = 0;
let checked = 0;

for (const target of defaultTargets()) {
const absoluteTarget = path.join(repoRoot, target);
if (!existsSync(absoluteTarget)) continue;
const markdown = markdownForTarget(target, absoluteTarget);
const targetDir = path.posix.dirname(target);
const failures = [];

const check = (repoRelative, label) => {
if (ALLOWLIST.has(repoRelative)) return;
checked += 1;
if (!existsSync(path.join(repoRoot, base))) failures.push(`${value} (glob base '${base}' missing)`);
continue;
if (!repoPathExists(repoRelative)) failures.push(label);
};

// Inline code spans: repo-root-relative repo paths.
for (const rawCandidate of codeSpanCandidates(markdown)) {
const value = stripSuffixes(rawCandidate);
const base = ROOT_PREFIXES.some((prefix) => value.startsWith(prefix)) ? globBaseDir(value) : null;
if (base !== null) {
if (ALLOWLIST.has(value)) continue;
checked += 1;
if (!existsSync(path.join(repoRoot, base))) failures.push(`${value} (glob base '${base}' missing)`);
continue;
}
if (!looksLikeRootPath(value)) continue;
check(value, value);
}
if (!looksLikeRootPath(value)) continue;
check(value, value);
}

// Markdown link targets: repo docs use both repo-root-relative targets
// (`src/lib/env.ts`) and file-relative targets (`codebase-index.md`,
// `../AGENTS.md`). Accept whichever resolves, confined to the repository.
for (const rawCandidate of linkCandidates(markdown)) {
if (isExternalLink(rawCandidate)) continue;
const value = stripSuffixes(rawCandidate);
if (value === "" || value.includes("*") || /[<>{}$\\]/.test(value) || /\s/.test(value)) continue;
const relative = path.posix.normalize(path.posix.join(targetDir === "." ? "" : targetDir, value));
if (relative.startsWith("..")) {
// Markdown link targets: repo docs use both repo-root-relative targets
// (`src/lib/env.ts`) and file-relative targets (`codebase-index.md`,
// `../AGENTS.md`). Accept whichever resolves, confined to the repository.
for (const rawCandidate of linkCandidates(markdown)) {
if (isExternalLink(rawCandidate)) continue;
const value = stripSuffixes(rawCandidate);
if (value === "" || value.includes("*") || /[<>{}$\\]/.test(value) || /\s/.test(value)) continue;
const relative = path.posix.normalize(path.posix.join(targetDir === "." ? "" : targetDir, value));
if (relative.startsWith("..")) {
checked += 1;
failures.push(`${rawCandidate} (escapes repository root)`);
continue;
}
const rootStyle = path.posix.normalize(value);
const candidates = rootStyle === relative || rootStyle.startsWith("..") ? [relative] : [rootStyle, relative];
if (candidates.some((candidate) => ALLOWLIST.has(candidate))) continue;
checked += 1;
failures.push(`${rawCandidate} (escapes repository root)`);
continue;
const found = candidates.some((candidate) => repoPathExists(candidate));
if (!found)
failures.push(rawCandidate === relative ? relative : `${rawCandidate} (tried ${candidates.join(", ")})`);
}

if (failures.length > 0) {
missing += failures.length;
console.error(`\n${target}:`);
for (const failure of failures) console.error(` MISSING ${failure}`);
}
const rootStyle = path.posix.normalize(value);
const candidates = rootStyle === relative || rootStyle.startsWith("..") ? [relative] : [rootStyle, relative];
if (candidates.some((candidate) => ALLOWLIST.has(candidate))) continue;
checked += 1;
const found = candidates.some((candidate) => repoPathExists(candidate));
if (!found)
failures.push(rawCandidate === relative ? relative : `${rawCandidate} (tried ${candidates.join(", ")})`);
}

if (failures.length > 0) {
missing += failures.length;
console.error(`\n${target}:`);
for (const failure of failures) console.error(` MISSING ${failure}`);
if (missing > 0) {
console.error(`\ndocs link check FAILED: ${missing} missing path(s) across ${checked} checked references.`);
process.exit(1);
}
}

if (missing > 0) {
console.error(`\ndocs link check FAILED: ${missing} missing path(s) across ${checked} checked references.`);
process.exit(1);
console.log(`docs link check passed: ${checked} repo path references resolve.`);
}

console.log(`docs link check passed: ${checked} repo path references resolve.`);
const invokedDirectly = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
if (invokedDirectly) main();
15 changes: 15 additions & 0 deletions scripts/ci-change-scope.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -270,6 +270,15 @@ const perfExclusionPatterns = [
"src/instrumentation.ts",
"src/sentry.server.config.ts",
"src/sentry.edge.config.ts",
// Developer-hub payload only. `src/lib/developer-area/ledger-snapshot.ts`
// imports this JSON, and the only route importers are under
// `src/app/mockups/development/` (already excluded; 404 in production). A
// ledger reconcile that closes the last P1 must not pay a 7-minute
// Lighthouse budget run, and must not fail merge on TBT noise from
// `/documents/search`. Measured on PR #2302: this file alone flipped
// perf_changed and the job failed mobile TBT +32.7% against a baseline
// the same change cannot move.
"data/outstanding-issues-snapshot.json",
];

function isPerfChangedPath(filePath) {
Expand DownExpand Up@@ -983,6 +992,12 @@ function selfTest() {
["public/therapy-compass-data/therapies-home.json", "data/medications-snapshot.json"],
{ perf_changed: true },
);
// Mockup-only ledger snapshot: same `data/` root as medications, but it
// cannot reach a budgeted route. Closing the last P1 on PR #2302 otherwise
// forced Lighthouse onto a docs/ledger reconcile.
assertScope("perf-off-for-outstanding-issues-snapshot", ["data/outstanding-issues-snapshot.json"], {
perf_changed: false,
});
assertScope("perf-on-for-build-config", ["next.config.ts", "postcss.config.mjs", "tsconfig.json"], {
perf_changed: true,
});
Expand Down
20 changes: 20 additions & 0 deletions tests/check-docs-links.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
import { describe, expect, it } from "vitest";

import { appliedInboxFallbackPath } from "../scripts/check-docs-links.mjs";

describe("appliedInboxFallbackPath", () => {
it("maps a pending inbox UUID citation to the applied sibling", () => {
expect(appliedInboxFallbackPath("docs/outstanding-issues-inbox/edebb730-91d9-42f5-bd93-ca2abb9678bc.json")).toBe(
"docs/outstanding-issues-inbox/applied/edebb730-91d9-42f5-bd93-ca2abb9678bc.json",
);
});

it("does not wrap an already-applied path or a nested inbox file", () => {
expect(
appliedInboxFallbackPath("docs/outstanding-issues-inbox/applied/edebb730-91d9-42f5-bd93-ca2abb9678bc.json"),
).toBeNull();
expect(appliedInboxFallbackPath("docs/outstanding-issues-inbox/README.md")).toBeNull();
expect(appliedInboxFallbackPath("docs/outstanding-issues.md")).toBeNull();
expect(appliedInboxFallbackPath("docs/outstanding-issues-inbox/not-a-uuid.json")).toBeNull();
});
});
23 changes: 14 additions & 9 deletions tests/developer-hub-page.dom.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -34,8 +34,8 @@ vi.mock("@/components/account-data-provider", () => ({
* snapshot — so the band is exercised against the shape the route actually
* loads rather than a hand-built fixture that could drift from it. `null` means
* "do not override", which is also how the assertions below read the true
* count. The committed snapshot has `p1 === 2`, so without this the singular
* and zero branches are never reached at all.
* count. The committed snapshot can have `p1 === 0` (no open P1s), so the
* override is what reaches the singular and non-zero branches.
*/
const p1 = vi.hoisted(() => ({ value: null as number | null }));

Expand DownExpand Up@@ -109,12 +109,17 @@ describe("developer hub page — synthetic-data warning", () => {
describe("developer hub page — needs-you-now band", () => {
it("reports the snapshot's own P1 count", () => {
// No override, so this is the real committed snapshot: the band must agree
// with the data the route actually loads, not merely with itself.
// with the data the route actually loads, not merely with itself. When the
// snapshot has no P1s the page must omit the band rather than render a
// settled-looking "0 blocking items" line — that is the same contract as
// the explicit zero-override case below.
const { counts } = loadLedgerSnapshot();
expect(counts.p1).toBeGreaterThan(0);

render(<DeveloperHubPage />);
expect(screen.getByTestId("developer-hub-needs-you-now")).toHaveTextContent(String(counts.p1));
if (counts.p1 > 0) {
expect(screen.getByTestId("developer-hub-needs-you-now")).toHaveTextContent(String(counts.p1));
} else {
expect(screen.queryByTestId("developer-hub-needs-you-now")).toBeNull();
}
});

it("carries no text beyond the computed count", () => {
Expand DownExpand Up@@ -144,9 +149,9 @@ describe("developer hub page — needs-you-now band", () => {
});

it("renders nothing rather than a reassuring all-clear when there are no blockers", () => {
// The committed snapshot has p1 = 2, so this branch is otherwise never
// exercised. A band reading "0 blocking items" would be a settled-looking
// statement about work the page cannot see.
// A band reading "0 blocking items" would be a settled-looking statement
// about work the page cannot see. The override keeps this branch explicit
// even on days the committed snapshot already has no P1s.
p1.value = 0;
render(<DeveloperHubPage />);

Expand Down
Loading