Skip to content

docs(og): gate the OG card URL's dotted final segment, and name the coupling on both sides - #13702

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-12326-og-marker-dot-coupling
Aug 31, 2026
Merged

docs(og): gate the OG card URL's dotted final segment, and name the coupling on both sides#13702
os-project-manager merged 1 commit into
mainfrom
claude/issue-12326-og-marker-dot-coupling

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #12326

The coupling, re-verified rather than inherited

getPageImage() in apps/docs/lib/source.ts builds every Open Graph card URL as
/og/docs/ + the page slugs + an image.png marker. That marker is load-bearing twice,
and only the first is discoverable from the code:

  1. app/og/docs/[...slug]/route.tsx resolves the page with source.getPage(slug.slice(0, -1))
    — the marker is the sacrificial segment that slice discards. Its name is irrelevant to that.
  2. Its DOT is what keeps the URL out of the locale rewriter. apps/docs/proxy.ts's matcher
    excludes any path containing a dot, so a dotted final segment skips the proxy entirely. A
    dotless marker is rewritten under the locale prefix, which is not a route at all — the
    app/og/ tree is top-level, not under app/[lang]/.

Both premises re-measured on this branch's merge base, not carried over from the card:

  • proxy.ts's matcher is still, byte for byte, '/((?!api|_next/static|_next/image|favicon.ico|.*\\..*).*)'.
  • app/og/ is a top-level route directory; the only locale-prefixed tree is app/[lang]/, whose
    children are docs/ and blog/ — so the rewritten path really has no route.
  • Compiling that matcher and applying it to the URLs in question:
/og/docs/ai/agents            -> proxy RUNS   (rewritten -> no route -> 404)
/og/docs/ai/agents/image.png  -> proxy SKIPS
/og/docs/ai/agents/x.png      -> proxy SKIPS  (the name is free)
/og/docs/ai/agents/image-png  -> proxy RUNS   (the dot is not)

That is the whole invariant, and until this PR it lived only in an issue comment.

Both halves ship

1. A cross-referencing comment on each side.
apps/docs/lib/source.ts now names apps/docs/proxy.ts, the OG route, and what breaks;
apps/docs/proxy.ts now names getPageImage() and lib/i18n.ts as the two surfaces its
dot-exclusion limb holds up, in opposite directions. Both point at pnpm check:docs-locale-catch-all.

2. The mechanical half — check-docs-locale-catch-all gains a limb.
The charter's "extend or companion" choice was resolved as extend, for three measured reasons:

  • The script already parses and compiles proxy.ts's matcher. A companion would either duplicate
    that parse or import from this script — a dependency in a family that is deliberately
    dependency-free so it can run with bare node and no workspace install.
  • The script's subject already is "what depends on the proxy's dot rule". The catch-all guard is one
    dependent; the OG marker is the other. One file now answers the whole question.
  • A companion costs a new lint.yml step, a new entry in the check-self-test-wired population and a
    second self-test output surface, for an assertion of roughly twenty lines.

What the limb asserts is exactly the half the charter named and nothing more: the URL
getPageImage() builds ends in a final segment containing a dot.
It deliberately does not
re-assert that the matcher excludes dotted paths.

It reads two things, not one. The marker literal alone would be a check on a variable that may no
longer reach the URL, so the limb also requires the returned template to still end with the joined
segments — otherwise the last array element is not the URL's final segment and the dot assertion
would be pinning the wrong string. Every shape it cannot parse (builder renamed away, array gone,
marker not a literal, url no longer built from a template) is a finding, never a quiet pass.

The ablation — the assertion observed going RED

Run against the real tree, on top of the implementation commit, with the mutation confirmed on disk
before any reading was taken (no build leg: the gate reads source text, it does not import built
artifacts).

HEAD blob   = 8fe04f2946079c5de4e96f12029b8b26e02d0f07
before hash = 8fe04f2946079c5de4e96f12029b8b26e02d0f07     (tree confirmed at HEAD)
mutation    = [...page.slugs, 'image.png']  ->  [...page.slugs, 'image']
removed-text occurrences after mutation = 0 (want 0)
injected-text occurrences after mutation = 1 (want 1)
after hash  = 5455cb64b81ddb4bd31610890b7b77635c3eac8d     (bytes moved)

VERDICT-EXIT prod=1
x check-docs-locale-catch-all -- 1 finding(s)
  - the OG card URL `getPageImage()` builds ends in `image`, which contains no dot.
    proxy.ts's matcher excludes ONLY paths containing a dot, so `/og/docs/ai/agents/image`
    is now locale-rewritten ... Every `og:image` on the site 404s at once, and nothing
    fetches these URLs, so no other check sees it.

restored hash = 8fe04f2946079c5de4e96f12029b8b26e02d0f07
git diff HEAD --name-only = []
RESTORE PROVEN: blob equals HEAD blob and git diff HEAD is empty

Control ablation — the same edit keeping the dot ('image.png' to 'card.jpeg'), mutation
likewise confirmed on disk: VERDICT-EXIT prod=0, summary
OG card marker "card.jpeg" ends in a dot: true. So the limb pins the dot, not the filename,
which is what the measurement says matters.

