mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
fix(docs-i18n): protect split product links
This commit is contained in:
@@ -115,10 +115,12 @@ Add one `operator_approvals` table to the shared state database.
|
||||
|
||||
Required indexes:
|
||||
|
||||
- unique `(resolution_ref)`; inserts also reject cross-column `approval_id`/`resolution_ref` ambiguity
|
||||
- `(status, expires_at_ms)`
|
||||
- `(source_session_key, created_at_ms DESC)`
|
||||
- `(resolved_at_ms)` for retention pruning
|
||||
| Index | Purpose |
|
||||
| ------------------------------------------ | --------------------------------------------------------------------------- |
|
||||
| unique `(resolution_ref)` | Reject cross-column `approval_id`/`resolution_ref` ambiguity during insert. |
|
||||
| `(status, expires_at_ms)` | Find pending approvals and reconcile authoritative deadlines. |
|
||||
| `(source_session_key, created_at_ms DESC)` | Replay recent approvals for one source session. |
|
||||
| `(resolved_at_ms)` | Prune retained terminal approvals according to the fixed retention policy. |
|
||||
|
||||
Audience arrays are small and bounded. Session-filtered replay first selects visible pending rows through Kysely, then decodes and filters the bounded audience arrays in application code; it does not use string matching or raw SQL JSON queries.
|
||||
|
||||
|
||||
@@ -783,6 +783,40 @@ func TestMaskMarkdownDocSyntaxPreservesCanonicalNestedBackticks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaskMarkdownDocSyntaxProtectsProductLinkLabelsInsideRawHTML(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
source := strings.Join([]string{
|
||||
`<div className="maturity-category-docs">`,
|
||||
"",
|
||||
"Use `channel links`: [Discord](/channels/discord), [Render](https://render.com/docs), [Groups](/channels/groups).",
|
||||
"[Render](/guides/pre-render) the page first.",
|
||||
"",
|
||||
`</div>`,
|
||||
"",
|
||||
}, "\n")
|
||||
state := NewPlaceholderState(source)
|
||||
placeholders := []string{}
|
||||
mapping := map[string]string{}
|
||||
masked := maskMarkdownDocSyntax(source, state.Next, &placeholders, mapping)
|
||||
|
||||
if strings.Contains(masked, "[Discord]") {
|
||||
t.Fatalf("expected protected link label %q to be masked:\n%s", "Discord", masked)
|
||||
}
|
||||
if strings.Contains(masked, "[Render](https://render.com/docs)") {
|
||||
t.Fatalf("expected contextual product link label %q to be masked:\n%s", "Render", masked)
|
||||
}
|
||||
if !strings.Contains(masked, "[Groups]") {
|
||||
t.Fatalf("expected ordinary link label to remain translatable:\n%s", masked)
|
||||
}
|
||||
if !strings.Contains(masked, "[Render](/guides/pre-render)") {
|
||||
t.Fatalf("expected contextual ordinary-word label to remain translatable:\n%s", masked)
|
||||
}
|
||||
if restored := unmaskMarkdown(masked, placeholders, mapping); restored != source {
|
||||
t.Fatalf("protected link-label round trip changed source:\n%s\nwant:\n%s", restored, source)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDocBodyRejectsTranslatedInlineCode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -2384,8 +2418,8 @@ func TestProcessFileDocUsesFieldLevelFrontmatterTranslation(t *testing.T) {
|
||||
if !strings.Contains(text, "在 Fly.io 上部署 OpenClaw") {
|
||||
t.Fatalf("expected translated read_when entry in output:\n%s", text)
|
||||
}
|
||||
if !strings.Contains(text, "prompt_version: 29") {
|
||||
t.Fatalf("expected prompt version 29 in output metadata:\n%s", text)
|
||||
if !strings.Contains(text, "prompt_version: 30") {
|
||||
t.Fatalf("expected prompt version 30 in output metadata:\n%s", text)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ var (
|
||||
inlineCodeRe = regexp.MustCompile("`[^`]+`")
|
||||
angleLinkRe = regexp.MustCompile(`<https?://[^>]+>`)
|
||||
linkURLRe = regexp.MustCompile(`\[[^\]]*\]\(([^)]+)\)`)
|
||||
linkLabelRe = regexp.MustCompile(`!?\[([^\]\r\n]+)\]\(([^)\r\n]+)\)`)
|
||||
placeholderRe = regexp.MustCompile(`__OC_I18N_\d+__`)
|
||||
listMarkerRe = regexp.MustCompile(`^([ \t]*(?:>[ \t]*)*)([-+*]|[0-9]+[.)])([ \t]+)`)
|
||||
// Hard validation stays limited to low-ambiguity composite literals. Plain numbers remain
|
||||
@@ -83,6 +84,7 @@ func maskMarkdownDocSyntax(text string, nextPlaceholder func() string, placehold
|
||||
inlineRanges = append(inlineRanges, span)
|
||||
}
|
||||
}
|
||||
inlineRanges = append(inlineRanges, protectedMarkdownLinkLabelRanges(text)...)
|
||||
masked := maskByteRanges(text, inlineRanges, nextPlaceholder, placeholders, mapping)
|
||||
|
||||
listRanges := make([][2]int, 0)
|
||||
@@ -116,6 +118,34 @@ func maskMarkdownDocSyntax(text string, nextPlaceholder func() string, placehold
|
||||
return maskByteRanges(masked, listRanges, nextPlaceholder, placeholders, mapping)
|
||||
}
|
||||
|
||||
func protectedMarkdownLinkLabelRanges(text string) [][2]int {
|
||||
ranges := make([][2]int, 0)
|
||||
for _, match := range linkLabelRe.FindAllStringSubmatchIndex(text, -1) {
|
||||
if len(match) < 6 {
|
||||
continue
|
||||
}
|
||||
label := text[match[2]:match[3]]
|
||||
destination := markdownInlineLinkDestination(text[match[4]:match[5]])
|
||||
if isProtectedProductLinkLabel(label, destination) {
|
||||
ranges = append(ranges, [2]int{match[2], match[3]})
|
||||
}
|
||||
}
|
||||
return ranges
|
||||
}
|
||||
|
||||
func markdownInlineLinkDestination(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if strings.HasPrefix(value, "<") {
|
||||
if end := strings.IndexByte(value, '>'); end > 0 {
|
||||
return value[1:end]
|
||||
}
|
||||
}
|
||||
if fields := strings.Fields(value); len(fields) > 0 {
|
||||
return fields[0]
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func extractNumericValues(text string) []string {
|
||||
protocolRanges := make([][2]int, 0)
|
||||
for _, span := range placeholderRe.FindAllStringIndex(text, -1) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
const (
|
||||
workflowVersion = 16
|
||||
promptVersion = 29
|
||||
promptVersion = 30
|
||||
docsI18nEngineName = "codex"
|
||||
envDocsI18nProvider = "OPENCLAW_DOCS_I18N_PROVIDER"
|
||||
envDocsI18nModel = "OPENCLAW_DOCS_I18N_MODEL"
|
||||
|
||||
Reference in New Issue
Block a user