Initial project import

This commit is contained in:
2026-08-14 09:40:51 +03:00
parent 00040e5ce4
commit d7099bf80d
146 changed files with 30509 additions and 1055 deletions
+67
View File
@@ -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())
+1 -1
View File
@@ -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="АвансовыйОтчет")
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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")
+8 -1
View File
@@ -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",
+2 -2
View File
@@ -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"}
+4 -1
View File
@@ -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})
+63 -3
View File
@@ -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) {
+89
View File
@@ -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"
+231
View File
@@ -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())
+1 -1
View File
@@ -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]:
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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"}
+1 -1
View File
@@ -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")
+1 -1
View File
@@ -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")
+1 -1
View File
@@ -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)
+13 -1
View File
@@ -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,
)
+1 -1
View File
@@ -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"
+67 -1
View File
@@ -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} }
+56 -1
View File
@@ -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"}:
+187
View File
@@ -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())
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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())
+52 -2
View File
@@ -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.")
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
+41 -10
View File
@@ -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"),
+1 -1
View File
@@ -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.")
+1 -1
View File
@@ -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.")
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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 = {
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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")
+23 -1
View File
@@ -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 = @(
+1 -1
View File
@@ -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")