chore: release @alauda/doom - #349
Merged
Merged
Conversation
youyongsong
approved these changes
Aug 17, 2026
github-actions
Bot
force-pushed
the
changeset-release/main
branch
from
August 31, 2026 16:05
cb6b05a to
4aeae8b
Compare
github-actions Bot
added a commit
that referenced
this pull request
Aug 31, 2026
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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 free
to 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.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@alauda/doom@2.6.0
Minor Changes
#347
c6fe198Thanks @youyongsong! - Fix a family of API-reference rendering defects where the components ignored facts already present in the CRD / OpenAPI sources, plus add offline integrity tooling.Correctness — endpoints and schema now read the source instead of guessing:
<K8sAPI>derivesnamespacedfrom the CRD'sspec.scopeinstead of always defaulting totrue, soCluster-scoped resources no longer render an unreachable/namespaces/{namespace}/path. Thenamespacedprop still overrides.spec.names.pluralinstead of guessing withpluralize(kind), fixing hyphenated (vpc-egress-gateways) and irregular (alaudaloadbalancer2) plurals. A newpluralprop is an escape hatch for OpenAPI-sourced resources. (toLocaleLowerCase→toLowerCase.)apiVersion, a multi-version CRD now renders the versionkubectlresolves to — the highest-priorityservedversion (GA > beta > alpha, apimachinery ordering) — instead ofspec.versions[0]. This never publishes aserved: falseversion. Both the schema and the endpoint-path version now come from this single resolved version. Configurable viaapi.crdVersion: 'preferred' | 'storage' | 'first'.<K8sAPI>no longer renders endpoint paths it cannot derive. When an OpenAPI schema carries nox-kubernetes-group-version-kind— aggregation-layer documents routinely omit it — and no CRD backs the name, the group, version and kind used to fall back to empty strings and concatenate into/api//and/api///{name}, shipping a broken path on a green build. The endpoints section is now omitted, with aconsole.errornaming the props to declare; the schema still renders.apiVersion+apiKind(plusapiGroupoutside the core group) make the page render endpoints again./status(and the new/scale) endpoints follow what the source declares, not whether the schema happens to contain astatusproperty — fixing both fabricated/statusendpoints and missing ones. A CRD declares the subresource inspec.versions[].subresources; an OpenAPI document declares it by routing it, so itspathsdecide, and a document that routes the resource without a/statusroute renders none. A document that does not route the resource at all says nothing either way, so the schema property stays the fallback there. The newhasStatusprop overrides both.x-kubernetes-int-or-stringfields (carried underanyOf) now render their type (integer|string) instead of an empty cell.Anchors and badges:
.spec.foo[]) no longer collide with their parent (.spec.foo) on the same HTML id; schema headings use a page-level stateful slugger, so every property section is uniquely addressable (HTML id uniqueness / WCAG 4.1.1).summaryno longer produceid="undefined"/href="#undefined"or bare numeric anchors; the heading id is derived from the method (and summary when present).<OpenAPIPath>Request Body required badge readsrequestBody.required(the boolean on the request body) instead of the body schema's list of required properties.New configuration and tooling:
api.referencesaccepts an object form{ href, routePath?: string | false }, separating the link href from the page-identity key used to decide inline expansion. Plain string values are unchanged.routePath: falseexplicitly means "always link, never expand".translate.copyOnlyDirectoriesoverrides which directories are copied instead of translated (default unchanged).doom api checkcommand: an offline validation of the local CRD / OpenAPI sources — every file parses, CRDs have the right kind and a unique name, filenames follow the<group>_<plural>.yamlconvention, and OpenAPI definitions do not conflict across files.no-unresolved-api-reflint rule flags<K8sAPI>/<K8sCrd>/<OpenAPIRef>/<OpenAPIPath>/<K8sPermissionTable>references that cannot be resolved, before they ship as blank pages. It also flags a<K8sAPI>whose group, version and kind can be derived from neither the schema nor a CRD nor explicit props, so that failure surfaces at lint time instead of as a missing endpoints section.filepath/openapiPathmap key no longer depends onprocess.cwd(), so pinning a source is stable across working directories and<OpenAPIPath>uses a consistent first-match.Other:
<K8sPermissionTable>renders a visible "not found" row instead of silently dropping an unresolved function.Property,Type,Description,Required,Specification,API Endpoints,HTTP method,Common Parameters,Request Body,Response, …) is now translated viauseTranslation(en/zh/ru) instead of hardcoded English.#350
cac2679Thanks @youyongsong! - Translate in a checked loop instead of one call, and have a second reading judge the meaning.doom translateused to hand a document to the model once and write down whatever came back. Nothing read the result: a dropped bullet, a sentence that lost the product name, a page returned in the source language — all of it shipped, because the only gate downstream was whether the build could still resolve every link.Translating a document is now a bounded loop. The model writes, the same checks that
doom translate checkruns are applied to what it wrote, and any findings go back to it as its next instruction. The loop is bounded on purpose —maxRepairRoundsper document andmaxTurnsoverall — and exhausting either fails the document rather than shipping the best attempt so far. A failed document fails the whole run and nothing is uploaded, so a partial success cannot reach a translations repository. The final verdict is always the harness re-running the checks itself; the agent's own account of what it fixed is never taken as evidence.The agent works in a scratch directory it cannot escape, and it only ever sees masked content — the placeholders described above are never resolved inside the loop, so no repair round can rewrite a link or an identifier.
Structure is not meaning, so a second reading looks at the two documents side by side and reports what the translation lost, added or got wrong. It is the only check that can see a translation which is well-formed, passes every structural rule, and is about something else. Two independent readings are taken and only findings both agree on count, which is what keeps a single confident misreading out of the results.
Which model does that reading is now a choice:
translate.judge.modelin a site's config, orALAUDA_OPENAI_JUDGE_MODELfor a whole gateway;A reviewer that is not the writer is worth having, because two readings by one model share its blind spots and its preference for its own output; a different model does not. It is not free, though: the pass/fail line for the judge is a measured property of the model behind it — its false-positive rate on translations believed to be good, and its recall on damage deliberately introduced — so changing the model means measuring both again before trusting it. The shipped default was calibrated; another model is not, until someone calibrates it.
Reasoning levels are now read per model rather than per gateway. One endpoint can serve families that disagree about the vocabulary — one accepts
noneand rejectsminimal, the other does the reverse — and a single map for the gateway makes whichever family it was not written for fail at the ends of the scale.The previous single-call path is gone rather than kept behind a flag: there is one way translations are produced, and rolling back means releasing a previous version.
#350
cac2679Thanks @youyongsong! - Protect everything a translation model must not author, deterministically, instead of asking it not to touch things.doom translateused to send the whole document to the model with a detailed**CRITICAL**: Do not translate or modify ANY link contentinstruction, and only code blocks longer than 50 characters were actually protected. The instruction did not hold: translated documents came back with<ExternalSiteLink … />flattened into a markdown link,../virtualization/virtual_machine/collapsed into../virtualization_virtual_machine/,../global_dr.mdxrewritten as../global.dr.mdx,how_to/rewritten ashow-to/, and links generated twice. None of that is visible to a build unless the resulting link happens to be dead, so it shipped.Link targets, image sources, code (fenced and inline, with no length floor), MDX JSX attribute values,
href/srcin raw HTML, custom heading anchors, bare URLs, MDX expressions and reference/footnote labels are now replaced with opaque placeholders before the document reaches the model, and restored afterwards. The model never has the real value in its context, so it cannot rewrite it.Restoring verifies the round trip and throws
MaskIntegrityError— naming the file and each offending placeholder — when:Which JSX attributes carry prose is declared in
runtime/components/_translation-policy.ts—Directive.title,ExternalSiteLink.children,Tab.label,img.alt. Everything else is masked, so a component nobody has classified yet fails safe: an untranslated label is visible and harmless, whereas a rewritten identifier is silent and breaks the page.Masking only ever touches AST nodes; it never pattern-matches inside prose text, so it cannot swallow content that should have been translated. Verified over 1789 real documents (914 English sources, 875 Chinese translations): mask → restore reproduces the document byte-for-byte in 1788 of them, the one difference being a pre-existing non-idempotency in remark's own stringifier that reproduces with no masking at all.
#350
cac2679Thanks @youyongsong! - Review translations with a different model by default, and give the gateway a budget that can be set.The reviewer is no longer the translator. Two readings by one model share that model's blind spots, and a model asked to review what it just wrote prefers it; taking more draws fixes neither. The default reviewer is now
grok-4.6while the translator staysgpt-5.6.Measured on 40 held-out pairs neither model had been run against, both reviewing under the same prompt: injection recall identical at 18/20, and the false-positive rate no worse — 3 of 40 documents flagged against 4 of 40. Those two numbers are one document apart, and the same model swings that far between samples, so this is not a claim that one model reviews better. It is a claim that the failures are not shared, which is the property being bought. In the round that measured it, the new reviewer caught a heading where "Collect Evidence Before Escalation" had become "before a version upgrade" — on a page the old arrangement passed.
translate.judge.modelstill decides for one site, andALAUDA_OPENAI_JUDGE_MODELfor a whole gateway. Setting either to the translator's own id restores the previous behaviour.This requires the gateway to serve both models. One that does not will fail on the first review, naming the model — set
translate.judge.modelto the translator's id there.How hard the gateway is driven is now a setting, where it used to be two constants:
translate.concurrency/ALAUDA_OPENAI_CONCURRENCY— how many documents are translated at once, and how many model calls may be in flight. Defaults to 2, down from 10.translate.requestsPerMinute/ALAUDA_OPENAI_REQUESTS_PER_MINUTE— the budget in model requests a minute. It counts calls rather than documents, so the extra turns a repair round takes count against it. Defaults to 25, down from 50.Concurrency is one number rather than a pair that can drift apart, and a malformed value fails naming the variable instead of quietly falling back to the default: a run that is not the run that was asked for should say so.
The new defaults are deliberately lower than the old constants. Translating a corpus is not urgent, and a gateway shared with everything else is the resource worth protecting; a run that finishes later costs less than one that crowds out the rest of the platform.
#350
cac2679Thanks @youyongsong! - Check translations against the source they were made from, as lint rules, and adddoom translate checkto run those checks offline.Nothing has ever compared a translated document with its source. A translation could drop a bullet, rewrite a link into a different existing page, or come back verbatim in English, and every gate stayed green: the build only notices a link that resolves nowhere, and it notices it after the damaged file has already been committed back to the translations repository.
The new
translation-parityrules read two documents instead of one, paired exactly by thesourceSHAthe translator writes:translation-up-to-date— the pairing itself. Every other rule stands down unless this one is satisfied, and it says so out loud rather than skipping in silence.translation-link-isomorphism— every link resolved against the document holding it, language segment stripped, multisets compared. Resolving is what makes it possible at all: a translation's asset links legitimately read../../../en/networking/x.pngwhere the source reads./x.png, and both name the same file.translation-component-multiset— a component is a thing on the page, not a turn of phrase; translating never adds or removes one.translation-jsx-attribute-parity— component attributes are identifiers. Which ones are prose is declared once, inruntime/components/_translation-policy.ts, and is the same list the translator's masking uses.translation-echoed-source— the model handed back what it was given.translation-heading-sequence,translation-frontmatter-preservation,translation-length-ratio,translation-url-residue.doom translate check [root]runs them over translations that already exist, with no translation model involved — the offline way to survey which documents are damaged before deciding what is worth re-translating.Measured over a real corpus of 1768 translated documents: 14 findings, each one verified by hand, none of them false. They include three documents whose prose came back in English with only the frontmatter translated, a
<Term>wrapped in backticks so it renders as literal source, two hyperlinks dropped from a page, a list item merged away, and — from the source side — a document whose frontmatter is missing its opening---, soweight: 13renders as a heading.Two rules were cut back to get there, and both cases are recorded in the rules themselves: comparing
src/hrefas written reported every illustrated page, and matching anything shaped likename.tld/pathreported API groups and annotation keys.translation-terminology-adherenceships but is deliberately not in the default rule set: run against the shared terminology table it reports 490 problems on a corpus that is substantially correct, because that table is a glossary of preferences rather than a set of invariants.Patch Changes
#350
cac2679Thanks @youyongsong! - Keep the Chinese-typography rules on Chinese documents.remark-lint-match-punctuationandremark-lint-no-chinese-punctuation-in-numberpair and place CJK punctuation. They joined the rule set when the only documents anyone linted were Chinese; now that translations are linted too, they readpod’ами— which is how Russian declines a Latin word — as an unmatched quotation mark and report every page that does it.Measured over a real corpus of 1728 translated documents, that was 17 of 19 findings, and none of the 17 was a defect. Both rules now apply only to documents under a
zhdirectory. A document whose language cannot be told from its path keeps its messages: not knowing is a reason to report, not a reason to go quiet.#350
cac2679Thanks @youyongsong! - Stopdoom lintfrom silently discarding messages on a trailing self-closing element.Message control drops messages that fall in a "gap" — a region of the file not represented in the tree. It decided a node's span was covered by walking its children, but a self-closing MDX element (
<K8sAPI name="…" />) is a parent with an emptychildrenarray, so its end offset was never recorded. When such an element was the last node, everything from its start to the end of the file became a gap and every message inside it was thrown away.A canonical API reference page is exactly that shape — a heading followed by a single
<K8sAPI … />— so the rules written for those pages reported nothing at all, on a green build. Every rule's own spec passed throughout, because the specs did not run message control; alintMdxPipelinetest helper now does, and the regression case fails without the fix.Fixing that immediately surfaced a second defect it had been hiding:
no-unresolved-api-refreadmetadata.namestraight off each permission source, while the permission plugin builds its runtime module fromitems. Against akind: Listsource — whatkubectl get -o yamlproduces, and what this repository's own fixture is — the check therefore knew no function names at all and reported every<K8sPermissionTable>reference as unresolved. Both shapes are now read the way the runtime reads them.#350
cac2679Thanks @youyongsong! - Apply doom's lint rules to translated documents at all.doom lintreported success on a directory of translations having applied no rule to it. The remark config — which is what carries every doom lint rule, dead links included — was attached only to files under the source-language directory, sozh/andru/documents were parsed and then checked against nothing. Linting a translation and linting nothing produced the same output.The remark config now covers every language directory. Spell checking stays scoped to the source language, where its dictionaries belong.
The
translation-parityrules also now run beforecheck-dead-links, which rewrites link urls in place (.mdxto.html, language prefixes) as a side effect of asking rspress to resolve them. Comparing a rewritten translation against an unrewritten source reported 961 problems on a corpus that has one.Expect a first run over an existing repository to find things: these rules have never seen the translated half of it.