Compare commits

...
15 Commits
Author SHA1 Message Date
apairon f26f8c7802 feat(schema): add tool schema, demo and admin tool types
continuous-integration/drone/push Build is passing
2026-10-20 22:56:04 +00:00
apairon 439c8773e6 feat(schema): add custom tools config and runtime types
continuous-integration/drone/push Build is passing
2026-10-20 20:45:19 +00:00
apairon 21472bdc72 Schema: ngram.sizes auf 2–10 begrenzen, combinedCache ergänzen
continuous-integration/drone/push Build is passing
Die Gesamtabnahme fand zwei Lücken im Config-Vertrag:

- ngram.sizes erlaubte minimum 1 und hatte kein maximum; der Server akzeptiert
  nur 2–10 (collection_setup.go) — das Schema ließ also Configs durch, die beim
  Projektload scheitern.
- combinedCache fehlte komplett, obwohl Server (models.CombinedCacheConfig) und
  Doku den Block als Projekt-YAML führen; additionalProperties:false lehnte
  gültige, dokumentierte Configs ab.

Ergänzt in collection.schema.json und index.d.ts (CombinedCacheConfig mit
enabled/ttl/maxEntries). Die Demo-Collection ngram-stopwords.yml enthält jetzt
eine Combined-Config mit combinedCache, sodass validate:schemas den Block
abdeckt. Gegenprobe per AJV: sizes [1] und [11] werden abgelehnt, [2,3] und
combinedCache akzeptiert.
2026-10-19 22:34:56 +00:00
apairon 9e12a8e9c3 Schema: ngram.stopwords, queryFilter und inMemory ergänzen
continuous-integration/drone/push Build is passing
Der Config-Vertrag kannte nur sizes und normalize. Damit lehnte
`validate:schemas` jedes Projekt ab, das die bereits im Server vorhandenen
ngram-Optionen nutzt (additionalProperties: false), und die generierte
Schema-Referenz konnte sie nie zeigen.

Ergänzt in collection.schema.json und index.d.ts:

- stopwords: { presets?: string[], words?: string[] }
- queryFilter: { minMatchRatio?: 0..1 }
- inMemory: { enabled?, maxDocs?, maxMemoryMB? }

plus demo-api/collections/ngram-stopwords.yml als Validierungs-Fixture; ohne
Schema-Erweiterung schlägt validate:schemas dort mit "must NOT have additional
properties" fehl.
2026-10-19 20:48:10 +00:00
apairon 3bf8055539 docs(example): Mehrfach-Duration (1m30s) als echter Wert im embedding-Beispiel
continuous-integration/drone/push Build is passing
Damit übt npm test den (...)+-Zweig des duration-Musters aus statt ihn nur zu
kommentieren (Review-Nachbefund, low).
2026-10-17 14:08:39 +00:00
apairon 1233bd393d fix(schema): latencyWindow ist ein String, duration-Muster folgt der Go-Grammatik
Review-Nachbefunde am neuen embedding-Schema:
- latencyWindow war als integer/number deklariert, ist in Go aber ein String
  (ConfigEmbeddingGroup.OnSingleQuery.LatencyWindow, per Atoi geprüft) — Schema
  und index.d.ts auf digits-String (>0) umgestellt.
- duration-Pattern erlaubte nur eine Einheit und akzeptierte Unsinn wie "1ss":
  jetzt (Zahl+Einheit)+, damit auch 1m30s/1h1m/1.5h gelten und 1ss/8/abc fallen.
- Beispiel examples/server/embedding.yml übt latencyWindow und Mehrfach-Einheiten
  aus (npm test: 5 YAML gegen 17 Schemas, tsc --noEmit --strict grün).
2026-10-17 14:07:30 +00:00
apairon 5a45c4dd3f feat(types): Embedding-Abschnitt der Server-Config (Provider, Failover-Gruppen, goldener Pfad) + Status-Payloads
index.d.ts: EmbeddingProviderConfig/EmbeddingGroupConfig (onSingleQuery, onBulkWrite,
health), EmbeddingConfig für config.server().embedding (Roh-JSON von config.Server.
Embedding) sowie die Status-Payloads von GET …/admin/embedding/status (Projekt- und
Server-Scope, Zustände live|degraded|quarantined|offline, Breaker closed|open|half-open).
VectorConfig.provider dokumentiert jetzt, dass dort auch ein Gruppenname stehen darf.

schemas/config/embedding.schema.json: validierbares Schema des Server-Abschnitts
(Enums für Policies/Distributionen, Go-Duration-Muster, Pflichtfelder, keine
erfundenen Properties); Beispiel examples/server/embedding.yml, per
scripts/validate-schemas.mjs mitgeprüft (npm test: 5 YAML gegen 17 Schemas).

