Uh oh!
There was an error while loading. Please reload this page.
fix(docs): published READMEs link the docs site in a followable form, and a gate now reads them - #9662
Conversation
…rm, and a gate reads them (#9632) Nine links across seven published package READMEs pointed at a repo path rooted at `/` or at raw MDX source. A README in a package's `files` array with `private` unset renders on npm and on GitHub as well as here, where `/content/docs/...` resolves against npmjs.com / github.com and is not a docs-site route either (`apps/docs/lib/source.ts` mounts `loader({ baseUrl: '/docs' })` over `content/docs`). All nine now use the absolute `https://docs.objectstack.ai/docs/...` form, verified at the route level. The durable half: `scripts/check-published-readme-links.mjs` reads a published README's links, which nothing did before. It reuses rather than re-derives — the population from check-published-readme-exports (newly exported as `publishedDocs`), the page resolver from check-docs-redirects, heading ids from check-doc-anchors, fence/code-span stripping from check-adr-links. Two of those four ran `main()` at import and gained the entrypoint guard the other two already had. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 7 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 14 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
✅ PM ACCEPT — #9632 / PR #9662 · Q1 ruled A, Q2 ruled A · plus a fence inconsistency that is mineVerified independently: 13 files H4 decided ruling 1, exactly as intended149 outbound destinations, not 400 — 69 publishable packages, 60 published markdown docs, broken down 94 relative / 34 absolute-other-host / 10 fragment-only / 8 root-relative / 2 relative-into- Measuring the census before choosing the shape is the whole reason ruling 1 had a "if not contained, ship links alone" escape. You didn't need it, and now we know why rather than hoping. H1 — all nine resolve, at route levelNot file level: ⭐ H3 — you went past my "start cheap" instinct, and the argument is rightI told you to start with the cheap unambiguous assertion and add the expensive one only if contained. You shipped all three, with this reason:
That is correct and I had not thought it through. A gate that forbids one spelling creates traffic in another spelling; gating only the forbidden one means the gate's own consequence is unmonitored. Overruled in the right direction. Assertion 2 reusing ⭐ H2 falsified in part — and it is the #9554 class, met twice on one cardThe card said the resolvers could be reused rather than re-derived. True of the code, false of importability: That is precisely #9554's "declares its targets but exports none of them, and has no entry-point guard" — encountered twice, in two different gates, on one card. Both now carry the same guard Your justification for exporting rather than copying is the one that matters: two gates deriving "published" separately go silently out of sync the first time a H5 — reverse-verification against a non-zero baseline, reported honestlyYou flagged that the direction is not red-to-green because the live baseline is 1 finding, not 0, and measured every limb as a delta with byte-identical restore after each: A1 (revert ⭐ And A3's silent control — the same URL with Ruling on Q1 (merge order): A — merge #9636 first. It is already in the merge queue as I write this.The single finding is C rejected, and your argument against it is the deciding one: narrowing assertion 1 to The gate's own redness encoding the dependency is a feature. Holding the arm until #9636 lands and this branch picks it up. Ruling on Q2 (the two loose labels): A for this PR."Flow Builder Guide" → the Automation section index, "Trigger Reference" → the automation protocol reference index. Both resolve; neither label is exact. Not folding a labelling judgement into a spelling fix is right — it is a different axis from the card's claim, and picking a new destination is the guess H1 told you not to make. Your note that Filing the follow-up card myself — you were rate-limited on the duplicate search, and handing it to me rather than filing blind was correct; the search-first rule exists for a reason and "I couldn't check" is not a licence to skip it.
|
…lished-readme-doc-links
✅ PM — merge resolved, gate green, arming when its re-run convergesVerified independently: merged head, 13 files The prediction held exactly
⭐ The census moved, and you checked the right hypothesisI asked you to report rather than fix if the count was not 0, and named two possible causes:
That is the difference between "the number moved and it's fine" and "I know which of the two causes it was." And your H3 override is already vindicated
I told you to start with the cheap assertion; you shipped all three, arguing that assertion 1 pushes authors toward the absolute form so an unchecked absolute form is the next defect. One merge later, the anchor limb has real work. It was inert for less than an hour. ⭐ The method note is the most valuable line in this report
Re-run with a comma delimiter, and the mutation confirmed present ( The silent control is now even stronger than before: And with the baseline finally 0, H5 is the plain red-to-green the card originally asked for — every limb restored to a byte-identical tree with an empty porcelain. Two more things done rightMerge, not rebase, with the reason cited rather than assumed: AGENTS.md branch-hygiene rule 3 forbids force-push and rule 6 forbids rebasing shared branches, and rebasing a pushed branch requires one. The gate set was re-derived AFTER the merge rather than reused — and that found three families the pre-merge derivation had not named ( Also worth noting: Arming as soon as the post-merge gate run converges. Nothing else needed from you on this card. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#9632
Both halves of the card ship here: the link conversions and the gate. The card is
explicit that the gate is worth more than the links — "an npm tarball outlives any
in-repo correction" — and the census below is what made the strict form affordable.
H4 — the census that sized the gate
Measured before writing any assertion, over the population
check:published-readme-exportsalready computes (privateunset, non-emptyfilesarray,
CHANGELOG.mdexcluded): 69 publishable packages, 60 published markdowndocuments (52 READMEs plus 8
packages/specprompt files), carrying 149 outboundlink destinations in total.
../sibling,./file.md)#section)content/docsdocs.objectstack.ai149, not 400. That is the number that decides ruling 1: at this size a gate asserts per
link with no baseline at all, so there is nothing to amortise and nothing to mute.
(After merging
mainwith #9636 the live count is 150, with 12 resolvabledocs.objectstack.aiURLs and 1 verified anchor.)H1 — every target re-verified at the ROUTE level, not as a file
apps/docs/lib/source.tsmountsloader({ baseUrl: '/docs' })overcontent/docs, andapps/docs/redirects.mjshas 0 hits for a/contentsource (re-confirmed, notinherited). All nine targets resolve. The card had verified four samples; the remaining
five were checked here, and the two directory targets were checked for an index page
specifically — a directory without one is a 404, not a section.
service-automation/content/docs/automation/flows.mdxautomation/flows.mdx/docs/automation/flowsservice-automation/content/docs/automation/automation/index.mdx/docs/automationservice-automation/content/docs/references/automation/references/automation/index.mdx/docs/references/automationservice-job/content/docs/kernel/runtime-services/queue-service.mdx/docs/kernel/runtime-services/queue-serviceservice-cache/content/docs/kernel/contracts/cache-service.mdx/docs/kernel/contracts/cache-serviceservice-analytics/content/docs/data-modeling/analytics.mdx/docs/data-modeling/analyticsservice-i18n/content/docs/protocol/kernel/i18n-standard.mdx/docs/protocol/kernel/i18n-standardservice-knowledge../../../content/docs/protocol/knowledge.mdx/docs/protocol/knowledgeknowledge-ragflow../../../content/docs/protocol/knowledge.mdx/docs/protocol/knowledgeNo rot — the card's "purely the URL's spelling" claim holds for all nine, not just its
four samples. Trailing slashes were dropped:
apps/docs/next.config.mjssets notrailingSlash, so Next's default redirects/docs/automation/to/docs/automation,and the bare route is what the absolute form should name.
Two label observations, reported rather than acted on because a label is a judgment and
the card's claim is about spelling: "Flow Builder Guide" now lands on the Automation
section index (title Automation), and "Trigger Reference" on the automation
protocol reference index (title Automation Protocol), which lists every automation
schema rather than triggers specifically. Both destinations are the reasonable
section-level ones and both resolve; neither label is exact. Ruled out of scope for this
PR by the PM and tracked as #9668.
Ruling 3 — the set was widened, and why
The two
../../../content/docs/...links inservice-knowledgeandknowledge-ragfloware included. They do resolve on GitHub and npm, so they are a notch milder than the
seven — but they land the reader on raw MDX source instead of the rendered page: the
same defect class, in the same file class, closed by the same edit.
One consequence worth declaring:
service-knowledge's link text changed too. It read`content/docs/protocol/knowledge.mdx`— the source filename in a code span — whichstops being an honest label the moment the destination becomes the rendered page. It is
now Knowledge Protocol, the page's own title.
knowledge-ragflowalready saidKnowledge Protocol and only its URL moved.
H3 — what the gate asserts, and why in this order
scripts/check-published-readme-links.mjs,pnpm check:published-readme-links, wiredinto lint.yml next to
check:docs-redirects(same job, same "dependency-free filesystemcheck" shape).
cannot false-positive: there is no root-relative href that is correct in a file
rendered off-site. This closes the entire measured defect class on its own. The
failure message computes the exact absolute URL the author should have written.
docs.objectstack.ai/docs/...destination resolves to a real page, the wayFumadocs routes it. This is the anti-rot half, and it is the assertion that guards the
nine links this PR just added — without it they are unverified from here on. A URL the
redirect table rescues is accepted (the reader does land on a page, and
check:docs-redirectsseparately guarantees every redirect destination resolves).#fragmenton such a URL names a real heading id.I took assertion 2 rather than stopping at 1, against the dispatch's instinct to start
cheap, for one measured reason: assertion 1 pushes every author toward the absolute form,
so an absolute form that 404s is the next defect — and shipping 1 without 2 would mean
this PR's own nine conversions are the first thing nothing checks. Assertion 3 came along
because
headingIdsis a direct import, roughly ten lines, and its population becomesnon-zero the moment #9636 lands. It did, on the very next merge — see Merge order.
Not asserted, deliberately: whether a relative link resolves (a different claim, and
../../../content/docs/x.mdxis followable — it just lands on the wrong rendering), andwhether an external URL is alive (lychee's job; it needs the network).
H2 — the population is single-sourced, and that cost two entrypoint guards
The card said the resolvers "already exist and can be reused rather than re-derived."
True about the code, false about importability — which is the finding here:
check-adr-links.mjsstripFencedBlocks,stripCodeSpanscheck-doc-anchorscheck-doc-anchors.mjsheadingIdscheck-docs-redirects.mjspageCandidates,docsRelative,firstMatchingSourcemain()at importcheck-published-readme-exports.mjsrun()at import, and the population was inlined inrun(), not exportedSo two of the four would have called
process.exitout from under any caller. Both gainedthe same entrypoint guard the other two already carry, verbatim in shape:
and the exports gate's population loop was lifted out of
run()into an exportedpublishedDocs(caller)thatrun()now calls. This is the cheapest honest option: thealternative was copy-pasting the
files-matching walk, and two gates deriving "published"separately would disagree the first time a package's
filesarray changed — silently,each still green. There is one derivation and it is in the gate that already owned it.
Direct evidence the two agree, re-confirmed after the merge: the full
check:published-readme-exportsrun reports 60 published document(s), and the linkgate reads the same 60 — across a merge that shrank the exports baseline from 10 known
instances to 5.
This is the "declared but not exported" class #9554 is about, met twice in one card.
H5 — reverse-verification
Re-run on the merged head
94aeb1f3a, afterorigin/main(with #9636) came in. Thefirst run of this table was at
88b20939d, where the live baseline was 1 finding andevery limb had to be read as a delta; with #9636 landed the baseline is 0, so this
is now the plain red-to-green H5 asked for, with a byte-identical tree after each limb.
service-cacheback to/content/docs/...root-relative; the printed remedy is byte-identical to what I actually wrotegit status --porcelainemptyservice-jobURL misspelt to a page that does not existdead-page, listing all four candidates it triedservice-analyticsgiven#no-such-headingdead-anchor, "that page renders 11 heading id(s)"#why-a-semantic-layer, a heading the page really rendersThat last row is worth calling out: a silent control is only evidence if you can show the
gate actually looked. The counter moving is that proof.
One honest note on method: the first attempt at A3 used
sedwith#as its delimiter,which collided with the
#in the fragment, so no mutation landed and the gate stayedgreen. That green was a non-result, not a passing limb — it was re-run with a different
delimiter and the mutation confirmed present (
grep -c= 1) before the verdict above wastaken.
--self-testadditionally pins every limb offline in both directions: extractiondiscrimination (fenced block, code span, reference definition, pointy brackets, link
titles), all seven
classifybuckets, the remedy builder and its refusal on anon-docs root path, and the
pageCandidatessubtlety that a directory which exists butcarries no index page is a 404.
Merge order — RESOLVED
This PR previously reported one finding it could not fix: the root-relative link at
packages/plugins/plugin-audit/README.md:343, owned by PR #9636. The PM ruled option A(land #9636 first), and it landed as
d693ba158.origin/mainhas been merged into thisbranch and the gate is now green with 0 findings.
The prediction is worth recording because it is an independent check on both PRs: the
remedy this gate printed was
https://docs.objectstack.ai/docs/permissions/permission-sets#access-depth--readscope--writescope-adr-0057-d1,and that is byte-for-byte the URL #9636 wrote. No baseline entry was ever added, so
nothing went stale when that PR landed — which was the whole reason for refusing one.
#9636 also made assertion 3 earn its place immediately. It added two absolute docs
links to
plugin-audit, one carrying a#fragment. That took the anchor assertion's livepopulation from 0 to 1 on the very next merge, and the gate verifies it. The census moved
from 149 links to 150, and from 10 resolvable
docs.objectstack.aiURLs to 12.Verification
Local gate union re-run on the merged head
94aeb1f3a, tree clean. The gate set wasre-derived after the merge rather than reused — the merge brought new gate scripts in,
and the re-derivation named three families the pre-merge derivation had not
(
check:changeset-gate-self-tests,check:objectui-changeset,check-adr-0087-registration); all three were run and are in the list.One gate is reasoned rather than run:
check:type-check-debtrefuses to--re-measurewithout a built closure and measures TypeScript debt across the ledgered packages. This
diff contains no TypeScript at all — seven markdown READMEs, one changeset, three
.mjsgate scripts,package.jsonandlint.yml— so it moves no number in that ledger.Its non-re-measure half,
check:type-check-coverage, passes above.Heavy work was serialized through
flock -E 99 -w 540 /tmp/os-heavy-verify.lock; twoqueue timeouts were hit and the intervals spent on the reverse-verification and this
write-up rather than idling.
Changeset
One
.changeset/*.mdcovering all seven packages atpatch. Each hasREADME.mdin itsfilesarray withprivateunset, so the README is part of what npm publishes —following the precedent of #9531, #9541 and #9636, which all shipped a changeset for
README-only changes.
Not addressed here
packages/plugins/plugin-audit/README.mdis untouched by this branch. Its link iscorrect on
mainbecause #9636 landed it there, and this branch inherits that through themerge rather than re-applying it.
The two loose
service-automationlink labels are out of scope by PM ruling and aretracked separately as #9668.
Generated by Claude Code