Initial project import
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
"""Summarize privacy-safe adapter JSONL telemetry.
|
||||
|
||||
Run inside the REST container or copy /data/adapter-audit.jsonl from it.
|
||||
No BSL text, SQL payload, or credentials are expected in the source log.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from collections import Counter, defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--log", default="/data/adapter-audit.jsonl")
|
||||
parser.add_argument("--slow-ms", type=int, default=5_000)
|
||||
parser.add_argument("--limit", type=int, default=20)
|
||||
args = parser.parse_args()
|
||||
path = Path(args.log)
|
||||
rows: list[dict] = []
|
||||
malformed_rows = 0
|
||||
if not path.exists():
|
||||
print(json.dumps({"schema": "onec_adapter_audit_summary.v1", "status": "log_not_found", "log": str(path)}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||
try:
|
||||
item = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
malformed_rows += 1
|
||||
continue
|
||||
if item.get("event") == "adapter_rpc":
|
||||
rows.append(item)
|
||||
by_base = Counter(str((row.get("request") or {}).get("base_id") or "<none>") for row in rows)
|
||||
exceptions = [row for row in rows if str(row.get("status") or "") == "exception" or row.get("error") == "request_exception"]
|
||||
rejected = [row for row in rows if str(row.get("status") or "") in {"blocked", "unsupported", "invalid_argument"}]
|
||||
slow = sorted((row for row in rows if int(row.get("duration_ms") or 0) >= args.slow_ms), key=lambda row: int(row.get("duration_ms") or 0), reverse=True)
|
||||
methods = Counter(str(row.get("method") or "<none>") for row in exceptions)
|
||||
print(json.dumps({
|
||||
"schema": "onec_adapter_audit_summary.v1",
|
||||
"events": len(rows),
|
||||
"malformed_rows": malformed_rows,
|
||||
"time_range": {"from": rows[0].get("time") if rows else None, "to": rows[-1].get("time") if rows else None},
|
||||
"bases": dict(by_base),
|
||||
"adapter_exceptions": len(exceptions),
|
||||
"expected_rejections": len(rejected),
|
||||
"exception_methods": dict(methods.most_common(args.limit)),
|
||||
"slow_threshold_ms": args.slow_ms,
|
||||
"slow": [
|
||||
{"time": row.get("time"), "base_id": (row.get("request") or {}).get("base_id"), "method": row.get("method"), "status": row.get("status"), "error": row.get("error"), "duration_ms": row.get("duration_ms"), "request_id": row.get("request_id")}
|
||||
for row in slow[:args.limit]
|
||||
],
|
||||
"recent_exceptions": [
|
||||
{"time": row.get("time"), "base_id": (row.get("request") or {}).get("base_id"), "method": row.get("method"), "error": row.get("error"), "exception_type": row.get("exception_type"), "duration_ms": row.get("duration_ms"), "request_id": row.get("request_id")}
|
||||
for row in exceptions[-args.limit:]
|
||||
],
|
||||
"findings": [
|
||||
*([{"priority": "P1", "kind": "adapter_exception", "count": len(exceptions), "next_action": "Inspect the matching REST request_id and exception_type; reproduce only on upo_test before changing code."}] if exceptions else []),
|
||||
*([{"priority": "P2", "kind": "slow_calls", "count": len(slow), "next_action": "Inspect timings_ms for the listed methods; optimise only after a repeated pattern is confirmed."}] if slow else []),
|
||||
*([{"priority": "P2", "kind": "malformed_audit_rows", "count": malformed_rows, "next_action": "Inspect log rotation and container shutdown events."}] if malformed_rows else []),
|
||||
],
|
||||
}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -204,7 +204,7 @@ def render_markdown(payload: dict[str, Any]) -> str:
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Analyze SQL MOXCEL merge-block row/size scalar bands against XML merge rows.")
|
||||
parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011")
|
||||
parser.add_argument("--adapter-url", default="http://docker.cin.su:8011")
|
||||
parser.add_argument("--base-id", default="upo_test")
|
||||
parser.add_argument("--owner-kind", default="Document")
|
||||
parser.add_argument("--owner-name", default="АвансовыйОтчет")
|
||||
|
||||
@@ -323,7 +323,7 @@ def render_markdown(payload: dict[str, Any]) -> str:
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Score SQL MOXCEL merge-block numeric slots against XML merge range fields.")
|
||||
parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011")
|
||||
parser.add_argument("--adapter-url", default="http://docker.cin.su:8011")
|
||||
parser.add_argument("--base-id", default="upo_test")
|
||||
parser.add_argument("--owner-kind", default="Document")
|
||||
parser.add_argument("--owner-name", default="АвансовыйОтчет")
|
||||
|
||||
@@ -17,7 +17,7 @@ from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
|
||||
|
||||
DEFAULT_BASE_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_BASE_URL = "http://docker.cin.su:8011"
|
||||
DEFAULT_BASE_ID = "upo_test"
|
||||
|
||||
# Metadata kinds that either own application data or expose values through the
|
||||
|
||||
@@ -279,7 +279,7 @@ def render_markdown(snapshot: dict[str, Any], diff: dict[str, Any] | None, previ
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Capture and diff a live 1C MOXCEL template probe snapshot.")
|
||||
parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011")
|
||||
parser.add_argument("--adapter-url", default="http://docker.cin.su:8011")
|
||||
parser.add_argument("--base-id", default="upo_test")
|
||||
parser.add_argument("--file-name", help="Explicit ConfigCAS file name. If omitted, use the newest MOXCEL payload.")
|
||||
parser.add_argument("--scan-limit", type=int, default=30)
|
||||
|
||||
@@ -10,7 +10,7 @@ from typing import Any
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_REST_ADAPTER_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_REST_ADAPTER_URL = "http://docker.cin.su:8011"
|
||||
DEFAULT_MCP_URL = "http://docker.cin.su:8021"
|
||||
SAVED_STATE_TABLES = ("ConfigSave", "ConfigCASSave")
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -509,11 +510,13 @@ def check_contract() -> dict[str, Any]:
|
||||
|
||||
calls: list[tuple[str, str, Any]] = []
|
||||
|
||||
def fake_http_json(method: str, path: str, body: Any | None = None, *, timeout: float | None = None) -> dict[str, Any]:
|
||||
def fake_http_json(method: str, path: str, body: Any | None = None, *, timeout: float | None = None, request_id: str | None = None) -> dict[str, Any]:
|
||||
calls.append((method, path, body))
|
||||
return {"status": "ok", "method": body.get("method") if isinstance(body, dict) else "health"}
|
||||
|
||||
original_http_json = adapter_mcp.http_json
|
||||
previous_diagnostic_mode = os.environ.get("ONEC_MCP_ALLOW_DIAGNOSTIC")
|
||||
os.environ["ONEC_MCP_ALLOW_DIAGNOSTIC"] = "true"
|
||||
adapter_mcp.http_json = fake_http_json
|
||||
try:
|
||||
calls.clear()
|
||||
@@ -562,6 +565,10 @@ def check_contract() -> dict[str, Any]:
|
||||
issues.append({"code": "mcp_rpc_body_method_mismatch", "method": method, "body": body})
|
||||
finally:
|
||||
adapter_mcp.http_json = original_http_json
|
||||
if previous_diagnostic_mode is None:
|
||||
os.environ.pop("ONEC_MCP_ALLOW_DIAGNOSTIC", None)
|
||||
else:
|
||||
os.environ["ONEC_MCP_ALLOW_DIAGNOSTIC"] = previous_diagnostic_mode
|
||||
|
||||
return {
|
||||
"schema": "onec_mcp_adapter_contract_check.v1",
|
||||
|
||||
@@ -389,7 +389,7 @@ def check_manifest(
|
||||
manifest_path: Path,
|
||||
*,
|
||||
live: bool = False,
|
||||
adapter_url: str = "http://docker-gpu.cin.su:8011",
|
||||
adapter_url: str = "http://docker.cin.su:8011",
|
||||
service_token: str = "",
|
||||
timeout: float = 90,
|
||||
base_overrides: dict[str, str] | None = None,
|
||||
@@ -453,7 +453,7 @@ def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Validate and optionally probe rare 1C metadata-kind fixtures.")
|
||||
parser.add_argument("--manifest", type=Path, default=Path("config/1c_metadata_kind_fixtures.json"))
|
||||
parser.add_argument("--live", action="store_true")
|
||||
parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011")
|
||||
parser.add_argument("--adapter-url", default="http://docker.cin.su:8011")
|
||||
parser.add_argument("--service-token-env", default="ONEC_ADAPTER_SERVICE_TOKEN")
|
||||
parser.add_argument("--timeout", type=float, default=90)
|
||||
parser.add_argument("--target-base", action="append", default=[], metavar="FIXTURE_ID=BASE_ID")
|
||||
|
||||
@@ -9,7 +9,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEFAULT_BASE_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_BASE_URL = "http://docker.cin.su:8011"
|
||||
DEFAULT_TABLES = ("ConfigCASSave", "ConfigSave")
|
||||
ALLOWED_TABLES = {"ConfigCASSave", "ConfigSave"}
|
||||
|
||||
|
||||
@@ -968,7 +968,10 @@ def validate_saved_state_module(
|
||||
if report.get("status") == "verified_and_rolled_back":
|
||||
if write_plan.get("allowed") is not True:
|
||||
failures.append({"code": "saved_state_module_write_plan_not_allowed", "path": str(path), "write_plan": write_plan})
|
||||
if write_plan.get("apply_method") != "metadata.module.write_apply":
|
||||
if write_plan.get("apply_method") not in {
|
||||
"metadata.module.write_apply",
|
||||
"form_embedded_module_handler_write_apply",
|
||||
}:
|
||||
failures.append({"code": "saved_state_module_apply_method_unexpected", "path": str(path), "write_plan": write_plan})
|
||||
if write_plan.get("target_kind") != "module":
|
||||
failures.append({"code": "saved_state_module_target_kind_unexpected", "path": str(path), "write_plan": write_plan})
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
param(
|
||||
[string]$RestDockerHost = "ssh://docker-gpu.cin.su",
|
||||
[string]$RestDockerHost = "ssh://docker.cin.su",
|
||||
[string]$McpDockerHost = "ssh://docker.cin.su",
|
||||
[string]$RestComposePath = "core/deploy/docker-gpu/adapter-1c/compose.yaml",
|
||||
[string]$RestComposePath = "core/deploy/docker/adapter-1c/compose.yaml",
|
||||
[string]$McpComposePath = "core/deploy/docker/adapter-1c-mcp/compose.yaml",
|
||||
[string]$RestEnvFile,
|
||||
[string]$McpEnvFile,
|
||||
[string]$RestServiceName = "adapter-1c-rest",
|
||||
[string]$RestAuditServiceName = "adapter-1c-audit",
|
||||
[string]$McpServiceName = "adapter-1c-mcp",
|
||||
[string]$McpAuditServiceName = "adapter-1c-mcp-audit",
|
||||
[string[]]$BaseId,
|
||||
[string]$AdapterUrl = "http://docker-gpu.cin.su:8011",
|
||||
[string]$AdapterUrl = "http://docker.cin.su:8011",
|
||||
[string]$McpUrl = "http://docker.cin.su:8021",
|
||||
[string]$ObjectRef,
|
||||
[string]$ObjectKind,
|
||||
@@ -19,6 +23,7 @@ param(
|
||||
[switch]$SkipRest,
|
||||
[switch]$SkipMcp,
|
||||
[switch]$SkipVerify,
|
||||
[switch]$SkipDrainCheck,
|
||||
[switch]$SkipWritePlanSafetySmoke,
|
||||
[switch]$SkipWriteRollbackSafetySmoke,
|
||||
[switch]$SkipSavedStateDiffSmoke,
|
||||
@@ -50,9 +55,13 @@ function Invoke-ComposeUp {
|
||||
[string]$Label,
|
||||
[string]$DockerHost,
|
||||
[string]$ComposePath,
|
||||
[string]$EnvFile,
|
||||
[string]$ServiceName
|
||||
)
|
||||
$command = @("docker", "--host", $DockerHost, "compose", "-f", $ComposePath, "up", "-d", "--no-deps")
|
||||
if ($EnvFile) {
|
||||
$command = @("docker", "--host", $DockerHost, "compose", "--env-file", $EnvFile, "-f", $ComposePath, "up", "-d", "--no-deps")
|
||||
}
|
||||
if (-not $NoBuild) {
|
||||
$command += "--build"
|
||||
}
|
||||
@@ -60,6 +69,34 @@ function Invoke-ComposeUp {
|
||||
Invoke-CheckedCommand -Label $Label -Command $command
|
||||
}
|
||||
|
||||
function Wait-RestAdapterIdle {
|
||||
if ($SkipDrainCheck) {
|
||||
Write-Host "[skip] REST drain check was explicitly skipped"
|
||||
return
|
||||
}
|
||||
$deadline = [DateTime]::UtcNow.AddSeconds($TimeoutSec)
|
||||
$lastIssue = ""
|
||||
while ([DateTime]::UtcNow -lt $deadline) {
|
||||
try {
|
||||
$health = Invoke-RestMethod -Method Get -Uri ($AdapterUrl.TrimEnd('/') + "/health") -TimeoutSec 10
|
||||
$runtime = $health.runtime
|
||||
if (-not $runtime) {
|
||||
Write-Warning "REST adapter is a legacy image without runtime drain telemetry; proceeding with this one transition deployment"
|
||||
return
|
||||
}
|
||||
if ($runtime -and $runtime.state -eq "ready" -and [int]$runtime.active_rpc_count -eq 0) {
|
||||
Write-Host "[ready] REST adapter has no active RPC calls"
|
||||
return
|
||||
}
|
||||
$lastIssue = "state=$($runtime.state) active_rpc_count=$($runtime.active_rpc_count)"
|
||||
} catch {
|
||||
$lastIssue = $_.Exception.Message
|
||||
}
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
throw "REST adapter did not become idle before deployment: $lastIssue. Re-run later or pass -SkipDrainCheck only after confirming no write is active."
|
||||
}
|
||||
|
||||
function Ensure-RestServiceToken {
|
||||
if ($env:ONEC_ADAPTER_SERVICE_TOKEN) {
|
||||
return
|
||||
@@ -135,15 +172,27 @@ try {
|
||||
$env:ONEC_ADAPTER_ALLOW_UNAUTHENTICATED_ADMIN = "true"
|
||||
}
|
||||
Ensure-RestServiceToken
|
||||
Wait-RestAdapterIdle
|
||||
Invoke-ComposeUp `
|
||||
-Label "Deploy REST adapter" `
|
||||
-DockerHost $RestDockerHost `
|
||||
-ComposePath $RestComposePath `
|
||||
-EnvFile $RestEnvFile `
|
||||
-ServiceName $RestServiceName
|
||||
Write-ContainerSummary `
|
||||
-Label "REST adapter container" `
|
||||
-DockerHost $RestDockerHost `
|
||||
-ServiceName $RestServiceName
|
||||
Invoke-ComposeUp `
|
||||
-Label "Deploy REST audit analyzer" `
|
||||
-DockerHost $RestDockerHost `
|
||||
-ComposePath $RestComposePath `
|
||||
-EnvFile $RestEnvFile `
|
||||
-ServiceName $RestAuditServiceName
|
||||
Write-ContainerSummary `
|
||||
-Label "REST audit analyzer container" `
|
||||
-DockerHost $RestDockerHost `
|
||||
-ServiceName $RestAuditServiceName
|
||||
}
|
||||
|
||||
if (-not $SkipMcp) {
|
||||
@@ -151,11 +200,22 @@ try {
|
||||
-Label "Deploy MCP proxy" `
|
||||
-DockerHost $McpDockerHost `
|
||||
-ComposePath $McpComposePath `
|
||||
-EnvFile $McpEnvFile `
|
||||
-ServiceName $McpServiceName
|
||||
Write-ContainerSummary `
|
||||
-Label "MCP proxy container" `
|
||||
-DockerHost $McpDockerHost `
|
||||
-ServiceName $McpServiceName
|
||||
Invoke-ComposeUp `
|
||||
-Label "Deploy MCP audit analyzer" `
|
||||
-DockerHost $McpDockerHost `
|
||||
-ComposePath $McpComposePath `
|
||||
-EnvFile $McpEnvFile `
|
||||
-ServiceName $McpAuditServiceName
|
||||
Write-ContainerSummary `
|
||||
-Label "MCP audit analyzer container" `
|
||||
-DockerHost $McpDockerHost `
|
||||
-ServiceName $McpAuditServiceName
|
||||
}
|
||||
|
||||
if (-not $SkipVerify) {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
param(
|
||||
[string]$DockerHost = "ssh://docker-gpu.cin.su",
|
||||
[string]$ComposeFile = "core/deploy/docker-gpu/embeddings/compose.yaml",
|
||||
[string]$EnvFile = "core/deploy/docker-gpu/embeddings/.env.example",
|
||||
[string]$BaseUrl = "http://docker-gpu.cin.su:8082",
|
||||
[string]$ExpectedModel = "qwen3-embedding-0.6b",
|
||||
[int]$WaitSeconds = 900,
|
||||
[switch]$ConfigOnly,
|
||||
[switch]$Pull,
|
||||
[switch]$Down
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if (-not (Test-Path -LiteralPath $ComposeFile)) {
|
||||
throw "Compose file not found: $ComposeFile"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $EnvFile)) {
|
||||
throw "Env file not found: $EnvFile"
|
||||
}
|
||||
|
||||
$composeArgs = @(
|
||||
"--host", $DockerHost,
|
||||
"compose",
|
||||
"--env-file", $EnvFile,
|
||||
"-f", $ComposeFile
|
||||
)
|
||||
|
||||
if ($ConfigOnly) {
|
||||
docker @composeArgs config
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
if ($Down) {
|
||||
docker @composeArgs down
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
if ($Pull) {
|
||||
docker @composeArgs pull
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
}
|
||||
|
||||
docker @composeArgs up -d
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
$deadline = [DateTimeOffset]::UtcNow.AddSeconds($WaitSeconds)
|
||||
$lastError = ""
|
||||
do {
|
||||
try {
|
||||
$health = Invoke-RestMethod -Method Get -Uri "$($BaseUrl.TrimEnd('/'))/health" -TimeoutSec 10
|
||||
if ($health.status -eq "ok") {
|
||||
$models = Invoke-RestMethod -Method Get -Uri "$($BaseUrl.TrimEnd('/'))/v1/models" -TimeoutSec 10
|
||||
$modelIds = @($models.data | ForEach-Object { $_.id })
|
||||
if ($modelIds -notcontains $ExpectedModel) {
|
||||
throw "Expected model '$ExpectedModel' is absent. Loaded: $($modelIds -join ', ')"
|
||||
}
|
||||
|
||||
$body = @{
|
||||
model = $ExpectedModel
|
||||
input = @("поиск процедуры проведения документа 1С")
|
||||
} | ConvertTo-Json -Depth 4
|
||||
$embedding = Invoke-RestMethod `
|
||||
-Method Post `
|
||||
-Uri "$($BaseUrl.TrimEnd('/'))/v1/embeddings" `
|
||||
-ContentType "application/json; charset=utf-8" `
|
||||
-Body ([Text.Encoding]::UTF8.GetBytes($body)) `
|
||||
-TimeoutSec 120
|
||||
$dimensions = @($embedding.data[0].embedding).Count
|
||||
if ($dimensions -le 0) {
|
||||
throw "Embedding endpoint returned an empty vector."
|
||||
}
|
||||
Write-Host "Embedding endpoint is ready: model=$ExpectedModel dimensions=$dimensions url=$BaseUrl"
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$lastError = $_.Exception.Message
|
||||
}
|
||||
Start-Sleep -Seconds 5
|
||||
} while ([DateTimeOffset]::UtcNow -lt $deadline)
|
||||
|
||||
docker @composeArgs logs --tail 100
|
||||
throw "Embedding endpoint did not become ready in $WaitSeconds seconds. Last error: $lastError"
|
||||
@@ -0,0 +1,231 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from embed_1c_semantic_cache import DEFAULT_ADAPTER_URL, adapter_call, batched, embedding_model_label
|
||||
from rag_embedding_providers import LOCAL_HASHING_MODEL, LOCAL_HASHING_PROVIDER, embed_texts, provider_metadata
|
||||
|
||||
|
||||
def code_embedding_model_label(*, provider: str, model: str, dimensions: int) -> str:
|
||||
label = embedding_model_label(provider=provider, model=model)
|
||||
normalized_provider = str(provider or "").strip().lower().replace("_", "-")
|
||||
if normalized_provider in {"openai-compatible", "openai"} and int(dimensions or 0) > 0:
|
||||
return f"{label}@d{int(dimensions)}"
|
||||
return label
|
||||
|
||||
|
||||
def embed_pending_code_vectors(
|
||||
*,
|
||||
adapter_url: str,
|
||||
base_id: str,
|
||||
limit: int = 100,
|
||||
batch_size: int = 16,
|
||||
embedding_provider: str = LOCAL_HASHING_PROVIDER,
|
||||
embedding_model: str = LOCAL_HASHING_MODEL,
|
||||
dimensions: int = 384,
|
||||
embedding_base_url: str = "",
|
||||
embedding_api_key_env: str = "OPENAI_API_KEY",
|
||||
chunk_kinds: tuple[str, ...] | list[str] = ("routine",),
|
||||
max_text_chars: int = 4000,
|
||||
dry_run: bool = False,
|
||||
timeout_seconds: int = 180,
|
||||
) -> dict[str, Any]:
|
||||
stored_model = code_embedding_model_label(
|
||||
provider=embedding_provider,
|
||||
model=embedding_model,
|
||||
dimensions=dimensions,
|
||||
)
|
||||
pending = adapter_call(
|
||||
adapter_url,
|
||||
"metadata.code_vector.pending",
|
||||
{
|
||||
"base_id": base_id,
|
||||
"embedding_model": stored_model,
|
||||
"limit": int(limit or 100),
|
||||
"chunk_kinds": list(dict.fromkeys(str(value).strip().lower() for value in chunk_kinds if str(value).strip())),
|
||||
"max_text_chars": int(max_text_chars),
|
||||
},
|
||||
timeout_seconds=timeout_seconds,
|
||||
)
|
||||
if pending.get("status") != "ok":
|
||||
return {
|
||||
"schema": "onec_code_vector_embedding_worker.v1",
|
||||
"status": pending.get("status") or "error",
|
||||
"error": pending.get("error"),
|
||||
"pending": pending,
|
||||
}
|
||||
chunks = [item for item in pending.get("chunks") or [] if isinstance(item, dict)]
|
||||
upserts: list[dict[str, Any]] = []
|
||||
skipped: list[dict[str, Any]] = []
|
||||
for batch in batched(chunks, max(int(batch_size or 1), 1)):
|
||||
texts = [str(item.get("text") or "") for item in batch]
|
||||
vectors = embed_texts(
|
||||
texts,
|
||||
provider=embedding_provider,
|
||||
model=embedding_model,
|
||||
dimensions=dimensions,
|
||||
base_url=embedding_base_url,
|
||||
api_key_env=embedding_api_key_env,
|
||||
timeout_seconds=timeout_seconds,
|
||||
)
|
||||
for item, vector in zip(batch, vectors):
|
||||
chunk_id = str(item.get("chunk_id") or "")
|
||||
text_sha1 = str(item.get("text_sha1") or "")
|
||||
if not chunk_id or not text_sha1 or not vector:
|
||||
skipped.append(
|
||||
{
|
||||
"chunk_id": chunk_id or None,
|
||||
"reason": "missing_chunk_id_text_sha1_or_embedding",
|
||||
}
|
||||
)
|
||||
continue
|
||||
if dry_run:
|
||||
upserts.append(
|
||||
{
|
||||
"status": "dry_run",
|
||||
"chunk_id": chunk_id,
|
||||
"text_sha1": text_sha1,
|
||||
"dimensions": len(vector),
|
||||
}
|
||||
)
|
||||
continue
|
||||
result = adapter_call(
|
||||
adapter_url,
|
||||
"metadata.code_vector.embedding.upsert",
|
||||
{
|
||||
"base_id": base_id,
|
||||
"chunk_id": chunk_id,
|
||||
"text_sha1": text_sha1,
|
||||
"embedding_model": stored_model,
|
||||
"embedding": vector,
|
||||
},
|
||||
timeout_seconds=timeout_seconds,
|
||||
)
|
||||
upserts.append(
|
||||
{
|
||||
"status": result.get("status"),
|
||||
"error": result.get("error"),
|
||||
"chunk_id": chunk_id,
|
||||
"text_sha1": text_sha1,
|
||||
"dimensions": result.get("dimensions") or len(vector),
|
||||
}
|
||||
)
|
||||
observed_dimensions = next(
|
||||
(
|
||||
int(item.get("dimensions") or 0)
|
||||
for item in upserts
|
||||
if int(item.get("dimensions") or 0) > 0
|
||||
),
|
||||
int(dimensions),
|
||||
)
|
||||
provider = provider_metadata(
|
||||
provider=embedding_provider,
|
||||
model=embedding_model,
|
||||
dimensions=observed_dimensions,
|
||||
base_url=embedding_base_url,
|
||||
)
|
||||
return {
|
||||
"schema": "onec_code_vector_embedding_worker.v1",
|
||||
"status": "ok",
|
||||
"base_id": base_id,
|
||||
"adapter_url": adapter_url,
|
||||
"dry_run": bool(dry_run),
|
||||
"embedding": {
|
||||
"provider": provider.get("embedding_provider"),
|
||||
"model": embedding_model,
|
||||
"stored_embedding_model": stored_model,
|
||||
"dimensions": observed_dimensions,
|
||||
"chunk_kinds": list(chunk_kinds),
|
||||
"max_text_chars": int(max_text_chars),
|
||||
},
|
||||
"counts": {
|
||||
"pending": len(chunks),
|
||||
"processed": len(upserts),
|
||||
"stored": len([item for item in upserts if item.get("status") == "ok"]),
|
||||
"conflicts": len([item for item in upserts if item.get("status") == "conflict"]),
|
||||
"skipped": len(skipped),
|
||||
"errors": len(
|
||||
[
|
||||
item
|
||||
for item in upserts
|
||||
if item.get("status") not in {"ok", "dry_run", "conflict"}
|
||||
]
|
||||
),
|
||||
},
|
||||
"upserts": upserts,
|
||||
**({"skipped": skipped} if skipped else {}),
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Embed pending BSL code chunks from the 1C adapter local code index."
|
||||
)
|
||||
parser.add_argument("--adapter-url", default=DEFAULT_ADAPTER_URL)
|
||||
parser.add_argument("--base-id", required=True)
|
||||
parser.add_argument("--limit", type=int, default=100)
|
||||
parser.add_argument("--batch-size", type=int, default=16)
|
||||
parser.add_argument(
|
||||
"--embedding-provider",
|
||||
default=LOCAL_HASHING_PROVIDER,
|
||||
choices=[LOCAL_HASHING_PROVIDER, "openai-compatible"],
|
||||
)
|
||||
parser.add_argument("--embedding-model", default=LOCAL_HASHING_MODEL)
|
||||
parser.add_argument("--dimensions", type=int, default=384)
|
||||
parser.add_argument("--embedding-base-url", default="")
|
||||
parser.add_argument("--embedding-api-key-env", default="OPENAI_API_KEY")
|
||||
parser.add_argument(
|
||||
"--chunk-kind",
|
||||
action="append",
|
||||
choices=["routine", "module"],
|
||||
default=None,
|
||||
help="Chunk kind to embed; repeat to include both. Defaults to routine.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--max-text-chars",
|
||||
type=int,
|
||||
default=4000,
|
||||
help="Skip oversized chunks in this pass. Defaults to 4000 characters.",
|
||||
)
|
||||
parser.add_argument("--timeout-seconds", type=int, default=180)
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
parser.add_argument("--json", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
result = embed_pending_code_vectors(
|
||||
adapter_url=args.adapter_url,
|
||||
base_id=args.base_id,
|
||||
limit=args.limit,
|
||||
batch_size=args.batch_size,
|
||||
embedding_provider=args.embedding_provider,
|
||||
embedding_model=args.embedding_model,
|
||||
dimensions=args.dimensions,
|
||||
embedding_base_url=args.embedding_base_url,
|
||||
embedding_api_key_env=args.embedding_api_key_env,
|
||||
chunk_kinds=tuple(args.chunk_kind or ["routine"]),
|
||||
max_text_chars=args.max_text_chars,
|
||||
dry_run=args.dry_run,
|
||||
timeout_seconds=args.timeout_seconds,
|
||||
)
|
||||
if args.json:
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
counts = result.get("counts") or {}
|
||||
print(
|
||||
"code vector embeddings: "
|
||||
f"pending={counts.get('pending')} processed={counts.get('processed')} "
|
||||
f"stored={counts.get('stored')} conflicts={counts.get('conflicts')} "
|
||||
f"errors={counts.get('errors')}"
|
||||
)
|
||||
return (
|
||||
0
|
||||
if result.get("status") == "ok"
|
||||
and int((result.get("counts") or {}).get("errors") or 0) == 0
|
||||
else 1
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -9,7 +9,7 @@ from typing import Any
|
||||
from rag_embedding_providers import LOCAL_HASHING_MODEL, LOCAL_HASHING_PROVIDER, embed_texts, provider_metadata
|
||||
|
||||
|
||||
DEFAULT_ADAPTER_URL = "http://docker-gpu.cin.su:8011/rpc"
|
||||
DEFAULT_ADAPTER_URL = "http://docker.cin.su:8011/rpc"
|
||||
|
||||
|
||||
def adapter_call(adapter_url: str, method: str, payload: dict[str, Any], *, timeout_seconds: int = 180) -> dict[str, Any]:
|
||||
|
||||
@@ -6,7 +6,7 @@ param(
|
||||
[string]$SqlPath = "reports/1c-sql/upo_test/prepare-saved-state-copy.sql",
|
||||
[string]$SqlPlanReport = "reports/1c-sql/upo_test/prepare-saved-state-copy-sql.json",
|
||||
[string]$PlanPath = "reports/1c-sql/upo_test/saved-state-copy-plan.json",
|
||||
[string]$BaseUrl = "http://docker-gpu.cin.su:8011",
|
||||
[string]$BaseUrl = "http://docker.cin.su:8011",
|
||||
[string]$ExpectedBaseId,
|
||||
[ValidateSet("ConfigSave", "ConfigCASSave")]
|
||||
[string]$ExpectedTargetTable = "ConfigSave",
|
||||
|
||||
@@ -13,7 +13,7 @@ from typing import Any
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_BASE_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_BASE_URL = "http://docker.cin.su:8011"
|
||||
DEFAULT_REPORT_ROOT = ROOT / "reports" / "1c-access"
|
||||
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ def render_markdown(payload: dict[str, Any]) -> str:
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Inventory templates across live 1C configuration objects.")
|
||||
parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011")
|
||||
parser.add_argument("--adapter-url", default="http://docker.cin.su:8011")
|
||||
parser.add_argument("--base-id", default="upo_test")
|
||||
parser.add_argument("--kind", action="append", dest="kinds", help="Repeatable metadata kind filter.")
|
||||
parser.add_argument("--page-size", type=int, default=200)
|
||||
|
||||
@@ -134,7 +134,7 @@ def render_markdown(payload: dict[str, Any]) -> str:
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Inventory recent 1C template payload candidates from ConfigCAS.")
|
||||
parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011")
|
||||
parser.add_argument("--adapter-url", default="http://docker.cin.su:8011")
|
||||
parser.add_argument("--base-id", default="upo_test")
|
||||
parser.add_argument("--scan-limit", type=int, default=200)
|
||||
parser.add_argument("--max-cells", type=int, default=200)
|
||||
|
||||
@@ -9,7 +9,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEFAULT_BASE_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_BASE_URL = "http://docker.cin.su:8011"
|
||||
DISCOVERY_KINDS = ("Catalog", "Document", "DataProcessor", "Report")
|
||||
SOURCE_TABLES = {"Config", "ConfigCAS"}
|
||||
TARGET_TABLES = {"ConfigSave", "ConfigCASSave"}
|
||||
|
||||
@@ -160,7 +160,7 @@ def render_markdown(payload: dict[str, Any]) -> str:
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Profile extension common template payloads from live SQL ConfigCAS.")
|
||||
parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011")
|
||||
parser.add_argument("--adapter-url", default="http://docker.cin.su:8011")
|
||||
parser.add_argument("--base-id", default="upo_test")
|
||||
parser.add_argument("--extension", required=True)
|
||||
parser.add_argument("--query", default="t_MOXEL")
|
||||
|
||||
@@ -268,7 +268,7 @@ def render_markdown(profile: dict[str, Any]) -> str:
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Profile decoded 1C managed forms and highlight decoder gaps.")
|
||||
parser.add_argument("--input-json", type=Path, help="Existing metadata.object.form.details JSON.")
|
||||
parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011")
|
||||
parser.add_argument("--adapter-url", default="http://docker.cin.su:8011")
|
||||
parser.add_argument("--base-id", default="upo_test")
|
||||
parser.add_argument("--kind")
|
||||
parser.add_argument("--name")
|
||||
|
||||
@@ -314,7 +314,7 @@ def main() -> int:
|
||||
"--inventory-json",
|
||||
default=str(Path("Z:/codex/LLM/reports/1c-template-baselines/upo_test_configuration_tabular_templates.json")),
|
||||
)
|
||||
parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011")
|
||||
parser.add_argument("--adapter-url", default="http://docker.cin.su:8011")
|
||||
parser.add_argument("--base-id", default="upo_test")
|
||||
parser.add_argument("--workers", type=int, default=8)
|
||||
parser.add_argument("--timeout-seconds", type=int, default=120)
|
||||
|
||||
@@ -41,12 +41,16 @@ def embed_texts_openai_compatible(
|
||||
*,
|
||||
model: str,
|
||||
base_url: str,
|
||||
dimensions: int = 0,
|
||||
api_key: str = "",
|
||||
timeout_seconds: int = 120,
|
||||
) -> list[list[float]]:
|
||||
if not model:
|
||||
raise ValueError("embedding_model is required")
|
||||
body = json.dumps({"model": model, "input": texts}, ensure_ascii=False).encode("utf-8")
|
||||
request_payload: dict[str, Any] = {"model": model, "input": texts}
|
||||
if int(dimensions or 0) > 0:
|
||||
request_payload["dimensions"] = int(dimensions)
|
||||
body = json.dumps(request_payload, ensure_ascii=False).encode("utf-8")
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if api_key:
|
||||
headers["Authorization"] = f"Bearer {api_key}"
|
||||
@@ -67,6 +71,13 @@ def embed_texts_openai_compatible(
|
||||
if not isinstance(embedding, list) or not embedding:
|
||||
raise ValueError("Embedding response item has no embedding[]")
|
||||
vector = [float(value) for value in embedding]
|
||||
if int(dimensions or 0) > 0:
|
||||
if len(vector) < int(dimensions):
|
||||
raise ValueError(
|
||||
f"Embedding response returned {len(vector)} dimensions, "
|
||||
f"fewer than requested {int(dimensions)}"
|
||||
)
|
||||
vector = vector[: int(dimensions)]
|
||||
by_index[int(index)] = l2_normalize(vector)
|
||||
vectors = [by_index[index] for index in range(len(texts)) if index in by_index]
|
||||
if len(vectors) != len(texts):
|
||||
@@ -96,6 +107,7 @@ def embed_texts(
|
||||
texts,
|
||||
model=model,
|
||||
base_url=base_url,
|
||||
dimensions=dimensions,
|
||||
api_key=api_key,
|
||||
timeout_seconds=timeout_seconds,
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ import urllib.request
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEFAULT_ADAPTER_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_ADAPTER_URL = "http://docker.cin.su:8011"
|
||||
|
||||
for stream in (sys.stdout, sys.stderr):
|
||||
if hasattr(stream, "reconfigure"):
|
||||
|
||||
@@ -11,7 +11,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEFAULT_BASE_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_BASE_URL = "http://docker.cin.su:8011"
|
||||
DEFAULT_BASE_ID = "upo_test"
|
||||
DEFAULT_TABLE = "ConfigCASSave"
|
||||
DEFAULT_FILE_NAME = "f96a0c45-3eff-11f1-8287-005056b0d483__25c39fbf-35a4-4b43-8e3f-cd1f91082c88.0"
|
||||
|
||||
@@ -84,7 +84,8 @@ while ($listener.IsListening) {
|
||||
$context = $listener.GetContext()
|
||||
try {
|
||||
if ($context.Request.HttpMethod -eq 'GET' -and $context.Request.Url.AbsolutePath -eq '/healthz') { Send-Json $context 200 @{status='ok';service='onec-repository-runner'}; continue }
|
||||
if ($context.Request.HttpMethod -ne 'POST' -or $context.Request.Url.AbsolutePath -ne '/repository/execute') { Send-Json $context 404 @{status='not_found'}; continue }
|
||||
$requestPath = $context.Request.Url.AbsolutePath
|
||||
if ($context.Request.HttpMethod -ne 'POST' -or $requestPath -notin @('/repository/execute', '/configuration/activation/debug')) { Send-Json $context 404 @{status='not_found'}; continue }
|
||||
$expected = [Environment]::GetEnvironmentVariable('ONEC_REPOSITORY_RUNNER_TOKEN', 'Process')
|
||||
if (-not $expected) { $expected = [Environment]::GetEnvironmentVariable('ONEC_REPOSITORY_RUNNER_TOKEN', 'Machine') }
|
||||
if (-not $expected -or $context.Request.Headers['Authorization'] -ne "Bearer $expected") { Send-Json $context 401 @{status='unauthorized'}; continue }
|
||||
@@ -93,6 +94,71 @@ while ($listener.IsListening) {
|
||||
$all = Get-Content -LiteralPath $ConfigPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
$base = $all.([string]$payload.base_id)
|
||||
if (-not $base -or -not $base.repository) { Send-Json $context 400 @{status='not_configured'}; continue }
|
||||
if ($requestPath -eq '/configuration/activation/debug') {
|
||||
$layer = if ($payload.layer) { [string]$payload.layer } else { 'all' }
|
||||
$mode = if ($payload.mode) { ([string]$payload.mode).ToLowerInvariant() } else { 'debug' }
|
||||
$requestId = [string]$payload.request_id
|
||||
$fingerprint = ([string]$payload.fingerprint).ToLowerInvariant()
|
||||
if ($layer -notin @('all', 'base_saved_state', 'extension_saved_state') -or $mode -ne 'debug') {
|
||||
Send-Json $context 400 @{status='invalid_request';message='a supported layer and mode=debug are required'}
|
||||
continue
|
||||
}
|
||||
if ([bool]$requestId -ne [bool]$fingerprint -or ($requestId -and ($requestId -notmatch '^actreq-[0-9a-fA-F]{32}$' -or $fingerprint -notmatch '^[0-9a-f]{64}$'))) {
|
||||
Send-Json $context 400 @{status='invalid_request';message='request_id and a 64-hex fingerprint must be supplied together'}
|
||||
continue
|
||||
}
|
||||
$config = $base.repository
|
||||
$selectorCount = 0
|
||||
foreach ($selectorKey in @('file', 'server', 'name')) {
|
||||
if ([string]$config.infobase.$selectorKey) { $selectorCount++ }
|
||||
}
|
||||
$designerConfigured = [bool]([string]$config.designer_path)
|
||||
$designerAvailable = $designerConfigured -and (Test-Path -LiteralPath ([string]$config.designer_path) -PathType Leaf)
|
||||
$selectorConfigured = $selectorCount -eq 1
|
||||
$ready = $designerAvailable -and $selectorConfigured
|
||||
$debugAcceptance = $null
|
||||
if ($requestId) {
|
||||
$receiptText = "base_id=$([string]$payload.base_id)`nlayer=$layer`nrequest_id=$requestId`nfingerprint=$fingerprint`nmode=debug"
|
||||
$receiptBytes = [Text.Encoding]::UTF8.GetBytes($receiptText)
|
||||
$receiptHash = [Security.Cryptography.SHA256]::Create()
|
||||
try {
|
||||
$receipt = if ($ready) { -join ($receiptHash.ComputeHash($receiptBytes) | ForEach-Object { $_.ToString('x2') }) } else { $null }
|
||||
} finally { $receiptHash.Dispose() }
|
||||
$debugAcceptance = @{
|
||||
accepted=$ready
|
||||
request_id=$requestId
|
||||
fingerprint=$fingerprint
|
||||
receipt=$receipt
|
||||
}
|
||||
}
|
||||
$result = @{
|
||||
schema='onec_configuration_activation_runner_probe.v1'
|
||||
status=$(if ($ready) {'ready'} else {'not_ready'})
|
||||
base_id=[string]$payload.base_id
|
||||
layer=$layer
|
||||
runner=@{
|
||||
kind='local'
|
||||
reachable=$true
|
||||
designer_path_configured=$designerConfigured
|
||||
designer_available=$designerAvailable
|
||||
infobase_selector_configured=$selectorConfigured
|
||||
}
|
||||
operation=@{
|
||||
kind=$(if ($layer -eq 'base_saved_state') {'/UpdateDBCfg'} else {$null})
|
||||
execution_supported=$false
|
||||
extension_manual_only=$layer -in @('all', 'extension_saved_state')
|
||||
}
|
||||
debug_acceptance=$debugAcceptance
|
||||
execution=@{
|
||||
mode='debug'
|
||||
performed=$false
|
||||
designer_started=$false
|
||||
active_configuration_changed=$false
|
||||
}
|
||||
}
|
||||
Send-Json $context 200 $result
|
||||
continue
|
||||
}
|
||||
$result = Invoke-RepositoryAction $base.repository $payload
|
||||
Send-Json $context $(if ($result.status -eq 'ok') {200} else {409}) $result
|
||||
} catch { Send-Json $context 500 @{status='runner_error';message=$_.Exception.Message} }
|
||||
|
||||
@@ -49,7 +49,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
self._json(404, {"status": "not_found"})
|
||||
|
||||
def do_POST(self) -> None: # noqa: N802
|
||||
if self.path != "/repository/execute":
|
||||
if self.path not in {"/repository/execute", "/configuration/activation/debug"}:
|
||||
self._json(404, {"status": "not_found"})
|
||||
return
|
||||
if not authorized(self.headers.get("Authorization", "")):
|
||||
@@ -66,6 +66,61 @@ class Handler(BaseHTTPRequestHandler):
|
||||
self._json(400, {"status": "invalid_request", "message": str(exc)})
|
||||
return
|
||||
base_id = str(payload.get("base_id") or "").strip()
|
||||
if self.path == "/configuration/activation/debug":
|
||||
layer = str(payload.get("layer") or "all").strip()
|
||||
mode = str(payload.get("mode") or "debug").strip().casefold()
|
||||
request_id = str(payload.get("request_id") or "").strip()
|
||||
fingerprint = str(payload.get("fingerprint") or "").strip().casefold()
|
||||
if not base_id or layer not in {"all", "base_saved_state", "extension_saved_state"} or mode != "debug":
|
||||
self._json(
|
||||
400,
|
||||
{
|
||||
"status": "invalid_request",
|
||||
"message": "base_id, a supported layer, and mode=debug are required",
|
||||
},
|
||||
)
|
||||
return
|
||||
if bool(request_id) != bool(fingerprint) or (
|
||||
request_id
|
||||
and (
|
||||
not request_id.startswith("actreq-")
|
||||
or len(request_id) != len("actreq-") + 32
|
||||
or any(char not in "0123456789abcdef" for char in request_id[len("actreq-"):].casefold())
|
||||
or len(fingerprint) != 64
|
||||
or any(char not in "0123456789abcdef" for char in fingerprint)
|
||||
)
|
||||
):
|
||||
self._json(
|
||||
400,
|
||||
{
|
||||
"status": "invalid_request",
|
||||
"message": "request_id and a 64-hex fingerprint must be supplied together",
|
||||
},
|
||||
)
|
||||
return
|
||||
config, error = repository_control.repository_config(base_id)
|
||||
if error:
|
||||
self._json(400, error)
|
||||
return
|
||||
if (config.get("runner") or {}).get("kind") != "local":
|
||||
self._json(
|
||||
400,
|
||||
{
|
||||
"status": "invalid_config",
|
||||
"message": "Windows runner base configuration must use runner.kind=local",
|
||||
},
|
||||
)
|
||||
return
|
||||
result = repository_control.activation_debug_probe(
|
||||
base_id,
|
||||
config,
|
||||
layer=layer,
|
||||
timeout_seconds=10,
|
||||
request_id=request_id,
|
||||
fingerprint=fingerprint,
|
||||
)
|
||||
self._json(200 if result.get("status") in {"ready", "not_ready"} else 409, result)
|
||||
return
|
||||
action = str(payload.get("action") or "").strip().casefold()
|
||||
objects = payload.get("objects") or []
|
||||
if not base_id or action not in {"report", "lock", "unlock", "commit"}:
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from embed_1c_code_vectors import code_embedding_model_label, embed_pending_code_vectors
|
||||
from embed_1c_semantic_cache import DEFAULT_ADAPTER_URL, adapter_call
|
||||
from rag_embedding_providers import LOCAL_HASHING_MODEL, LOCAL_HASHING_PROVIDER, embed_texts, provider_metadata
|
||||
|
||||
|
||||
DEFAULT_QWEN3_CODE_RETRIEVAL_INSTRUCTION = (
|
||||
"Given a natural-language software task, retrieve the relevant 1C Enterprise "
|
||||
"BSL source-code fragment that implements or explains it"
|
||||
)
|
||||
|
||||
|
||||
def prepare_query_embedding_text(
|
||||
query: str,
|
||||
*,
|
||||
embedding_model: str,
|
||||
query_instruction: str | None,
|
||||
) -> tuple[str, str]:
|
||||
instruction = query_instruction
|
||||
if instruction is None and "qwen3-embedding" in str(embedding_model or "").strip().lower():
|
||||
instruction = DEFAULT_QWEN3_CODE_RETRIEVAL_INSTRUCTION
|
||||
clean_instruction = str(instruction or "").strip()
|
||||
if not clean_instruction:
|
||||
return query, ""
|
||||
return f"Instruct: {clean_instruction}\nQuery:{query}", clean_instruction
|
||||
|
||||
|
||||
def search_code_vectors(
|
||||
*,
|
||||
adapter_url: str,
|
||||
base_id: str,
|
||||
query: str,
|
||||
limit: int = 10,
|
||||
scan_limit: int = 2000,
|
||||
embedding_provider: str = LOCAL_HASHING_PROVIDER,
|
||||
embedding_model: str = LOCAL_HASHING_MODEL,
|
||||
dimensions: int = 384,
|
||||
embedding_base_url: str = "",
|
||||
embedding_api_key_env: str = "OPENAI_API_KEY",
|
||||
query_instruction: str | None = None,
|
||||
embed_pending: bool = False,
|
||||
embed_limit: int = 100,
|
||||
embed_batch_size: int = 16,
|
||||
timeout_seconds: int = 180,
|
||||
) -> dict[str, Any]:
|
||||
refresh = None
|
||||
if embed_pending:
|
||||
refresh = embed_pending_code_vectors(
|
||||
adapter_url=adapter_url,
|
||||
base_id=base_id,
|
||||
limit=embed_limit,
|
||||
batch_size=embed_batch_size,
|
||||
embedding_provider=embedding_provider,
|
||||
embedding_model=embedding_model,
|
||||
dimensions=dimensions,
|
||||
embedding_base_url=embedding_base_url,
|
||||
embedding_api_key_env=embedding_api_key_env,
|
||||
timeout_seconds=timeout_seconds,
|
||||
)
|
||||
query_embedding_text, applied_instruction = prepare_query_embedding_text(
|
||||
query,
|
||||
embedding_model=embedding_model,
|
||||
query_instruction=query_instruction,
|
||||
)
|
||||
query_embedding = embed_texts(
|
||||
[query_embedding_text],
|
||||
provider=embedding_provider,
|
||||
model=embedding_model,
|
||||
dimensions=dimensions,
|
||||
base_url=embedding_base_url,
|
||||
api_key_env=embedding_api_key_env,
|
||||
timeout_seconds=timeout_seconds,
|
||||
)[0]
|
||||
stored_model = code_embedding_model_label(
|
||||
provider=embedding_provider,
|
||||
model=embedding_model,
|
||||
dimensions=len(query_embedding),
|
||||
)
|
||||
result = adapter_call(
|
||||
adapter_url,
|
||||
"metadata.code_vector.search",
|
||||
{
|
||||
"base_id": base_id,
|
||||
"query": query,
|
||||
"query_embedding": query_embedding,
|
||||
"embedding_model": stored_model,
|
||||
"limit": limit,
|
||||
"scan_limit": scan_limit,
|
||||
"verify": True,
|
||||
"strict": True,
|
||||
},
|
||||
timeout_seconds=timeout_seconds,
|
||||
)
|
||||
provider = provider_metadata(
|
||||
provider=embedding_provider,
|
||||
model=embedding_model,
|
||||
dimensions=len(query_embedding),
|
||||
base_url=embedding_base_url,
|
||||
)
|
||||
result["client_embedding"] = {
|
||||
"provider": provider.get("embedding_provider"),
|
||||
"model": embedding_model,
|
||||
"stored_embedding_model": stored_model,
|
||||
"dimensions": len(query_embedding),
|
||||
"query_instruction": applied_instruction or None,
|
||||
}
|
||||
if refresh is not None:
|
||||
result["embedding_refresh"] = {
|
||||
"status": refresh.get("status"),
|
||||
"counts": refresh.get("counts") or {},
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Search BSL code with a local or OpenAI-compatible embedding model."
|
||||
)
|
||||
parser.add_argument("query")
|
||||
parser.add_argument("--adapter-url", default=DEFAULT_ADAPTER_URL)
|
||||
parser.add_argument("--base-id", required=True)
|
||||
parser.add_argument("--limit", type=int, default=10)
|
||||
parser.add_argument("--scan-limit", type=int, default=2000)
|
||||
parser.add_argument(
|
||||
"--embedding-provider",
|
||||
default=LOCAL_HASHING_PROVIDER,
|
||||
choices=[LOCAL_HASHING_PROVIDER, "openai-compatible"],
|
||||
)
|
||||
parser.add_argument("--embedding-model", default=LOCAL_HASHING_MODEL)
|
||||
parser.add_argument("--dimensions", type=int, default=384)
|
||||
parser.add_argument("--embedding-base-url", default="")
|
||||
parser.add_argument("--embedding-api-key-env", default="OPENAI_API_KEY")
|
||||
parser.add_argument(
|
||||
"--query-instruction",
|
||||
default=None,
|
||||
help=(
|
||||
"Instruction prepended only to the query embedding. "
|
||||
"Qwen3 Embedding gets a 1C-code retrieval instruction automatically; "
|
||||
"pass an empty value to disable it."
|
||||
),
|
||||
)
|
||||
parser.add_argument("--embed-pending", action="store_true")
|
||||
parser.add_argument("--embed-limit", type=int, default=100)
|
||||
parser.add_argument("--embed-batch-size", type=int, default=16)
|
||||
parser.add_argument("--timeout-seconds", type=int, default=180)
|
||||
parser.add_argument("--json", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
result = search_code_vectors(
|
||||
adapter_url=args.adapter_url,
|
||||
base_id=args.base_id,
|
||||
query=args.query,
|
||||
limit=args.limit,
|
||||
scan_limit=args.scan_limit,
|
||||
embedding_provider=args.embedding_provider,
|
||||
embedding_model=args.embedding_model,
|
||||
dimensions=args.dimensions,
|
||||
embedding_base_url=args.embedding_base_url,
|
||||
embedding_api_key_env=args.embedding_api_key_env,
|
||||
query_instruction=args.query_instruction,
|
||||
embed_pending=args.embed_pending,
|
||||
embed_limit=args.embed_limit,
|
||||
embed_batch_size=args.embed_batch_size,
|
||||
timeout_seconds=args.timeout_seconds,
|
||||
)
|
||||
if args.json:
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
print(
|
||||
f"status={result.get('status')} matches={len(result.get('matches') or [])} "
|
||||
f"model={(result.get('client_embedding') or {}).get('stored_embedding_model')}"
|
||||
)
|
||||
for position, match in enumerate(result.get("matches") or [], start=1):
|
||||
print(
|
||||
f"{position}. score={float(match.get('score') or 0):.4f} "
|
||||
f"object={match.get('object_ref')} routine={(match.get('chunk') or {}).get('routine_name')}"
|
||||
)
|
||||
return 0 if result.get("status") in {"ok", "not_found"} else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -10,7 +10,7 @@ from typing import Any
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_BASE_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_BASE_URL = "http://docker.cin.su:8011"
|
||||
DEFAULT_REF = "РегистрСведений.УОП_АктуальныеСпецификации"
|
||||
DEFAULT_REPORT = ROOT / "reports" / "1c-access" / "upo_test-access-object.json"
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import Any
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_BASE_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_BASE_URL = "http://docker.cin.su:8011"
|
||||
DEFAULT_REPORT = ROOT / "reports" / "1c-access" / "upo_test-access-snapshot-bsp.json"
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import urllib.request
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEFAULT_ADAPTER_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_ADAPTER_URL = "http://docker.cin.su:8011"
|
||||
DEFAULT_MCP_URL = "http://docker.cin.su:8021"
|
||||
EXPECTED_CONTRACT_VERSION = "onec-selector-contract.v1"
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Controlled public smoke for extension ConfigCASSave preparation.
|
||||
|
||||
The smoke uses no storage coordinates. It creates one extension saved-state
|
||||
copy, verifies readback, rolls it back by opaque receipt, then proves that the
|
||||
same public selector is immediately ready for another prepare.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
|
||||
def rpc(base_url: str, method: str, payload: dict, timeout: float) -> dict:
|
||||
request = Request(
|
||||
base_url.rstrip("/") + "/rpc",
|
||||
data=json.dumps({"method": method, "payload": payload}, ensure_ascii=False).encode("utf-8"),
|
||||
method="POST",
|
||||
headers={"Content-Type": "application/json; charset=utf-8"},
|
||||
)
|
||||
try:
|
||||
with urlopen(request, timeout=timeout) as response:
|
||||
result = json.loads(response.read().decode("utf-8"))
|
||||
except (HTTPError, URLError) as exc:
|
||||
raise AssertionError(f"{method} transport failure: {exc}") from exc
|
||||
if not isinstance(result, dict):
|
||||
raise AssertionError(f"{method} returned a non-object response")
|
||||
return result
|
||||
|
||||
|
||||
def require(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
raise AssertionError(message)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Public extension saved-state prepare/rollback smoke.")
|
||||
parser.add_argument("--base-url", default="http://docker.cin.su:8011")
|
||||
parser.add_argument("--base-id", default="upo_test")
|
||||
parser.add_argument("--extension", default="фс_ДоработкиОбщее")
|
||||
parser.add_argument("--ref", default="Catalog.Номенклатура")
|
||||
parser.add_argument("--timeout", type=float, default=120.0)
|
||||
parser.add_argument("--report", type=Path)
|
||||
parser.add_argument("--apply", action="store_true", help="Perform the controlled SQL prepare and rollback.")
|
||||
args = parser.parse_args()
|
||||
report: dict = {
|
||||
"schema": "onec_extension_saved_state_prepare_smoke.v1",
|
||||
"base_url": args.base_url,
|
||||
"base_id": args.base_id,
|
||||
"extension": args.extension,
|
||||
"ref": args.ref,
|
||||
"status": "pending",
|
||||
"passed": False,
|
||||
}
|
||||
target = {
|
||||
"base_id": args.base_id,
|
||||
"extension": args.extension,
|
||||
"ref": args.ref,
|
||||
"layer": "extension_saved_state",
|
||||
}
|
||||
try:
|
||||
initial = rpc(args.base_url, "metadata.saved_state.prepare", target | {"mode": "plan"}, args.timeout)
|
||||
report["initial_plan"] = {"status": initial.get("status"), "counts": initial.get("counts")}
|
||||
require(initial.get("status") == "plan_ready", f"initial plan must be plan_ready, got {initial.get('status')}")
|
||||
if not args.apply:
|
||||
report["status"] = "plan_ready"
|
||||
report["passed"] = True
|
||||
else:
|
||||
applied = rpc(
|
||||
args.base_url,
|
||||
"metadata.saved_state.prepare",
|
||||
target | {"mode": "apply_and_verify", "allow_sql_saved_state_prepare": True},
|
||||
args.timeout,
|
||||
)
|
||||
report["prepare"] = {"status": applied.get("status"), "counts": applied.get("counts"), "verification": applied.get("verification")}
|
||||
require(applied.get("status") == "verified" and applied.get("applied") is True, "prepare must be verified")
|
||||
receipt_id = str(applied.get("prepare_receipt_id") or "")
|
||||
require(receipt_id, "prepare response must include an opaque receipt")
|
||||
rolled_back = rpc(
|
||||
args.base_url,
|
||||
"metadata.saved_state.ensure.rollback",
|
||||
{"base_id": args.base_id, "prepare_receipt_id": receipt_id, "allow_sql_saved_state_rollback": True},
|
||||
args.timeout,
|
||||
)
|
||||
report["rollback"] = {"status": rolled_back.get("status"), "counts": rolled_back.get("counts")}
|
||||
require(rolled_back.get("status") == "rolled_back" and rolled_back.get("applied") is True, "prepare rollback must succeed")
|
||||
started = time.monotonic()
|
||||
final_plan = rpc(args.base_url, "metadata.saved_state.prepare", target | {"mode": "plan"}, args.timeout)
|
||||
elapsed_ms = round((time.monotonic() - started) * 1000, 1)
|
||||
report["post_rollback_plan"] = {"status": final_plan.get("status"), "counts": final_plan.get("counts"), "elapsed_ms": elapsed_ms}
|
||||
require(final_plan.get("status") == "plan_ready", f"post-rollback plan must be plan_ready, got {final_plan.get('status')}")
|
||||
require(int((final_plan.get("counts") or {}).get("existing_saved_records") or 0) == 0, "rollback must leave no saved records")
|
||||
report["status"] = "verified_and_rolled_back"
|
||||
report["passed"] = True
|
||||
except Exception as exc:
|
||||
report["status"] = "failed"
|
||||
report["error"] = str(exc)
|
||||
if args.report:
|
||||
args.report.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.report.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2))
|
||||
return 0 if report["passed"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -38,6 +38,9 @@ LIVE_HELP_SELECTOR_METHODS = (
|
||||
WORKING_STATE_METHODS = {
|
||||
"extension.objects.find",
|
||||
"metadata.resolve_overrides",
|
||||
"metadata.object.forms",
|
||||
"metadata.object.form.details",
|
||||
"metadata.form.decode",
|
||||
"modules.search",
|
||||
"code.search",
|
||||
}
|
||||
@@ -863,8 +866,16 @@ def build_live_report(
|
||||
)
|
||||
if write_plan.get("allowed") is not True:
|
||||
issues.append({"code": "live_composed_write_plan_not_allowed", "status": write_plan.get("status"), "problems": write_plan.get("problems")})
|
||||
if route.get("apply_method") != "metadata.module.write_apply":
|
||||
if route.get("apply_method") not in {"metadata.module.write_apply", "code.write"}:
|
||||
issues.append({"code": "live_composed_write_plan_apply_method_mismatch", "actual": route.get("apply_method")})
|
||||
if hint.get("method") and hint.get("method") != route.get("apply_method"):
|
||||
issues.append(
|
||||
{
|
||||
"code": "live_composed_write_plan_hint_method_mismatch",
|
||||
"route_method": route.get("apply_method"),
|
||||
"hint_method": hint.get("method"),
|
||||
}
|
||||
)
|
||||
if hint.get("ready_for_apply_method") is not True:
|
||||
issues.append({"code": "live_composed_write_plan_hint_not_ready", "hint": hint})
|
||||
if write_plan_target.get("module_ref") and hint_payload.get("module_ref") != write_plan_target.get("module_ref"):
|
||||
@@ -1107,6 +1118,45 @@ def selector_chain_examples() -> list[dict[str, Any]]:
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "report_to_separate_form_description",
|
||||
"steps": [
|
||||
{
|
||||
"tool": "onec_request",
|
||||
"method": "metadata.object.forms",
|
||||
"payload": {
|
||||
"base_id": "<base_id-from-project-context>",
|
||||
"ref": "Report.<metadata-object-name>",
|
||||
"source_state": "working",
|
||||
},
|
||||
"next": "Forms are references of the report. Select a returned public form name; do not treat the report card as the form description.",
|
||||
},
|
||||
{
|
||||
"tool": "onec_request",
|
||||
"method": "metadata.object.form.details",
|
||||
"payload": {
|
||||
"base_id": "<base_id-from-project-context>",
|
||||
"ref": "Report.<metadata-object-name>",
|
||||
"form": "<form-name-returned-by-metadata.object.forms>",
|
||||
"source_state": "working",
|
||||
"include_parameters": True,
|
||||
},
|
||||
"next": "Read the separate form description: attributes, parameters, commands, items, and form module summary. A command has no module; its handler is a routine in this form module when decoded evidence provides the link.",
|
||||
},
|
||||
{
|
||||
"tool": "onec_request",
|
||||
"method": "metadata.form.decode",
|
||||
"payload": {
|
||||
"base_id": "<base_id-from-project-context>",
|
||||
"ref": "Report.<metadata-object-name>",
|
||||
"form": "<form-name-returned-by-metadata.object.forms>",
|
||||
"source_state": "working",
|
||||
"view": "structure",
|
||||
},
|
||||
"next": "Use the static structure projection only. Parent/child edges remain unresolved unless the adapter reports proven codec evidence.",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -1180,7 +1230,7 @@ def main() -> int:
|
||||
parser.add_argument("--no-report", action="store_true", help="Do not write a report file.")
|
||||
parser.add_argument("--live", action="store_true", help="Run optional live adapter smoke through /rpc.")
|
||||
parser.add_argument("--transport", choices=("rest", "mcp"), default="rest", help="Live smoke transport.")
|
||||
parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011", help="1C REST adapter base URL for --live.")
|
||||
parser.add_argument("--adapter-url", default="http://docker.cin.su:8011", help="1C REST adapter base URL for --live.")
|
||||
parser.add_argument("--mcp-url", default="http://docker.cin.su:8021", help="1C MCP proxy base URL for --live --transport mcp.")
|
||||
parser.add_argument("--base-id", help="Concrete 1C base id for --live.")
|
||||
parser.add_argument("--ref", help="Optional concrete public object ref for deterministic --live checks, for example Kind.ObjectName.")
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import Any
|
||||
from urllib.error import HTTPError, URLError
|
||||
|
||||
|
||||
DEFAULT_BASE_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_BASE_URL = "http://docker.cin.su:8011"
|
||||
DEFAULT_MCP_URL = "http://docker.cin.su:8021"
|
||||
EXPECTED_CONTRACT_VERSION = "onec-selector-contract.v1"
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import Any
|
||||
from urllib.error import HTTPError, URLError
|
||||
|
||||
|
||||
DEFAULT_BASE_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_BASE_URL = "http://docker.cin.su:8011"
|
||||
DEFAULT_MCP_URL = "http://docker.cin.su:8021"
|
||||
EXPECTED_CONTRACT_VERSION = "onec-selector-contract.v1"
|
||||
|
||||
|
||||
@@ -3,12 +3,13 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEFAULT_BASE_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_BASE_URL = "http://docker.cin.su:8011"
|
||||
|
||||
|
||||
def rpc(base_url: str, method: str, payload: dict[str, Any]) -> dict[str, Any]:
|
||||
@@ -49,10 +50,15 @@ def discover_module_target(base_url: str, base_id: str, table: str, timeout_seco
|
||||
if not file_name:
|
||||
continue
|
||||
for stream in module.get("streams") or []:
|
||||
if not isinstance(stream, dict) or not stream.get("has_bsl_marker"):
|
||||
if not isinstance(stream, dict):
|
||||
continue
|
||||
preview = str(stream.get("preview") or "")
|
||||
old = "#Если " if "#Если " in preview else "Процедура " if "Процедура " in preview else ""
|
||||
if not stream.get("has_bsl_marker") and not any(
|
||||
marker in preview for marker in ("Процедура ", "Функция ", "&НаКлиенте", "&НаСервере")
|
||||
):
|
||||
continue
|
||||
declaration = re.search(r"(?im)^\s*(?:Процедура|Функция)\s+[A-Za-zА-Яа-яЁё_][\wА-Яа-яЁё]*\s*\([^)]*\)", preview)
|
||||
old = declaration.group(0).strip() if declaration else "#Если " if "#Если " in preview else ""
|
||||
if not old:
|
||||
continue
|
||||
write_plan_target = stream.get("write_plan_target") if isinstance(stream.get("write_plan_target"), dict) else {}
|
||||
@@ -77,6 +83,11 @@ def main() -> int:
|
||||
parser.add_argument("--base-id", default="upo_test")
|
||||
parser.add_argument("--table", default="ConfigCASSave")
|
||||
parser.add_argument("--file-name", default="", help="Saved-state module file name. Empty or placeholder* auto-selects a BSL stream.")
|
||||
parser.add_argument(
|
||||
"--module-ref",
|
||||
default="",
|
||||
help="Exact module_ref returned by discovery. Useful for embedded form modules without a #stream suffix.",
|
||||
)
|
||||
parser.add_argument("--stream-index", type=int, default=4)
|
||||
parser.add_argument("--old", default="Перем Параметры; ")
|
||||
parser.add_argument("--new", default="Перем Параметры; ")
|
||||
@@ -115,13 +126,29 @@ def main() -> int:
|
||||
if auto_target.get("expected_sha1") and not args.expected_sha1:
|
||||
args.expected_sha1 = str(auto_target["expected_sha1"])
|
||||
|
||||
module_ref = f"{args.table}:{args.file_name}#stream:{args.stream_index}"
|
||||
module_ref = str(
|
||||
args.module_ref
|
||||
or auto_target.get("module_ref")
|
||||
or f"{args.table}:{args.file_name}#stream:{args.stream_index}"
|
||||
)
|
||||
saved_state = rpc(
|
||||
args.base_url,
|
||||
"metadata.saved_state.modules.search",
|
||||
{
|
||||
"base_id": args.base_id,
|
||||
"tables": [args.table],
|
||||
"file_name": args.file_name,
|
||||
"limit": 1,
|
||||
"scan_limit": 10,
|
||||
"include_storage": True,
|
||||
"timeout_seconds": args.timeout_seconds,
|
||||
},
|
||||
)
|
||||
saved_state_preflight = {
|
||||
"status": saved_state.get("status"),
|
||||
"counts": saved_state.get("counts") or {},
|
||||
}
|
||||
if args.allow_empty_saved_state:
|
||||
saved_state = rpc(
|
||||
args.base_url,
|
||||
"metadata.saved_state.modules.search",
|
||||
{"base_id": args.base_id, "tables": [args.table], "limit": 1, "scan_limit": 100, "timeout_seconds": args.timeout_seconds},
|
||||
)
|
||||
if saved_state.get("status") == "ok" and ((saved_state.get("counts") or {}).get("modules") or 0) == 0:
|
||||
result = {
|
||||
"schema": "onec_module_stream_write_smoke.v1",
|
||||
@@ -157,6 +184,10 @@ def main() -> int:
|
||||
result = {
|
||||
"schema": "onec_module_stream_write_smoke.v1",
|
||||
"status": "write_failed",
|
||||
"base_id": args.base_id,
|
||||
"table": args.table,
|
||||
"module_ref": module_ref,
|
||||
"saved_state_preflight": saved_state_preflight,
|
||||
"metadata_write": written,
|
||||
"write_plan": write_plan,
|
||||
}
|
||||
@@ -185,7 +216,7 @@ def main() -> int:
|
||||
"base_id": args.base_id,
|
||||
"table": args.table,
|
||||
"module_ref": module_ref,
|
||||
**({"saved_state_preflight": auto_target.get("discovery")} if auto_target else {}),
|
||||
"saved_state_preflight": saved_state_preflight,
|
||||
"write_plan": {
|
||||
"status": write_plan.get("status"),
|
||||
"allowed": write_plan.get("allowed"),
|
||||
|
||||
@@ -290,7 +290,7 @@ def discover_smoke_routes(base_url: str, base_id: str, table: str, timeout: floa
|
||||
def main() -> int:
|
||||
|
||||
parser = argparse.ArgumentParser(description="Smoke test saved-state form write routing with apply_and_rollback.")
|
||||
parser.add_argument("--base-url", default="http://docker-gpu.cin.su:8011", help="1C REST adapter URL.")
|
||||
parser.add_argument("--base-url", default="http://docker.cin.su:8011", help="1C REST adapter URL.")
|
||||
parser.add_argument("--base-id", default="upo_test", help="Configured adapter base id.")
|
||||
parser.add_argument("--table", default="ConfigCASSave", help="Saved-state SQL table.")
|
||||
parser.add_argument("--file-name", default=DEFAULT_FORM_FILE, help="Saved-state form file name. Omit to auto-select a safe saved-state form title route.")
|
||||
|
||||
@@ -86,7 +86,7 @@ def compact_smoke_result(smoke: dict[str, Any], *, max_failures: int) -> dict[st
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Build and optionally smoke-test saved-state form write matrix.")
|
||||
parser.add_argument("--base-url", default="http://docker-gpu.cin.su:8011", help="1C REST adapter URL.")
|
||||
parser.add_argument("--base-url", default="http://docker.cin.su:8011", help="1C REST adapter URL.")
|
||||
parser.add_argument("--base-id", default="upo_test", help="Configured adapter base id.")
|
||||
parser.add_argument("--table", default="ConfigCASSave", help="Saved-state SQL table.")
|
||||
parser.add_argument("--file-name", default=DEFAULT_FORM_FILE, help="Saved-state form file name.")
|
||||
|
||||
@@ -10,7 +10,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEFAULT_BASE_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_BASE_URL = "http://docker.cin.su:8011"
|
||||
DEFAULT_MCP_URL = "http://docker.cin.su:8021"
|
||||
EXPECTED_CONTRACT_VERSION = "onec-selector-contract.v1"
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from typing import Any
|
||||
from urllib.error import HTTPError, URLError
|
||||
|
||||
|
||||
DEFAULT_BASE_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_BASE_URL = "http://docker.cin.su:8011"
|
||||
DEFAULT_MCP_URL = "http://docker.cin.su:8021"
|
||||
EXPECTED_CONTRACT_VERSION = "onec-selector-contract.v1"
|
||||
PREFLIGHT_CLASSIFICATION_STATUSES = {
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import Any
|
||||
from urllib.error import HTTPError, URLError
|
||||
|
||||
|
||||
DEFAULT_BASE_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_BASE_URL = "http://docker.cin.su:8011"
|
||||
DEFAULT_MCP_URL = "http://docker.cin.su:8021"
|
||||
EXPECTED_CONTRACT_VERSION = "onec-selector-contract.v1"
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ def build_history(
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Track recent MOXCEL template signatures from live ConfigCAS.")
|
||||
parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011")
|
||||
parser.add_argument("--adapter-url", default="http://docker.cin.su:8011")
|
||||
parser.add_argument("--base-id", default="upo_test")
|
||||
parser.add_argument("--limit", type=int, default=12)
|
||||
parser.add_argument("--track-name", default="R7C2_TEST")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string[]]$BaseId,
|
||||
[string]$AdapterUrl = "http://docker-gpu.cin.su:8011",
|
||||
[string]$AdapterUrl = "http://docker.cin.su:8011",
|
||||
[string]$McpUrl = "http://docker.cin.su:8021",
|
||||
[string]$ObjectRef,
|
||||
[string]$ObjectKind,
|
||||
@@ -554,6 +554,28 @@ try {
|
||||
Assert-SavedStateChangesReport -Label "REST adapter saved-state changes smoke ($currentBaseId)" -Path $savedStateChangesReport
|
||||
}
|
||||
|
||||
# This is the regression for the extension first-write path. It
|
||||
# is deliberately restricted to the disposable authorised base:
|
||||
# the smoke creates one ConfigCASSave row and removes it again by
|
||||
# the opaque receipt before returning.
|
||||
if ($currentBaseId -eq "upo_test") {
|
||||
$extensionPrepareReport = Join-Path $reportDir "extension-saved-state-prepare-smoke.json"
|
||||
$extensionPrepareCommand = @(
|
||||
"python",
|
||||
"scripts/smoke_1c_extension_saved_state_prepare.py",
|
||||
"--base-url",
|
||||
$AdapterUrl,
|
||||
"--base-id",
|
||||
$currentBaseId,
|
||||
"--timeout",
|
||||
$TimeoutSec.ToString(),
|
||||
"--report",
|
||||
$extensionPrepareReport,
|
||||
"--apply"
|
||||
)
|
||||
Invoke-CheckedCommand -Label "REST adapter extension saved-state prepare smoke ($currentBaseId)" -Command $extensionPrepareCommand
|
||||
}
|
||||
|
||||
if (-not $SkipSavedStateWriteSmoke) {
|
||||
$readinessReport = Join-Path $reportDir "saved-state-strict-readiness.json"
|
||||
$readinessCommand = @(
|
||||
|
||||
@@ -9,7 +9,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEFAULT_BASE_URL = "http://docker-gpu.cin.su:8011"
|
||||
DEFAULT_BASE_URL = "http://docker.cin.su:8011"
|
||||
SOURCE_BY_TARGET = {"ConfigSave": "Config", "ConfigCASSave": "ConfigCAS"}
|
||||
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ def wait_for_new_moxel(
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Capture a before/after MOXCEL one-property experiment around a manual 1C save.")
|
||||
parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011")
|
||||
parser.add_argument("--adapter-url", default="http://docker.cin.su:8011")
|
||||
parser.add_argument("--base-id", default="upo_test")
|
||||
parser.add_argument("--property", required=True, help="Property label, for example ВертикальноеПоложение.")
|
||||
parser.add_argument("--operation", default="manual_one_property_save")
|
||||
|
||||
Reference in New Issue
Block a user