Quelle der Wahrheit: internal/config/server.go + internal/config/embedding_group.go
(tibi-server). Typecheck: tsc --noEmit --strict über index.d.ts grün.
2026-10-17 14:00:53 +00:00
LeanAgent 4c55f82446 feat(schema): meta.query in Collection-Meta + designGuide-Einrückung
continuous-integration/drone/push Build is passing
Hooks lesen oft zusätzliche Query-Parameter (ctx.request().query(name)).
Mit meta.query werden sie deklariert, für Agenten dokumentiert
(REST _meta/OpenAPI, MCP collection_describe) und bei MCP-Calls
validiert. Vergleiche docs 04-collections (meta.query).
2026-10-12 16:12:01 +00:00
LeanAgent ca684fb4b3 feat(schema): meta.agent in Collection- und Projekt-Schema
continuous-integration/drone/push Build is passing
Die Starter-Collections dokumentieren Editor-Workflows in meta.agent
(tibi-server spielt das ueber MCP/REST _meta aus), die Schemas kannten
den Schlüssel aber nicht – YAML-LS meldete 'Property agent is not
allowed'. agent ist bewusst freiform (additionalProperties: true),
damit die Doku nicht wieder hinterherhinkt.
2026-10-12 16:10:18 +00:00
apaironandClaude Opus 4.8 1934cf9b89 feat(schema): neues Section-Format — position: "section:<group>" + meta.sections
Feld-Meta position erlaubt jetzt section und section:<group> neben main/sidebar.
Collection-Meta bekommt sections: [{ group, label, collapsible, collapsed }] als
Pendant zu sidebar.

Ohne das Pattern-Update schlaegt die YAML-Validierung im Editor fehl und
position: "section:x" wird als ungueltig markiert.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-10-01 00:17:33 +00:00
apairon 7870253b73 merge: dynamic pagebuilder schemas (templateProps, schemaRef, designGuide, blockStudio)
# Conflicts:
#	schemas/config/collection-meta.schema.json
2026-09-30 20:37:08 +00:00
apairon 6720efbee2 feat(schema): collection meta exports (custom scripted exports)
Wiederhergestellt aus dem Arbeitsstand vor dem Dynamic-Blocks-Branch.
2026-09-30 20:36:46 +00:00
apairon 0fa213f3b6 feat(schema): templateProps + schemaRef + designGuide + blockStudio
Vorbereitung fuer dynamische Pagebuilder-Bloecke (nova feature/dynamic-pagebuilder-blocks):
- field-meta: Widget templateProps, meta.schemaRef {ref?, field required}
  (statische subFields / Sibling-Schema / FK-Schema fuer type:any-Felder)
- collection-meta: meta.designGuide (i18nText) als KI-Hinweis pro Collection
- collection-meta: viewHint.blockStudio {previewField, lint, renderer.file}
  (Spezial-View wie navigation/media, inkl. oneOf-Zweig)
2026-09-30 20:36:22 +00:00
apairon 19601b1f0f fix(schema): allow dashes in field names, i18n sections, number widget 2026-09-21 19:38:44 +00:00
apairon 1de0d67992 fix(schema): add cors to collection and action schemas 2026-09-21 16:47:03 +00:00
13 changed files with 1085 additions and 4 deletions

No files matched your search

