Uh oh!
There was an error while loading. Please reload this page.
fix(docs): converge the docs host on the canonical origin, gate included - #10659
Merged
Conversation
The repo named three hosts for one docs site. `content/docs.site.json` declared `protocol.objectstack.ai` as the site's own canonical URL, `docker/Dockerfile` pointed self-hosting readers at `docs.objectstack.ai`, and `scripts/check-published-readme-links.mjs` — a gate — *prescribed* `docs.objectstack.ai` to every author who tripped it. Maintainer ruling, 2026-08-21: 「这个仓的文档站规范 URL 是 https://objectstack.ai」. The gate is the load-bearing one, because it propagates: it landed on 2026-08-18 with one such link in its own census, and by 2026-08-21 authors following its remedy text had written twelve. Its classifier, its remedy strings and its self-test all encoded the host, so all three move together. The two halves are deliberately asymmetric, and the header says why: * the classifier ACCEPTS the aliases (`docs.`, `protocol.`, `www.`). Dropping a host from `DOCS_HOSTS` does not reject it — it reclassifies it as `external`, which is out of scope by name, so the page and anchor assertions stop reading it. Tightening there would delete coverage from exactly the URLs most likely to rot. * the remedy PRESCRIBES only `https://objectstack.ai`, and a new assertion 2 makes an alias origin a finding in its own right. Accepted for checking, rejected for authoring. The 12 published-README links, `docker/Dockerfile`'s two comments, the `create-objectstack` scaffold templates and `meta.url` are all converged. Fixes#10641 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GawRwpD44VwBDVy3hs77AX
Backs the three `src/templates/blank/` files out of this PR. They are the declared surface of #10321, which was promoted into sweep anchor #10353 — a queued card with its own PR contract ("zero changes outside the six items", one closing line per member). Sweeping them here would have left that member half-delivered by a PR that never named it, and `blank/README.md` is also touched by the open PR #10507. The canonical host is settled now, so #10321's open question ("dev states which and why") has an answer; that is recorded on the anchor rather than acted on here. The gate does not read these files — they are outside the published-doc population — so nothing goes red by leaving them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GawRwpD44VwBDVy3hs77AX
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 9 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 25 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
os-elon
marked this pull request as ready for review
August 21, 2026 07:52
os-elon
enabled auto-merge
August 21, 2026 07:52
Uh oh!
There was an error while loading. Please reload this page.
os-elon pushed a commit
that referenced
this pull request
Aug 21, 2026
…ost, and retarget protection docsUrl at a live page Two defects sharing one string literal, in the code that emits text to users. The host half is spelling ratification. `docs.objectstack.ai` redirects to `https://objectstack.ai` path-preservingly, so nothing here was broken -- it was the unratified spelling sitting where users copy from. Maintainer ruling, 2026-08-21: 「这个仓的文档站规范 URL 是 https://objectstack.ai」. #10641/#10659 converged the docs-site half (link gate, docs.site.json, Dockerfile, published READMEs); these are the references outside that surface. The path half is a real dead link. All 29 `protection.docsUrl` values pointed at `/adr/0010-metadata-protection`, and `/adr/...` is not a route on any host: `apps/docs/lib/source.ts` mounts `content/docs` under `/docs`, `docs/adr/` is not published, `apps/docs/app` has no `/adr` segment, and `check-docs-redirects` confirms 0 redirect sources outside the `/docs` route space. The slug was wrong too -- the record is `0010-metadata-protection-model.md`, the same filename drift `check-adr-links.mjs`'s header already records. Studio renders this URL in the lock banner, so an operator asking why an item is locked was sent nowhere. Retargeted to `https://objectstack.ai/docs/references/shared/protection` -- the published reference for the very schema carrying the field, and a `/docs/` route that `check:published-readme-links` assertion 3 and `check:docs-redirects` both keep alive. A github.com blob of the ADR was the alternative and was rejected: the consumer is a browser banner aimed at tenant operators, not contributors, and no gate would notice the blob path rotting. `packages/spec/src/shared/protection.zod.ts` is a generated-reference source, so `content/docs/references/shared/protection.mdx` moves with it (one line). `authorable-surface.base.json` is unchanged -- no acceptance face moved. Deliberate survivors of the repo-wide sweep, each owned elsewhere: `skills/objectstack-data/SKILL.md` and the two ADRs (#10656, governed surfaces needing a maintainer-landed PR); the create-objectstack templates (#10321); `scripts/check-published-readme-links.mjs`, whose classifier accepts the aliases by design; and 10 CHANGELOG entries plus #10659's changeset, all historical prose. Fixes#10655 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B4h3medzvhB9rpfoja9jcw
os-elon pushed a commit
that referenced
this pull request
Aug 22, 2026
…nonical host The scaffolded blank template's README.md linked docs.objectstack.ai (an accepted-but-unratified alias) in three places, while the root README.md already used the ruled canonical origin, https://objectstack.ai. One `npm create objectstack@latest` run therefore handed the newcomer two different documentation hostnames. Maintainer ruling, 2026-08-21: 「这个仓的文档站规范 URL 是 https://objectstack.ai」. scripts/check-published-readme-links.mjs's CANONICAL_DOCS_ORIGIN prescribes the same origin, and #10659/#10935 already converged the docs-site build config and the runtime-emitted docsUrl strings onto it — 02b3b07 explicitly deferred the create-objectstack templates to this issue (#10321). Fixes#10321 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#10641
The repo named three hosts for one docs site. The maintainer ruled one of them
canonical on 2026-08-21, verbatim and untranslated:
This converges the tree on it — including the gate that was prescribing one of
the others.
Could the hosts be measured? No — and here is what turned up instead
This environment's egress proxy denies CONNECT to all three hosts (
objectstack.ai,protocol.objectstack.ai,docs.objectstack.ai— 403 at the gateway, forcurlandfor the fetch tool alike), so the live resolve-check the card asked for is
outstanding. DNS resolves for all three, but overlapping edge IPs on a shared
anycast frontend say nothing about what is served, so that is not offered as a
measurement.
What is available is in-repo evidence, and it answers the question the card was
really asking — aliases, or separate deployments? Commit
5329a65450("docs: point site links at canonical objectstack.ai", 2026-07-17) says in its own
message:
So: aliases, not separate deployments — the naming-consistency fix the card
scoped, not the larger finding it told me to stop on. Every link rewritten here was
followable before and is followable after; only the spelling changes. Confirmation
against the live hosts is still worth doing by someone whose network can reach them.
Why this kept coming back
That July commit converged root
README.md,docker/README.mdand the docs-sitenavbar link. Two things it missed, and one that landed later and undid it:
content/docs.site.json'smeta.urlanddocker/Dockerfile;scripts/check-published-readme-links.mjslanded, prescribingdocs.objectstack.aiin its remedy text. Its own census counted one such linkat that commit. By 2026-08-21 there were twelve — authors did what the gate told
them. A gate that prescribes is a gate that propagates, which is why the gate, not
the links, is the substance of this PR.
The load-bearing file, and the decision made in it
check-published-readme-links.mjsencoded the host in three places — classifier,remedy text, self-test. They move together here, but not symmetrically, and the
asymmetry is the decision the card asked me to state:
The classifier ACCEPTS the aliases. The remedy PRESCRIBES only the canonical origin.
Accepting is not slack — it is the direction that preserves coverage. Removing a
host from
DOCS_HOSTSdoes not reject it; it reclassifies it asexternal, a bucketthat is out of scope by name, so the page-resolution and anchor assertions stop
reading it entirely. Tightening the classifier would therefore have deleted the
anti-rot checks from exactly the URLs most likely to rot — the ones already shipped
inside npm tarballs, which outlive any in-repo fix. It would also have rested on an
assumption about alias liveness that I could not measure.
Prescribing narrowly is where the strictness goes. A new assertion 2 makes a
non-canonical origin a finding in its own right, carrying the canonical rewrite of
that exact URL as its remedy. It deliberately does not
continue: a link can beboth off-convention and dead, and the author should be told both in one run.
So an alias URL is accepted for checking and rejected for authoring. Those two
facts do not conflict, which is why assertion 2 is documented as a convention
claim rather than folded in with the three reachability ones. The gate is now four
assertions, still ordered by cost.
The comparison is on
URL.origin, not hostname, sohttp://on the canonical host isalso a non-canonical origin and the rewrite fixes the scheme in the same move.
The self-test was verified by ablation, not by inspection
Both legs ran from the committed state, with the mutation confirmed on disk by
grepping the anchor text before and after (a zero-hit
sedexits 0 and wouldotherwise read as a clean ablation), then restored and re-confirmed byte-identical.
No build step is involved — these are
.mjsscripts node runs directly, nothingresolves through a
dist/.CANONICAL_DOCS_ORIGINreverted to the aliasA2 FAILS on an alias origin,A2 SILENT on the canonical origin,A2 does not continue,canonical: …both directions), not only the string pinsDOCS_HOSTSnarrowed to the canonical host aloneA2 does not consume the link: an alias page still RESOLVESandA4 still reads the anchor when the origin is an aliasThe second is the one that matters: it puts the design decision under test, not the
string. A future edit narrowing
DOCS_HOSTSwould lose coverage silently — thefindings would simply stop appearing — so the self-test asserts the resolution and
fragment counters, which cannot go green by going quiet.
Before the sweep, the new assertion was also observed firing against the real tree:
12 findings, one per link, each naming its own canonical rewrite.
content/docs.site.json— what consumesmeta.urlNothing in this repo does. Searched by filename, by key, and by content-tree read
path:
apps/docsreads onlycontent/docsandcontent/blog, throughsource.config.ts. Site metadata (title, description, icons) is hardcoded inapps/docs/app/layout.tsx; there is nometadataBase, no sitemap route and norobotsroute, andapp/og/docsdoes not read this file.So the honest statement is that this is a declaration, not a live input — page
metadata and OG tags do not currently flow from it. It still mattered: it is the file
a human reads to learn what the canonical URL is, and it was the source of one of the
three answers. Its siblings (
meta.favicon, the navbar "Website" link) already usedthe apex;
meta.urlwas the straggler, left behind by that July commit. Set to theruled string exactly, so a consumer added later reads the right value.
The rest of the sweep
docker/Dockerfile— both comment lines, which point self-hosting readers atthe alias.
It found exactly 12 and 12 were fixed; the gate is green with
0 non-canonical origin(s)and all 12 pages still resolving.A README ships inside the npm tarball, so without a republish the old host keeps
facing every reader of those package pages. That is the same reasoning the gate's
own header gives for existing at all.
What was deliberately left alone
The
create-objectstackscaffold templates. I swept them, then backed them out ina2bf4b8734— the second commit here is that reversal, left visible rather thanrebased away.
src/templates/blank/{README.md,Dockerfile,docker-compose.yml}are thedeclared surface of #10321, which was promoted into sweep anchor #10353, a queued card
whose PR contract reads "zero changes outside the six items" with one closing line
per member. Delivering part of a member from a PR that never named it would have left
that anchor's accounting wrong, and
blank/README.mdis touched by the open PR #10507as well. Those files are outside this gate's published-doc population, so nothing goes
red by leaving them. #10321's open question — "dev states which and why" — now has an
answer, and it is recorded on the anchor.
Three further groups are filed rather than swept, each needing a different kind of PR:
docsUrl:values (which also point at a non-/docs/route) #10655 — runtime and metadata strings a user actually sees: the CLI'srelease-notes URL (
RELEASES_BASE, with tests pinning it), the Setup/Studio in-appdoc text, and ~25
docsUrl:values on platform objects. That last group also pointsat
/adr/0010-metadata-protection, which is not a/docs/route at all — a second,separate defect worth settling before the host is swapped. Touching
packages/spec/src/shared/protection.zod.tsthere drags in the specgenerated-artifact gates, which is why it is not a rider here.
skills/objectstack-data/SKILL.md, two ADRs).Prime Directive feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14: a diff touching those lands by the maintainer's hand, so mixing
them in would have forked this PR's landing path over five lines.
[text](url)and[label]: urlforms, but not the CommonMark autolink — abare URL wrapped in angle brackets, which is the form five published READMEs use for
their prominent "Docs" and "API Reference" lines. Ten such docs-site links are read
by none of the four assertions. All ten currently resolve, so nothing is broken
through it — a latent hole, filed as an observation. (Spelled out in words here on
purpose: written literally, GitHub's body sanitizer eats the token, backticks and
all. It did exactly that to the first version of this paragraph.)
Non-goals honoured
apps/docs/next.config.mjsis untouched — #10638 owns it. Nothing outside this repochanged; if the aliases should stop answering, that is a DNS/Vercel action and the
maintainer's call, not a PR.
Gates
Re-derived after the final commit with
node scripts/pm/dispatch-gates.mjs(no pathargs — it takes the change set from the merge base itself), then run at
a2bf4b8734.The derived set was re-derived again after the scope reversal, and re-run on the new
head. All green, quoting each gate's own verdict line rather than a shell status:
Plus
check:changeset-gate-self-tests,check:cross-package-test-inputs(bothspellings),
check:objectui-changeset,check-adr-0087-registration,check-changeset-no-major,check-empty-changesetandaffected-docs— all exit 0.check:i18nfirst refused rather than passing: the fresh worktree's CLI was not built,and it says so — "Nothing was checked … this result says NOTHING about whether the
committed translation bundles are in sync" — instead of comparing nothing and calling
it green. Built
@objectstack/cliand re-ran: in sync.Exit codes were captured before any pipe throughout, never read from a
$?that apipeline had already overwritten, so no verdict quoted here is a
tailstatus wearinga gate's clothes.
Generated by Claude Code