The same two directions are pinned in --self-test as well, over the real checkApp path: a marker
stripped of its dot, a marker dropped entirely, a url that stopped ending in its segments, a missing
builder — each observed failing — plus a renamed-but-dotted marker observed staying green.
Self-test assertion count went 12 to 21.

Was a running server needed? No.

Both facts are static: the matcher is a string literal in proxy.ts and the marker is a string literal
in lib/source.ts. The gate compiles the one and applies it to the other, with no server, no network
and no workspace install — the same dependency-free shape the rest of the file already had.

Verification

Derived from the actual diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
(36 families, 39 commands), run at 6bcc114bc — every reading below taken with the exit code captured
before any pipe.

  • 37 of 39 green, including pnpm check:docs-locale-catch-all (production exit=0;
    1 top-level dynamic segment(s), 1 guarded; dotted paths bypass proxy.ts: true; OG card marker "image.png" ends in a dot: true (that URL skips proxy.ts: true)), check:nul-bytes,
    check-self-test-wired, check-self-test-workflow-commands, check:workflow-status-functions
    and the other four workflow parsers that read the edited lint.yml.
  • node scripts/check-test-completeness.mjs -> exit 3 = NOT MEASURED by the script's own words
    (it needs a saved turbo run test log). Not a finding.
  • pnpm check:type-check-debt -> its --re-measure leg refuses to run without the built package
    closure, and says so: measuring from here would measure a different world. Declared narrowing,
    with its evidence: the sibling pnpm check:type-check-coverage (same script, same --self-test,
    structural half) is green, and this diff contains no input of that gate — no packages/**, no
    package.json, no tsconfig*.json, no *typecheck-debt.json. CI builds the closure and runs it.
  • Repo-wide eslint . narrowed to the changed files, and the narrowing measured rather than
    assumed:
    population decided by eslint's own config resolution (it reports .github/workflows/lint.yml
    as File ignored because no matching configuration was supplied, so the YAML is outside its
    population by eslint's own answer); 3 files linted, counted from --format json output length;
    0 errors, 0 warnings. Invariance for untouched files: this repo runs one eslint.config.mjs which
    never enables type-aware linting for any file (no parserOptions.project, no typed rules — stated
    and positively controlled in that config's own header), so nothing in this diff can move an untouched
    file's verdict.
  • apps/docs typecheck (next typegen && tsc --noEmit) narrowed away, mechanically: the entire
    apps/docs diff is added comment lines and nothing else — every added line matches a comment
    prefix and there are zero removed lines. The one thing that does read those comments as data is
    check-docs-locale-catch-all itself, which parses both files and is green.

A measurement the reviewer should see

The dispatch charter states that the complementary half — that proxy.ts's matcher excludes dotted
paths — is already asserted, and forbids duplicating it. Measured against the script: it is
reported, not asserted. stats.dottedBypassesProxy is a condition; when it is false the script
does not fail, it relaxes the catch-all requirement and stays green (self-test case 6 pins exactly
that behaviour, deliberately).

Consequence, stated plainly rather than acted on: after this PR, a change to the lib/source.ts side
of the coupling is gated, and a widening of the proxy.ts side still goes green on both limbs even
though it 404s the same surface. The charter's boundary was respected — nothing here re-asserts the
matcher — and the fact is surfaced instead, in three places: this section, the new header section in
the script, and the gate's own summary line, which now prints (that URL skips proxy.ts: true) for
the built OG URL so the value is visible in every CI log. Closing that half is a one-line change the
seat can call.

Changeset

None, and skip-changeset applied: this PR releases nothing. The two apps/docs edits are
comment-only in a private, unpublished app; the remaining files are a CI gate script and a comment
plus a step name in lint.yml — the case lint.yml itself names as the textbook one for the label.


Generated by Claude Code

…upling on both sides

`getPageImage()` in `apps/docs/lib/source.ts` appends an `image.png` marker to
every Open Graph card URL. That marker is load-bearing twice, and only the first
is visible from the code: the OG route slices it off to resolve the page, and
its DOT is what keeps the URL out of `proxy.ts`'s locale rewriter (the matcher
excludes any path containing a dot). Measured: `/og/docs/ai/agents` is rewritten
to `/en/og/docs/ai/agents`, which is not a route; `/og/docs/ai/agents/x.png` is
not rewritten. The marker's name is free; its dot is not.

Both halves:
- Cross-referencing comments on each side, each naming the other file and what
  breaks.
- `check-docs-locale-catch-all` gains a limb asserting that the URL
  `getPageImage()` builds ends in a final segment containing a dot. It reads the
  marker AND the template that turns the array into the URL, so the assertion
  cannot survive as a check on a variable that no longer reaches the URL. The
  matcher-excludes-dots half is the condition the script already reads; it is
  reported in the summary, not re-asserted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] docs site: the OG card URL depends on its marker segment containing a DOT — an undocumented coupling between lib/source.ts and proxy.ts

2 participants