+43
View File
@@ -0,0 +1,43 @@
name: ngram-stopwords
uploadPath: ../media/ngram-stopwords
permissions: !include global/test-permissions.yml
fields:
- name: title
type: string
meta:
label:
de: Titel
en: Title
widget: "text"
search:
- name: fuzzy
mode: ngram
fields: [title]
autoRegenerate: true
ngram:
sizes: [2, 3]
normalize:
use: [german-umlauts]
stopwords:
presets: [de, en]
words: [zum, zur]
queryFilter:
minMatchRatio: 0.4
inMemory:
enabled: true
maxDocs: 150000
maxMemoryMB: 512
- name: combined
mode: combined
rrf:
k: 60
topK: 100
weights:
fuzzy: 1.0
combinedCache:
enabled: true
ttl: 45s
maxEntries: 1000
+12
View File
@@ -4,6 +4,18 @@ namespace: myapi
meta:
# wird in der Admin-UI zum Projekt aufgegeben
imageUrl: https://source.unsplash.com/random/?internet
tools:
- name: demo-overview
label:
de: Demo-Übersicht
en: Demo overview
description:
de: Beispiel-Tool für die Projekt-Übersicht
en: Example project overview tool
icon: view-dashboard-outline
group: tools
module: _/assets/tools/demo-overview/index.js
capabilities: [db, ui, log]
# Liste aller möglichen Kollektionen (Listen, Seiten...) zum Projekt
collections:
+49
View File
@@ -0,0 +1,49 @@
# Beispiel für den embedding-Abschnitt der Server-config.yml
# (tibi-server, server.embedding) — validiert gegen schemas/config/embedding.schema.json.
providers:
- name: bge-m3-external
type: flagembedding
model: BAAI/bge-m3
dimensions: 1024
baseURL: https://embed.intern/embed
timeoutPerAttempt: 8s
slowThreshold: 1500ms
- name: bge-m3-local
type: native
model: BAAI/bge-m3
dimensions: 1024
modelPath: /models/bge-m3
timeoutPerAttempt: 60s
groups:
- name: bge-m3-main
description: Bevorzugt extern, sonst lokal, kehrt automatisch zurück.
members:
- bge-m3-external
- bge-m3-local
onSingleQuery:
policy: prefer-in-order
preferredOrder:
- bge-m3-external
- bge-m3-local
timeoutPerAttempt: 8s
timeoutTotal: 1m30s # Mehrfach-Einheiten sind erlaubt (Go time.ParseDuration)
autoFailback: true
retryAttempts: 1
retryBackoff: 200ms
retryBackoffMax: 2s
latencyWindow: "100"
onBulkWrite:
distribution: lowest-latency
maxConcurrentTotal: 6
maxConcurrentPerEndpoint: 2
timeoutPerAttempt: 15s
timeoutTotal: 120s
health:
checkInterval: 30s
checkTimeout: 3s
unhealthyAfterFailures: 3
healthyAfterSuccesses: 2
cooldownAfterUnhealthy: 60s
goldenPath: testdata/embedding-golden
Vendored
+265
View File
@@ -81,9 +81,33 @@ declare global {
eval?: string
}
export interface NgramStopwordsConfig {
presets?: string[]
words?: string[]
}
export interface NgramQueryFilterConfig {
minMatchRatio?: number
}
export interface CombinedCacheConfig {
enabled?: boolean
ttl?: string
maxEntries?: number
}
export interface NgramInMemoryConfig {
enabled?: boolean
maxDocs?: number
maxMemoryMB?: number
}
export interface NgramConfig {
sizes?: number[]
normalize?: NormalizeConfig
stopwords?: NgramStopwordsConfig
queryFilter?: NgramQueryFilterConfig
inMemory?: NgramInMemoryConfig
}
export interface RrfConfig {
@@ -109,6 +133,12 @@ declare global {
}
export interface VectorConfig {
/**
* Name of the embedding provider (server config: `embedding.providers`) or
* of an embedding failover group (`embedding.groups`). Groups route across
* their members (policy, breaker, bulk sharding) and look like one provider
* to the collection.
*/
provider: string
documentPrefix?: string
queryPrefix?: string
@@ -169,6 +199,8 @@ declare global {
filter?: { [key: string]: any }
/** Ngram search configuration */
ngram?: NgramConfig
/** Result cache for combined searches */
combinedCache?: CombinedCacheConfig
/** Regex search configuration */
regex?: RegexConfig
/** Custom scoring for eval/filter search */
@@ -561,6 +593,121 @@ declare global {
[key: string]: any
}
// ── Custom Tools (Admin) ─────────────────────────────────────────────────
/** Project config entry (`meta.tools[]`). */
export interface AdminToolConfig {
name: string
label?: string | { [lang: string]: string }
description?: string | { [lang: string]: string }
icon?: string
group?: string
module: string | { eval: string }
permissions?: string[]
capabilities?: Array<"db" | "files" | "xlsx" | "ui" | "log" | "progress" | "state">
settings?: Record<string, any>
hidden?: boolean
}
/** File handle handed to tools (data URL, no live Blob). */
export interface AdminToolFile {
src: string
name: string
type?: string
size?: number
}
/** Fail-fast batch result; no rollback. */
export interface AdminToolBatchResult {
ok: boolean
failedIndex?: number
error?: unknown
}
export interface AdminToolDb {
find(
collection: string,
opts?: Record<string, any>
): Promise<CollectionDocument[]>
count(collection: string, opts?: Record<string, any>): Promise<number>
loadAll(
collection: string,
opts?: Record<string, any>,
onProgress?: (done: number, total: number) => void
): Promise<CollectionDocument[]>
create(collection: string, data: Record<string, any>): Promise<CollectionDocument>
update(collection: string, id: string, data: Record<string, any>): Promise<Partial<CollectionDocument>>
delete(collection: string, id: string): Promise<void>
updateMany(collection: string, items: Array<{ id: string; data: Record<string, any> }>): Promise<AdminToolBatchResult>
deleteMany(collection: string, ids: string[]): Promise<AdminToolBatchResult>
}
export interface AdminToolUi {
supports(type: string): boolean
slot(el: HTMLElement, element: any): void
styles: CSSStyleSheet[]
registerWidget(type: string, factory: (...args: any[]) => any, opts?: { replace?: boolean }): () => void
render(el: HTMLElement, widget: Record<string, any>): void | (() => void)
field(opts: Record<string, any>): any
form(opts: Record<string, any>): any
[key: string]: any
}
export interface AdminToolI18n {
lang: string
languages: string[]
t(key: string, vars?: Record<string, unknown>): string
text(value: string | Record<string, string> | undefined | null, vars?: Record<string, unknown>): string
has(key: string): boolean
add(lang: string, messages: Record<string, unknown>): () => void
add(messages: Record<string, unknown>, lang?: string): () => void
extend(lang: string, messages: Record<string, unknown>): () => void
extend(messages: Record<string, unknown>, lang?: string): () => void
onChange(cb: (lang: string) => void): () => void
dispose?(): void
}
/** Browser context handed to a tool module. `context === ctx`. */
export interface AdminToolContext {
context: AdminToolContext
apiVersion: 1
uiVersion: 1
host: "tool" | "macro" | "server"
capabilities: string[]
project: ProjectInfo
tool: AdminToolConfig
auth: { id: string; username: string; role: number; permissions: string[] } | null
db: AdminToolDb
files: { get(input: File | Blob | string, name?: string): Promise<AdminToolFile> }
xlsx: any
state: Map<string, unknown>
lang: string
languages: string[]
i18n: AdminToolI18n
theme: { mode: "light" | "dark"; tokens: Record<string, string> }
log(message: string, details?: unknown): void
progress(done: number, total: number, label?: string): void
signal: AbortSignal
hostData: Record<string, any>
asset(specifier: string): string
ui: AdminToolUi
}
export interface AdminToolModule {
mount(
el: HTMLElement,
ctx: AdminToolContext
): void | (() => void) | Promise<void | (() => void)>
messages?: Record<string, Record<string, any>>
}
export interface AdminToolException {
status?: number
msg: string
json?: any
}
/** Represents a single audit log entry as received in audit.return hooks */
export interface AuditLogEntry {
id?: string
@@ -735,6 +882,122 @@ declare global {
job?: JobConfig
}
/**
* Embedding provider as exposed by `config.server()` — mirror of
* `ConfigEmbeddingProvider` (secrets and modelPath are stripped from the JSON).
*/
export interface EmbeddingProviderConfig {
name: string
/** "native" (ONNX in-process) or "flagembedding" (external HTTP endpoint) */
type: string
model?: string
dimensions?: number
baseURL?: string
documentPrefix?: string
queryPrefix?: string
/** flagembedding: HTTP bound; native: per-call bound (entry-only) */
timeoutPerAttempt?: string
/** Group-only metric knob (feeds lowest-latency routing) */
slowThreshold?: string
/** Weights/build tag; part of the model identity used by groups */
revision?: string
/** false = endpoint/model has no sparse vectors (native skips the sparse session) */
sparse?: boolean
}
/** Single-query routing of an embedding group (`onSingleQuery`). */
export interface EmbeddingOnSingleQueryConfig {
policy?:
| "prefer-in-order"
| "fastest-wins"
| "round-robin"
| "lowest-latency"
| "weighted"
/** Comma-separated names (YAML list is joined with commas before marshalling) */
preferredOrder?: string
timeoutPerAttempt?: string
timeoutTotal?: string
autoFailback?: boolean
raceDelay?: string
raceMaxFanout?: number
weights?: number[]
/** Digits as string (Go field is a string, validated with Atoi; must be > 0) */
latencyWindow?: string
retryAttempts?: number
retryBackoff?: string
retryBackoffMax?: string
hedgeAfterSlow?: string
}
/** Bulk-write sharding of an embedding group (`onBulkWrite`). */
export interface EmbeddingOnBulkWriteConfig {
distribution?: "round-robin" | "lowest-latency" | "weighted"
maxConcurrentTotal?: number
maxConcurrentPerEndpoint?: number
timeoutPerAttempt?: string
timeoutTotal?: string
}
/** Health/breaker timing of an embedding group (`health`). */
export interface EmbeddingHealthConfig {
/** > 0 also enables late join/recovery of quarantined members */
checkInterval?: string
checkTimeout?: string
unhealthyAfterFailures?: number
healthyAfterSuccesses?: number
cooldownAfterUnhealthy?: string
}
/** Embedding failover group as exposed by `config.server()`. */
export interface EmbeddingGroupConfig {
name: string
description?: string
members: string[]
onSingleQuery?: EmbeddingOnSingleQueryConfig
onBulkWrite?: EmbeddingOnBulkWriteConfig
health?: EmbeddingHealthConfig
}
/** Embedding section as exposed by `config.server()`. */
export interface EmbeddingConfig {
providers?: EmbeddingProviderConfig[]
groups?: EmbeddingGroupConfig[]
/** Directory of golden verification files (default testdata/embedding-golden) */
goldenPath?: string
}
/**
* Member state from `GET …/admin/embedding/status` (server scope keeps
* `lastError`, project scope redacts it).
*/
export interface EmbeddingMemberHealth {
state: "live" | "degraded" | "quarantined" | "offline"
breaker: "closed" | "open" | "half-open"
lastError?: string
latencyMs?: number
successes?: number
failures?: number
slowCount?: number
}
/** Group state from `GET …/admin/embedding/status`. */
export interface EmbeddingGroupHealth {
/** null while no member is live */
referenceMember: string | null
members: { [member: string]: EmbeddingMemberHealth }
}
/** Project-scope payload: `{ namespace, health: { groups } }`. */
export interface EmbeddingHealthProjectResponse {
namespace: string
health: { groups: { [group: string]: EmbeddingGroupHealth } }
}
/** Server-scope payload: `{ groups }`. */
export interface EmbeddingHealthServerResponse {
groups: { [group: string]: EmbeddingGroupHealth }
}
interface ConfigPackage {
/**
* get current namespace string
@@ -773,6 +1036,8 @@ declare global {
maxTokensPerRequest?: number
}[]
}
/** Raw JSON of config.Server.Embedding (apiKey/modelPath stripped, JSON tags decide) */
embedding?: EmbeddingConfig
audit?: {
enabled: boolean
defaultTTL: string
+40
View File
@@ -46,6 +46,46 @@
}
}
}
},
"cors": {
"type": "object",
"additionalProperties": false,
"properties": {
"merge": {
"type": "boolean"
},
"allowOrigins": {
"type": "array",
"items": {
"type": "string"
}
},
"allowCredentials": {
"type": "boolean"
},
"allowHeaders": {
"type": "array",
"items": {
"type": "string"
}
},
"allowMethods": {
"type": "array",
"items": {
"type": "string"
}
},
"exposeHeaders": {
"type": "array",
"items": {
"type": "string"
}
},
"maxAge": {
"type": "integer",
"minimum": 0
}
}
}
},
"required": [
+158
View File
@@ -17,6 +17,42 @@
"group": {
"type": "string"
},
"exports": {
"description": "Custom scripted exports (tibi-admin-nova ExportDialog). The script loads data itself via db.find/loadAll.",
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
},
"label": {
"$ref": "defs.schema.json#/definitions/i18nText"
},
"file": {
"oneOf": [
{
"type": "string"
},
{
"$ref": "defs.schema.json#/definitions/evalString"
}
]
},
"filename": {
"type": "string"
}
},
"required": [
"name",
"file"
]
}
},
"designGuide": {
"$ref": "defs.schema.json#/definitions/i18nText"
},
"imageUrl": {
"oneOf": [
{
@@ -287,6 +323,30 @@
}
}
}
},
"blockStudio": {
"type": "object",
"additionalProperties": false,
"properties": {
"previewField": {
"type": "string"
},
"lint": {
"type": "boolean"
},
"renderer": {
"type": "object",
"additionalProperties": false,
"properties": {
"file": {
"type": "string"
}
},
"required": [
"file"
]
}
}
}
},
"oneOf": [
@@ -299,6 +359,11 @@
"required": [
"navigation"
]
},
{
"required": [
"blockStudio"
]
}
]
}
@@ -373,8 +438,101 @@
]
}
},
"sections": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"group": {
"type": "string"
},
"label": {
"$ref": "defs.schema.json#/definitions/i18nText"
},
"collapsible": {
"type": "boolean"
},
"collapsed": {
"type": "boolean"
}
},
"required": [
"group"
]
}
},
"openapi": {
"$ref": "openapi.schema.json"
},
"query": {
"description": "Additional query parameters hooks may read (ctx.request().query(name)). Documented for agents (REST _meta/OpenAPI, MCP collection_describe + extra argument) and validated on MCP calls. Credential-like keys (permission token names, auth headers) and core query keys are never allowed.",
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": {
"type": "string",
"enum": [
"string",
"number",
"boolean"
]
},
"enum": {
"type": "array",
"items": {
"type": [
"string",
"number",
"boolean"
]
}
},
"maxLength": {
"type": "integer",
"minimum": 1
},
"description": {
"$ref": "defs.schema.json#/definitions/i18nText"
}
}
}
},
"agent": {
"description": "Editor- und Agenten-Dokumentation (Zweck, Workflows, Gotchas). Freiform-Doku, wird u.a. ueber MCP/REST _meta ausgespielt.",
"type": "object",
"additionalProperties": true,
"properties": {
"purpose": {
"type": "string"
},
"instructions": {
"type": "string"
},
"fieldHints": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"examples": {
"type": "array"
},
"workflows": {
"type": "array",
"items": {
"type": "string"
}
},
"hints": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
+104 -1
View File
@@ -144,6 +144,46 @@
}
}
},
"cors": {
"type": "object",
"additionalProperties": false,
"properties": {
"merge": {
"type": "boolean"
},
"allowOrigins": {
"type": "array",
"items": {
"type": "string"
}
},
"allowCredentials": {
"type": "boolean"
},
"allowHeaders": {
"type": "array",
"items": {
"type": "string"
}
},
"allowMethods": {
"type": "array",
"items": {
"type": "string"
}
},
"exposeHeaders": {
"type": "array",
"items": {
"type": "string"
}
},
"maxAge": {
"type": "integer",
"minimum": 0
}
}
},
"queryLimits": {
"type": "object",
"additionalProperties": false,
@@ -281,7 +321,8 @@
"type": "array",
"items": {
"type": "integer",
"minimum": 1
"minimum": 2,
"maximum": 10
}
},
"normalize": {
@@ -298,6 +339,68 @@
"type": "string"
}
}
},
"stopwords": {
"type": "object",
"additionalProperties": false,
"properties": {
"presets": {
"type": "array",
"items": {
"type": "string"
}
},
"words": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"queryFilter": {
"type": "object",
"additionalProperties": false,
"properties": {
"minMatchRatio": {
"type": "number",
"minimum": 0,
"maximum": 1
}
}
},
"inMemory": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean"
},
"maxDocs": {
"type": "integer",
"minimum": 0
},
"maxMemoryMB": {
"type": "integer",
"minimum": 0
}
}
}
}
},
"combinedCache": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean"
},
"ttl": {
"type": "string"
},
"maxEntries": {
"type": "integer",
"minimum": 0
}
}
},
+255
View File
@@ -0,0 +1,255 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "embedding.schema.json",
"title": "Tibi server config — embedding section (server-level, not project config)",
"description": "Der Abschnitt `embedding` aus der Server-config.yml (server.embedding): Provider + Failover-Gruppen + Gold-Datei-Pfad. Quelle der Wahrheit: internal/config/server.go und internal/config/embedding_group.go im tibi-server.",
"type": "object",
"additionalProperties": false,
"properties": {
"providers": {
"type": "array",
"items": {
"$ref": "#/definitions/provider"
}
},
"groups": {
"type": "array",
"items": {
"$ref": "#/definitions/group"
}
},
"goldenPath": {
"type": "string",
"description": "Verzeichnis der Gold-Dateien für die Mitglieds-Prüfung (Vorgabe: testdata/embedding-golden relativ zum Arbeitsverzeichnis). Erzeugen mit cmd/embed-probe-golden. Ohne verankerte Gold-Datei prüft die Gruppe nur gegen ihre Referenz."
}
},
"definitions": {
"duration": {
"type": "string",
"description": "Go-Duration (time.ParseDuration), z. B. 120s, 800ms, 25s, 1m30s, 1.5h",
"pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$"
},
"provider": {
"type": "object",
"additionalProperties": false,
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Provider-Name; Sammlungen referenzieren ihn (oder eine Gruppe) über vector.provider."
},
"type": {
"type": "string",
"enum": [
"native",
"flagembedding"
],
"description": "native = ONNX im Prozess (braucht -tags cgo_embedding), flagembedding = externer HTTP-Endpunkt. Ohne Angabe: native."
},
"model": {
"type": "string"
},
"modelPath": {
"type": "string",
"description": "ONNX-Modellverzeichnis (type: native)."
},
"dimensions": {
"type": "integer",
"minimum": 1
},
"baseURL": {
"type": "string",
"description": "Embedding-Endpunkt (type: flagembedding)."
},
"apiKey": {
"type": "string"
},
"documentPrefix": {
"type": "string"
},
"queryPrefix": {
"type": "string"
},
"timeoutPerAttempt": {
"$ref": "#/definitions/duration",
"description": "flagembedding: HTTP-Timeout (Vorgabe 120s); native: Schranke je Embed-Aufruf (nur am Aufruf-/Stufen-Eintritt wirksam, ONNX-Runs sind nicht unterbrechbar). Ungültige oder nicht-positive Werte werden ignoriert (Warnung im Log)."
},
"slowThreshold": {
"$ref": "#/definitions/duration",
"description": "Nur gruppenwirksam: langsamere Aufrufe zählen für lowest-latency als langsam."
},
"revision": {
"type": "string",
"description": "Gewichts-/Build-Tag (Datei-Hash, Quantisierung); fließt in die Modell-Identität ein (Gruppen-Prüfung, Query-Cache)."
},
"sparse": {
"type": "boolean",
"description": "false = Endpunkt/Modell liefert keine Sparse-Vektoren: native lädt die Sparse-Session nicht, flagembedding schweigt die einmalige Dense-only-Warnung; in einer Gruppe lehnt die Prüfung abweichende Sparse-Formen ab."
}
}
},
"group": {
"type": "object",
"additionalProperties": false,
"required": [
"name",
"members"
],
"properties": {
"name": {
"type": "string",
"description": "Gruppenname; darf kein Provider-Name sein und darf nur einmal vorkommen."
},
"description": {
"type": "string"
},
"members": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "string"
},
"description": "Provider-Namen der Gruppe. Alle Mitglieder müssen dieselbe Modell-Identität liefern (Dimensionen, Sparse-Unterstützung, revision)."
},
"onSingleQuery": {
"type": "object",
"additionalProperties": false,
"properties": {
"policy": {
"type": "string",
"enum": [
"prefer-in-order",
"fastest-wins",
"round-robin",
"lowest-latency",
"weighted"
]
},
"preferredOrder": {
"description": "Bevorzugte Reihenfolge; als Komma-String oder YAML-Liste. Mitglieder, die fehlen, hängen in Config-Reihenfolge hinten an.",
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
},
"timeoutPerAttempt": {
"$ref": "#/definitions/duration",
"description": "Schranke je Versuch. Der bevorzugte Member läuft genau darin; Ausweich-Member bekommen max(Provider-Timeout, dieser Wert)."
},
"timeoutTotal": {
"$ref": "#/definitions/duration",
"description": "Gesamt-Deadline je Aufruf; muss >= timeoutPerAttempt sein."
},
"autoFailback": {
"type": "boolean",
"description": "true (Vorgabe) = jeder Aufruf beginnt wieder beim bevorzugten Mitglied; false = bleibt beim letzten Gewinner."
},
"raceDelay": {
"$ref": "#/definitions/duration",
"description": "Nur policy fastest-wins: Staffelung zwischen den Teilnehmern."
},
"raceMaxFanout": {
"type": "integer",
"minimum": 0,
"description": "Nur policy fastest-wins: maximale parallele Teilnehmer (0 = alle)."
},
"weights": {
"type": "array",
"items": {
"type": "integer",
"minimum": 0
},
"description": "Nur policy weighted: relative Gewichte je Mitglied, positional zur members-Liste."
},
"latencyWindow": {
"type": "string",
"pattern": "^[0-9]+$",
"description": "Fenster für die EWMA-Latenz (lowest-latency) als Ziffern-String (> 0); Go-Feld ist ein String, der per Atoi geprüft wird."
},
"retryAttempts": {
"type": "integer",
"minimum": 0
},
"retryBackoff": {
"$ref": "#/definitions/duration"
},
"retryBackoffMax": {
"$ref": "#/definitions/duration"
},
"hedgeAfterSlow": {
"$ref": "#/definitions/duration",
"description": "prefer-in-order: nach dieser Dauer starten die übrigen Mitglieder parallel (Hedge)."
}
}
},
"onBulkWrite": {
"type": "object",
"additionalProperties": false,
"properties": {
"distribution": {
"type": "string",
"enum": [
"round-robin",
"lowest-latency",
"weighted"
],
"description": "Verteilung der Batch-Ranges über die Mitglieder; ohne Angabe kein Sharding (serieller Schreibpfad). Jeder Range bleibt ganz auf einem Mitglied."
},
"maxConcurrentTotal": {
"type": "integer",
"minimum": 0
},
"maxConcurrentPerEndpoint": {
"type": "integer",
"minimum": 0,
"description": "Pro Mitglied; lokale (native) Mitglieder sind auf 1 begrenzt."
},
"timeoutPerAttempt": {
"$ref": "#/definitions/duration",
"description": "Bulk-Schranke je Versuch; pro Mitglied gilt max(Provider-Timeout, dieser Wert)."
},
"timeoutTotal": {
"$ref": "#/definitions/duration"
}
}
},
"health": {
"type": "object",
"additionalProperties": false,
"properties": {
"checkInterval": {
"$ref": "#/definitions/duration",
"description": "Abstand der Health-Probes. > 0 aktiviert zusätzlich den Late-Join: quarantänisierte Mitglieder (und offline Gruppen) werden gegen die aktuelle Referenz neu geprüft und kommen zurück, sobald sie passen. 0 = nur Neustart hilft."
},
"checkTimeout": {
"$ref": "#/definitions/duration",
"description": "Schranke je Probe-Embed; die Rundenschranke beim Start skaliert mit der Zahl der Probe-Texte."
},
"unhealthyAfterFailures": {
"type": "integer",
"minimum": 0
},
"healthyAfterSuccesses": {
"type": "integer",
"minimum": 0
},
"cooldownAfterUnhealthy": {
"$ref": "#/definitions/duration",
"description": "Pause, bevor ein offenes Mitglied wieder halb geöffnet wird (danach genügt Verkehr, kein Probe-Lauf)."
}
}
}
}
}
}
}
+19 -2
View File
@@ -16,6 +16,7 @@
"enum": [
"text",
"textarea",
"number",
"checkbox",
"switch",
"select",
@@ -34,6 +35,7 @@
"foreignMedia",
"pagebuilder",
"pageBuilder",
"templateProps",
"containerLessObject",
"containerLessObjectArray",
"color"
@@ -56,10 +58,10 @@
},
"position": {
"type": "string",
"pattern": "^(main|sidebar|sidebar:.+)$"
"pattern": "^(main|sidebar|section|sidebar:.+|section:.+)$"
},
"section": {
"type": "string"
"$ref": "defs.schema.json#/definitions/i18nText"
},
"containerProps": {
"type": "object",
@@ -298,6 +300,21 @@
}
}
},
"schemaRef": {
"type": "object",
"additionalProperties": false,
"properties": {
"ref": {
"type": "string"
},
"field": {
"type": "string"
}
},
"required": [
"field"
]
},
"openapi": {
"$ref": "openapi.schema.json#/definitions/fieldOpenapi"
}
+1 -1
View File
@@ -13,7 +13,7 @@
"properties": {
"name": {
"type": "string",
"pattern": "^[0-9A-Za-z_]+$"
"pattern": "^[0-9A-Za-z_-]+$"
},
"type": {
"type": "string",
+57
View File
@@ -65,6 +65,21 @@
]
}
},
"ui": {
"type": "object",
"additionalProperties": false,
"properties": {
"css": {
"type": "string"
}
}
},
"tools": {
"type": "array",
"items": {
"$ref": "tool.schema.json"
}
},
"i18n": {
"type": "object",
"additionalProperties": false,
@@ -89,6 +104,20 @@
"code"
]
}
},
"messages": {
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"additionalProperties": true
}
]
}
}
},
"required": [
@@ -98,6 +127,34 @@
},
"openapi": {
"$ref": "openapi.schema.json"
},
"agent": {
"description": "Projektweite Editor- und Agenten-Dokumentation (Routen, Lookups, Workflows, Gotchas). Freiform-Doku, wird u.a. ueber MCP/REST _meta ausgespielt.",
"type": "object",
"additionalProperties": true,
"properties": {
"description": {
"type": "string"
},
"frontend": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"workflows": {
"type": "array",
"items": {
"type": "string"
}
},
"hints": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
+79
View File
@@ -0,0 +1,79 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Custom Tool",
"description": "Project custom tool entry (meta.tools[]).",
"type": "object",
"additionalProperties": false,
"required": [
"name",
"module"
],
"properties": {
"name": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-_]*$"
},
"label": {
"oneOf": [
{
"type": "string"
},
{
"$ref": "defs.schema.json#/definitions/i18nText"
}
]
},
"description": {
"oneOf": [
{
"type": "string"
},
{
"$ref": "defs.schema.json#/definitions/i18nText"
}
]
},
"icon": {
"type": "string"
},
"group": {
"type": "string"
},
"module": {
"oneOf": [
{
"type": "string"
},
{
"$ref": "defs.schema.json#/definitions/evalString"
}
]
},
"permissions": {
"type": "array",
"items": {
"type": "string"
}
},
"capabilities": {
"type": "array",
"items": {
"enum": [
"db",
"files",
"xlsx",
"ui",
"log",
"progress",
"state"
]
}
},
"settings": {
"type": "object"
},
"hidden": {
"type": "boolean"
}
}
}
+3
View File
@@ -64,6 +64,9 @@ const validations = [
{ schema: "job.schema.json", pattern: "demo-api/jobs/*.y*ml" },
{ schema: "action.schema.json", pattern: "demo-api/actions/*.y*ml" },
{ schema: "asset.schema.json", pattern: "demo-api/assets/*.y*ml" },
// Server-level Abschnitt (config.yml des tibi-servers), Beispiel getrennt
// vom Projekt-Demo unter examples/server/:
{ schema: "embedding.schema.json", pattern: "examples/server/*.y*ml" },
]
let hasErrors = false