Initial SQL-only 1C adapter baseline

This commit is contained in:
2026-07-22 03:03:47 +03:00
commit e2503b77e7
545 changed files with 184711 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
# 1C Adapter Tools
This directory contains the current agent-facing 1C adapter tools.
The adapter must expose 1C metadata in configurator terms first: object kind,
object name, forms, modules, attributes, tabular sections, commands, events,
and 1C type names. SQL table names, DBNames roles, GUIDs, CAS files, and XML
paths are internal evidence and diagnostics.
## Current Entry Points
Use these commands for normal agent work:
```powershell
python scripts/resolve_1c_object.py --index <UnifiedRouteIndexJson> --kind <Kind> --name <Name> --output <ObjectResolutionJson>
python scripts/get_1c_object_brief_context.py --index <UnifiedRouteIndexJson> --kind <Kind> --name <Name> --view effective --output <ObjectBriefContextJson>
python scripts/get_1c_object_metadata.py --index <UnifiedRouteIndexJson> --kind <Kind> --name <Name> --view effective --output <ObjectMetadataJson>
python scripts/search_1c_object_context.py --index <UnifiedRouteIndexJson> --kind <Kind> --name <Name> --text <SearchText> --view effective --search-code --output <ObjectContextSearchJson>
python scripts/get_1c_object_artifacts.py --index <UnifiedRouteIndexJson> --kind <Kind> --name <Name> --output <ArtifactsJson>
python scripts/get_1c_object_code_context.py --index <UnifiedRouteIndexJson> --kind <Kind> --name <Name> --view effective --output <CodeContextJson>
python scripts/get_1c_module.py --index <UnifiedRouteIndexJson> --kind <Kind> --name <Name> --module <ModuleName> --view effective --max-chars <N> --output <ModuleContentJson>
python scripts/get_1c_form_context.py --index <UnifiedRouteIndexJson> --kind <Kind> --name <Name> --form <FormName> --view effective --max-items <N> --output <FormContextJson>
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/compare_1c_saved_state_objects.ps1 -Server <SqlServer> -Database <SqlDatabase> -User <SqlUser> -Password <SqlPassword> -Output <SavedStateObjectComparisonJson>
python scripts/analyze_1c_saved_state_object_details.py --comparison <SavedStateObjectComparisonJson> --config-save-dir <ConfigSaveExportDir> --config-dir <ConfigActiveExportDir> --config-cas-save-dir <ConfigCASSaveExportDir> --config-cas-dir <ConfigCASActiveExportDir> --extension-manifest-summary <ExtensionManifestSummaryJson> --config-cas-all-dir <ConfigCASAllDir> --output <SavedStateObjectDetailJson>
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/build_1c_saved_state_object_report.ps1 -Server <SqlServer> -Database <SqlDatabase> -User <SqlUser> -Password <SqlPassword> -OutputDir <SavedStateReportDir> [-SkipMarkdown]
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/watch_1c_saved_state_once.ps1 -Server <SqlServer> -Database <SqlDatabase> -User <SqlUser> -Password <SqlPassword> -OutputRoot <SavedStateWatchRoot>
python scripts/list_1c_saved_state_watch_runs.py --root <SavedStateWatchRoot> [--limit <N>] [--only-with-delta] [--only-changed] --output <SavedStateWatchRunListJson> [--skip-markdown] [--skip-check]
python scripts/check_1c_saved_state_watch_run_list.py --list <SavedStateWatchRunListJson> --output <SavedStateWatchRunListCheckJson>
python scripts/get_1c_saved_state_latest_watch_run.py --root <SavedStateWatchRoot> [--require-delta] [--require-changed] --output <SavedStateLatestWatchRunJson> [--skip-markdown] [--skip-check]
python scripts/check_1c_saved_state_latest_watch_run.py --latest <SavedStateLatestWatchRunJson> --output <SavedStateLatestWatchRunCheckJson>
python scripts/render_1c_saved_state_latest_watch_run_markdown.py --latest <SavedStateLatestWatchRunJson> --output <SavedStateLatestWatchRunMarkdown>
python scripts/render_1c_saved_state_watch_run_list_markdown.py --list <SavedStateWatchRunListJson> --output <SavedStateWatchRunListMarkdown>
python scripts/check_1c_saved_state_object_report.py --report <SavedStateObjectReportJson> --output <SavedStateObjectReportCheckJson>
python scripts/check_1c_saved_state_watch_once.py --manifest <SavedStateWatchRunJson> --output <SavedStateWatchRunCheckJson>
python scripts/render_1c_saved_state_watch_once_markdown.py --manifest <SavedStateWatchRunJson> --output <SavedStateWatchRunMarkdown>
python scripts/compare_1c_saved_state_object_reports.py --before <PreviousSavedStateObjectReportJson> --after <CurrentSavedStateObjectReportJson> --output <SavedStateObjectReportDeltaJson> [--skip-markdown] [--skip-check]
python scripts/check_1c_saved_state_object_report_delta.py --delta <SavedStateObjectReportDeltaJson> --output <SavedStateObjectReportDeltaCheckJson>
python scripts/render_1c_saved_state_object_report_delta_markdown.py --delta <SavedStateObjectReportDeltaJson> --output <SavedStateObjectReportDeltaMarkdown>
python scripts/list_1c_saved_state_object_changes.py --report <SavedStateObjectReportJson> [--layer base|extension] [--kind <Kind>] [--payload-role <Role>] [--active-missing true|false] --output <SavedStateObjectChangeListJson>
python scripts/get_1c_saved_state_object_change.py --report <SavedStateObjectReportJson> --name <ConfiguratorObjectName> --output <SavedStateObjectChangeJson>
python scripts/render_1c_saved_state_object_report_markdown.py --report <SavedStateObjectReportJson> --output <SavedStateObjectReportMarkdown>
```
Use these commands for task-level investigation and extension patch workflow:
```powershell
python scripts/plan_1c_task_context.py --index <UnifiedRouteIndexJson> --text <TaskText> --view effective --output <TaskContextPlanJson>
python scripts/build_1c_task_evidence.py --index <UnifiedRouteIndexJson> --text <TaskText> --view effective --output <TaskEvidenceBundleJson>
python scripts/propose_1c_task_changes.py --evidence <TaskEvidenceBundleJson> --output <TaskChangeProposalJson>
python scripts/render_1c_task_proposal_markdown.py --proposal <TaskChangeProposalJson> --output <TaskChangeProposalMarkdown>
python scripts/check_1c_change_proposal_safety.py --proposal <TaskChangeProposalJson> --output <TaskChangeProposalSafetyJson>
python scripts/create_1c_patch_workspace.py --proposal <TaskChangeProposalJson> --slug <PatchSlug> --output <PatchWorkspaceCreationJson>
python scripts/check_1c_patch_workspace_integrity.py --workspace <PatchWorkspaceDir> --output <PatchWorkspaceIntegrityJson>
python scripts/check_1c_patch_source_freshness.py --workspace <PatchWorkspaceDir> --output <PatchSourceFreshnessJson>
python scripts/validate_1c_patch_workspace_semantics.py --workspace <PatchWorkspaceDir> --output <PatchWorkspaceSemanticValidationJson>
python scripts/edit_1c_bsl_routine.py --workspace <PatchWorkspaceDir> --relative-path <ManifestBslModuleRelativePath> --operation append|replace|upsert --routine-text-b64 <Utf8Base64BslRoutine> --output <BslRoutineEditJson>
python scripts/edit_1c_form_command.py --workspace <PatchWorkspaceDir> --relative-path <ManifestFormXmlRelativePath> --operation append|replace|upsert --name <CommandName> --title <RussianTitle> --action <BslHandlerName> --output <FormCommandEditJson>
python scripts/edit_1c_form_button.py --workspace <PatchWorkspaceDir> --relative-path <ManifestFormXmlRelativePath> --operation append|replace|upsert --parent-name <ParentFormItemName> --name <ButtonName> --title <RussianTitle> --command-name <ExistingCommandName> --output <FormButtonEditJson>
python scripts/add_1c_form_button_workflow.py --workspace <PatchWorkspaceDir> --form-relative-path <ManifestFormXmlRelativePath> --bsl-relative-path <ManifestFormModuleRelativePath> --operation append|replace|upsert --routine-text-b64 <Utf8Base64BslRoutine> --command-name <CommandName> --command-title <RussianCommandTitle> --command-action <BslHandlerName> --button-parent-name <ParentFormItemName> --button-name <ButtonName> --button-title <RussianButtonTitle> --output <FormButtonWorkflowJson>
python scripts/diff_1c_patch_workspace.py --workspace <PatchWorkspaceDir> --output <PatchWorkspaceDiffJson>
python scripts/create_1c_patch_bundle.py --workspace <PatchWorkspaceDir> --slug <BundleSlug> --output <PatchBundleCreationJson>
python scripts/check_1c_patch_bundle.py --bundle-dir <PatchBundleDir> --output <PatchBundleCheckJson>
python scripts/create_1c_extension_staging_from_bundle.py --bundle-dir <PatchBundleDir> --slug <StagingSlug> --output <ExtensionStagingCreationJson>
python scripts/check_1c_extension_staging.py --staging-dir <ExtensionStagingDir> --output <ExtensionStagingCheckJson>
python scripts/check_1c_extension_runner_config.py --config <ExtensionRunnerConfigJson> --output <ExtensionRunnerConfigCheckJson>
python scripts/create_1c_extension_validation_plan.py --staging-dir <ExtensionStagingDir> --output <ExtensionValidationPlanJson> --markdown-output <ExtensionValidationPlanMarkdown>
python scripts/create_1c_extension_validation_evidence.py --plan <ExtensionValidationPlanJson> --output <ExtensionValidationEvidenceManifestJson>
python scripts/check_1c_extension_validation_evidence.py --plan <ExtensionValidationPlanJson> --output <ExtensionValidationEvidenceCheckJson>
python scripts/check_1c_extension_validation_release.py --plan <ExtensionValidationPlanJson> --output <ExtensionValidationReleaseCheckJson>
python scripts/render_1c_extension_validation_release_markdown.py --release-check <ExtensionValidationReleaseCheckJson> --output <ExtensionValidationReleaseMarkdown>
python scripts/check_1c_patch_preflight.py --workspace <PatchWorkspaceDir> --output <PatchPreflightJson>
python scripts/render_1c_patch_preflight_markdown.py --preflight <PatchPreflightJson> --output <PatchPreflightMarkdown>
```
Use this read orchestrator when object data rows are needed from SQL:
```powershell
python scripts/read_1c_object_view.py --kind <Kind> --name <Name> --view effective --summary <SummaryJson> --validation <ValidationJson> --route-index <RouteIndexJson> --output-dir <OutputDir>
```
Use `--name-b64` and `--kind-b64` when a shell cannot pass Unicode safely.
Saved-state object change lookup also supports `--name-b64`.
## Rebuild And Maintenance
These tools rebuild the evidence used by the current adapter:
```powershell
python scripts/extract_1c_dbnames.py --help
python scripts/build_1c_xml_guid_index.py --help
python scripts/compare_1c_sql_xml_guids.py --help
python scripts/build_1c_unified_object_route_index.py --help
python scripts/build_1c_metadata_from_resolved_object.py --help
python plugins/1c/tools/enrich_structured_metadata_with_dbnames.py --help
python scripts/build_1c_enum_presentation_map.py --help
python scripts/build_1c_sql_read_view.py --help
python scripts/smoke_1c_read_view_kinds.py --help
python plugins/1c/tools/build_sql_read_projection.py --help
./scripts/validate_1c_predicted_columns.ps1 -?
./scripts/execute_1c_sql_read_projection.ps1 -?
./scripts/resolve_1c_sql_read_references.ps1 -?
./scripts/resolve_1c_sql_composite_references.ps1 -?
```
## Current Contract
The current public contract is documented in:
```text
docs/1c-adapter-api-contract.md
docs/1c-write-path-safety.md
docs/1c-sql-format-spec.md
```
Default view is `effective`: base configuration plus active extension overlays.
Use `base` only for main-configuration diagnostics and `extension` only when a
specific extension layer is requested.
Direct SQL writes to `Config`, `ConfigSave`, `ConfigCAS`, `ConfigCASSave`, and
data tables are not part of the safe adapter workflow.
@@ -0,0 +1,233 @@
#!/usr/bin/env python3
"""Build a SQL read projection from enriched structured 1C metadata."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
METADATA_FIELDS = (
"attributes",
"dimensions",
"resources",
"addressing_attributes",
"accounting_flags",
)
STANDARD_COLUMNS = {
"Document": ["_IDRRef", "_Marked", "_Date_Time", "_Number", "_Posted"],
"Catalog": ["_IDRRef", "_Marked", "_PredefinedID", "_Description"],
"AccumulationRegister": ["_Period", "_RecorderTRef", "_RecorderRRef", "_LineNo", "_Active", "_RecordKind"],
"AccountingRegister": ["_Period", "_RecorderTRef", "_RecorderRRef", "_LineNo", "_Active"],
# Information registers differ by periodicity/recorder settings. Standard
# columns must be added from live table schema, not assumed globally.
"InformationRegister": [],
"BusinessProcess": ["_IDRRef", "_Marked", "_Date_Time", "_Number"],
"Task": ["_IDRRef", "_Marked", "_Date_Time", "_Number"],
"ChartOfAccounts": ["_IDRRef", "_Marked", "_PredefinedID", "_Code", "_Description"],
"ChartOfCalculationTypes": ["_IDRRef", "_Marked", "_PredefinedID", "_Code", "_Description"],
}
def load_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8-sig"))
def quote_ident(value: str) -> str:
return "[" + value.replace("]", "]]") + "]"
def alias(value: str) -> str:
return value.replace("]", "").replace("[", "").replace(".", "__")
def primary_tables(report: dict[str, Any]) -> list[str]:
tables = []
for route in report.get("object_storage_routes") or []:
role = route.get("storage_role") or ""
table = route.get("physical_name_candidate")
if not table or route.get("route_kind") != "table":
continue
if role.endswith("ChngR") or role.endswith("SInf"):
continue
if role in {"BPrPoints", "AccumRgT", "AccumRgOpt", "AccRgAT0", "AccRgCT", "AccRgOpt"}:
continue
tables.append(table)
return tables
def validation_column_map(validation: dict[str, Any]) -> dict[tuple[str, str], dict[str, Any]]:
result = {}
for row in validation.get("results") or []:
if not row.get("found"):
continue
for match in row.get("matches") or []:
result[(match["table"], match["column"])] = match
return result
def item_columns(item: dict[str, Any], candidate_tables: list[str], schema: dict[tuple[str, str], dict[str, Any]]) -> list[dict[str, Any]]:
result = []
for column in item.get("physical_columns") or []:
name = column.get("column")
if not name:
continue
for table in candidate_tables:
match = schema.get((table, name))
result.append(
{
"metadata_name": item.get("name"),
"metadata_uuid": item.get("uuid"),
"column": name,
"sql_type": match,
"value_type": item.get("value_type"),
"source": item.get("source"),
"extension_name": item.get("extension_name"),
}
)
return result
def projected_items(items: list[dict[str, Any]], *, view: str, extension: str | None) -> list[dict[str, Any]]:
if view == "effective":
return items
if view == "base":
return [item for item in items if (item.get("source") or "base") != "extension"]
if view == "extension":
if not extension:
raise SystemExit("Use --extension with --view extension.")
result = []
for item in items:
if item.get("source") == "extension" and item.get("extension_name") == extension:
result.append(item)
continue
for override in item.get("extension_overrides") or []:
if override.get("extension_name") == extension:
copy = dict(item)
copy["source"] = "extension"
copy["extension_name"] = extension
if override.get("value_type"):
copy["value_type"] = override["value_type"]
result.append(copy)
break
return result
raise SystemExit(f"Unsupported view: {view}")
def select_sql(table: str, columns: list[dict[str, Any]], *, top: int) -> str:
parts = []
for index, col in enumerate(columns, start=1):
name = col["column"]
out_alias = col.get("alias") or f"c{index:03d}"
col["select_alias"] = out_alias
parts.append(f" {quote_ident(name)} AS {quote_ident(out_alias)}")
select_list = ",\n".join(parts) if parts else " *"
return f"SELECT TOP ({top})\n{select_list}\nFROM {quote_ident(table)};"
def build_projection(report: dict[str, Any], validation: dict[str, Any], *, top: int, view: str, extension: str | None) -> dict[str, Any]:
schema = validation_column_map(validation)
kind = report.get("kind")
tables = primary_tables(report)
main_table = tables[0] if tables else None
diagnostics: dict[str, Any] = {"standard_columns_without_validation": []}
main_columns = []
if main_table:
for column in STANDARD_COLUMNS.get(kind, []):
match = schema.get((main_table, column))
if not match:
diagnostics["standard_columns_without_validation"].append({"scope": "main", "table": main_table, "column": column})
main_columns.append(
{
"metadata_name": f"standard.{column}",
"metadata_path": f"standard.{column}",
"column": column,
"sql_type": match,
}
)
for field in METADATA_FIELDS:
for item in projected_items(report.get(field) or [], view=view, extension=extension):
for column in item_columns(item, [main_table], schema):
column["metadata_field"] = field
column["metadata_path"] = f"{field}.{item.get('name')}"
main_columns.append(column)
table_parts = []
by_parent: dict[str, list[dict[str, Any]]] = {}
for item in projected_items(report.get("tabular_section_attributes") or [], view=view, extension=extension):
parent_uuid = item.get("tabular_section_uuid") or item.get("parent_uuid")
by_parent.setdefault(parent_uuid, []).append(item)
for section in report.get("tabular_sections") or []:
physical_tables = [row["table"] for row in section.get("physical_tables") or [] if row.get("table")]
if not physical_tables:
continue
table = physical_tables[0]
columns = []
parent_id = f"{main_table}_IDRRef" if main_table else ""
if parent_id:
match = schema.get((table, parent_id))
if not match:
diagnostics["standard_columns_without_validation"].append({"scope": f"table_part:{section.get('name')}", "table": table, "column": parent_id})
columns.append({"metadata_name": "standard.owner", "metadata_path": "standard.owner", "column": parent_id, "sql_type": match})
for route in section.get("storage_routes") or []:
if route.get("storage_role") == "LineNo":
line_column = f"_LineNo{route['sql_number']}"
match = schema.get((table, line_column))
if not match:
diagnostics["standard_columns_without_validation"].append({"scope": f"table_part:{section.get('name')}", "table": table, "column": line_column})
columns.append({"metadata_name": "standard.line_no", "metadata_path": "standard.line_no", "column": line_column, "sql_type": match})
for item in by_parent.get(section.get("uuid"), []):
for column in item_columns(item, [table], schema):
column["metadata_field"] = "tabular_section_attributes"
column["metadata_path"] = f"{section.get('name')}.{item.get('name')}"
columns.append(column)
table_parts.append(
{
"name": section.get("name"),
"uuid": section.get("uuid"),
"table": table,
"columns": columns,
"select_sql": select_sql(table, columns, top=top),
}
)
return {
"schema": "onec_sql_read_projection.v1",
"kind": kind,
"identity": report.get("identity"),
"view": view,
"extension": extension,
"top": top,
"main_table": main_table,
"main_columns": main_columns,
"main_select_sql": select_sql(main_table, main_columns, top=top) if main_table else None,
"table_parts": table_parts,
"diagnostics": diagnostics,
}
def main() -> int:
parser = argparse.ArgumentParser(description="Build SQL read projection from enriched 1C metadata.")
parser.add_argument("--metadata", type=Path, required=True)
parser.add_argument("--validation", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--top", type=int, default=10)
parser.add_argument("--view", choices=["effective", "base", "extension"], default="effective")
parser.add_argument("--extension")
args = parser.parse_args()
projection = build_projection(load_json(args.metadata), load_json(args.validation), top=args.top, view=args.view, extension=args.extension)
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(projection, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(json.dumps({"output": str(args.output), "main_table": projection.get("main_table"), "table_parts": len(projection.get("table_parts") or [])}, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,251 @@
#!/usr/bin/env python3
"""Attach DBNames storage routes to structured metadata projection reports."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from typing import Any
PLUGIN_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(PLUGIN_ROOT))
from parser.dbnames import DBNamesRecord # noqa: E402
from parser.storage import storage_routes # noqa: E402
METADATA_FIELDS = (
"attributes",
"tabular_sections",
"dimensions",
"resources",
"forms",
"templates",
"commands",
"addressing_attributes",
"accounting_flags",
"columns",
"enum_values",
"integration_service_channels",
"operations",
"url_templates",
"tabular_section_attributes",
)
def load_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8-sig"))
def write_json(path: Path, data: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def dbnames_records(report: dict[str, Any]) -> list[DBNamesRecord]:
records = []
for db_file in report.get("dbnames") or []:
source = db_file.get("file_name") or db_file.get("source") or "DBNames"
for row in db_file.get("records") or []:
guid = (row.get("guid") or "").lower()
role = row.get("storage_role") or ""
number = row.get("sql_number")
if not guid or not role or number is None:
continue
records.append(
DBNamesRecord(
guid=guid,
storage_role=role,
sql_number=int(number),
index=int(row.get("index") or 0),
source=source,
)
)
return records
def route_index(records: list[DBNamesRecord]) -> dict[str, list[dict[str, Any]]]:
grouped: dict[str, list[DBNamesRecord]] = {}
for record in records:
grouped.setdefault(record.guid, []).append(record)
return {
guid: [route.to_dict() for route in storage_routes(rows)]
for guid, rows in grouped.items()
}
def enrich_report(report: dict[str, Any], routes_by_guid: dict[str, list[dict[str, Any]]]) -> dict[str, Any]:
result = dict(report)
object_guid = ((report.get("identity") or {}).get("guid") or "").lower()
result["object_storage_routes"] = routes_by_guid.get(object_guid, [])
object_tables = [
route["physical_name_candidate"]
for route in result["object_storage_routes"]
if route.get("route_kind") == "table"
and route.get("physical_name_candidate")
and is_primary_object_table(route)
]
matched = 0
total = 0
for field in METADATA_FIELDS:
enriched_items = []
for item in report.get(field) or []:
total += 1
copy = dict(item)
guid = (copy.get("uuid") or "").lower()
routes = routes_by_guid.get(guid, [])
copy["storage_routes"] = routes
copy["storage_route_count"] = len(routes)
copy["physical_columns"] = predicted_columns(copy, routes)
if field == "tabular_sections":
copy["physical_tables"] = predicted_tabular_section_tables(copy, routes, object_tables)
if field == "tabular_section_attributes":
copy["parent_physical_tables"] = parent_tabular_section_tables(result, copy)
if routes:
matched += 1
enriched_items.append(copy)
result[field] = enriched_items
result["storage_route_summary"] = {
"metadata_item_count": total,
"metadata_items_with_routes": matched,
"object_route_count": len(result["object_storage_routes"]),
}
return result
def parent_tabular_section_tables(report: dict[str, Any], item: dict[str, Any]) -> list[dict[str, Any]]:
parent_uuid = (item.get("tabular_section_uuid") or item.get("parent_uuid") or "").lower()
if not parent_uuid:
return []
for tabular_section in report.get("tabular_sections") or []:
if (tabular_section.get("uuid") or "").lower() == parent_uuid:
return tabular_section.get("physical_tables") or []
return []
def predicted_tabular_section_tables(
item: dict[str, Any],
routes: list[dict[str, Any]],
object_tables: list[str],
) -> list[dict[str, Any]]:
result = []
vt_routes = [route for route in routes if route.get("storage_role") == "VT" and route.get("sql_number") is not None]
line_routes = [route for route in routes if route.get("storage_role") == "LineNo"]
for vt in vt_routes:
for table in object_tables:
result.append(
{
"table": f"{table}_VT{vt['sql_number']}",
"reason": "tabular section VT route under object table",
"vt_sql_number": vt["sql_number"],
"line_no_sql_numbers": [route["sql_number"] for route in line_routes],
}
)
return result
def is_primary_object_table(route: dict[str, Any]) -> bool:
role = route.get("storage_role") or ""
if role.endswith("ChngR") or role.endswith("SInf"):
return False
if role in {"BPrPoints", "AccumRgT", "AccumRgOpt", "AccRgAT0", "AccRgCT", "AccRgOpt"}:
return False
return True
def predicted_columns(item: dict[str, Any], routes: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""Predict physical SQL column names for proven simple type cases."""
value_type = item.get("value_type") or {}
types = value_type.get("types") or []
if len(types) > 1:
return predicted_composite_columns(types, routes)
if len(types) == 0:
return [{"status": "no_value_type"}] if routes else []
value = types[0]
result = []
for route in routes:
if route.get("storage_role") != "Fld" or not route.get("physical_name_candidate"):
continue
base = route["physical_name_candidate"]
if value.startswith("cfg:") and "Ref." in value:
result.append({"column": f"{base}RRef", "reason": "single 1C reference type", "value_type": value})
elif value in {"xs:string", "xs:decimal", "xs:boolean", "xs:dateTime"}:
result.append({"column": base, "reason": "single primitive XML type", "value_type": value})
else:
result.append({"status": "unmapped_single_type", "value_type": value, "base": base})
return result
def predicted_composite_columns(types: list[str], routes: list[dict[str, Any]]) -> list[dict[str, Any]]:
result = []
has_reference = any(value.startswith("cfg:") and "Ref." in value for value in types)
reference_types = [value for value in types if value.startswith("cfg:") and "Ref." in value]
has_string = "xs:string" in types
has_decimal = "xs:decimal" in types
has_boolean = "xs:boolean" in types
has_datetime = "xs:dateTime" in types
for route in routes:
if route.get("storage_role") != "Fld" or not route.get("physical_name_candidate"):
continue
base = route["physical_name_candidate"]
result.append({"column": f"{base}_TYPE", "reason": "composite value discriminator", "value_types": types})
if has_reference:
if len(reference_types) > 1:
result.append({"column": f"{base}_RTRef", "reason": "composite reference type id", "value_types": types})
result.append({"column": f"{base}_RRRef", "reason": "composite reference value", "value_types": types})
if has_string:
result.append({"column": f"{base}_S", "reason": "composite string value", "value_types": types})
if has_decimal:
result.append({"column": f"{base}_N", "reason": "composite numeric value", "value_types": types})
if has_boolean:
result.append({"column": f"{base}_B", "reason": "composite boolean value", "value_types": types})
if has_datetime:
result.append({"column": f"{base}_T", "reason": "composite datetime value", "value_types": types})
return result or [{"status": "unmapped_composite_type", "value_types": types}]
def enrich_batch(args: argparse.Namespace) -> dict[str, Any]:
db_report = load_json(args.dbnames)
routes_by_guid = route_index(dbnames_records(db_report))
batch = load_json(args.summary)
outputs = []
totals = {"metadata_item_count": 0, "metadata_items_with_routes": 0, "object_route_count": 0}
for row in batch.get("outputs") or []:
source = Path(row["output"])
report = load_json(source)
enriched = enrich_report(report, routes_by_guid)
output = args.output_dir / source.name
write_json(output, enriched)
summary = enriched["storage_route_summary"]
for key in totals:
totals[key] += summary[key]
outputs.append({**row, "output": str(output), "storage_route_summary": summary})
return {
"schema": "onec_structured_metadata_dbnames_enrichment_batch.v1",
"summary": str(args.summary),
"dbnames": str(args.dbnames),
"outputs": outputs,
"totals": totals,
}
def main() -> int:
parser = argparse.ArgumentParser(description="Attach DBNames storage routes to structured metadata reports.")
parser.add_argument("--dbnames", type=Path, required=True)
parser.add_argument("--summary", type=Path, required=True)
parser.add_argument("--output-dir", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
result = enrich_batch(args)
write_json(args.output, result)
print(json.dumps({"output": str(args.output), "totals": result["totals"]}, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+456
View File
@@ -0,0 +1,456 @@
id: 1c-tools
name: 1C Tool Contract
status: draft
rules:
- "Do not invent 1C metadata. Request metadata through tools when object structure matters."
- "Do not expose credentials, connection strings, tokens, personal data, or client secrets."
- "Prefer read-only tools by default."
tools:
- id: get_1c_metadata
mode: read
description: "Get available 1C metadata objects by kind."
input_schema:
type: object
required:
- object_kind
properties:
object_kind:
type: string
enum:
- document
- catalog
- register
- all
output_notes:
- "Return object names, kinds, and available fields when supported by the backend."
- "Never include secrets or database credentials."
- id: search_1c_modules
mode: read
description: "Search BSL modules by text or object name."
status: planned
- id: explain_bsl_error
mode: local
description: "Explain a BSL error message and propose safe fixes."
status: planned
- id: check_1c_query
mode: local
description: "Check a 1C query for common syntax and semantic issues."
status: draft
policy: plugins/1c/connector/policies/read-only-query.yaml
command: scripts/validate_1c_readonly_query.py
- id: compare_1c_saved_state_objects
mode: read
description: "Compare saved-but-not-applied 1C state with active state and return changes in configurator object terms."
status: draft
command: scripts/compare_1c_saved_state_objects.ps1
output_schema: onec_saved_state_object_comparison.v1
safety:
- "Read-only SQL access."
- "Compares ConfigSave with Config and ConfigCASSave with ConfigCAS."
- "Returns public object changes as 1C configurator objects, not SQL table names."
- "Keeps FileName/hash details only as storage evidence."
- "Does not perform SQL writes."
- id: analyze_1c_saved_state_object_details
mode: read
description: "Analyze saved-state object changes inside changed payloads and return object-level detail hints."
status: draft
command: scripts/analyze_1c_saved_state_object_details.py
output_schema: onec_saved_state_object_detail.v1
safety:
- "Read-only file analysis."
- "Consumes saved-state object comparison plus exported active/saved payload files."
- "Returns text deltas, added/removed words, and saved form/module string samples in 1C object context."
- "Does not perform SQL writes."
- id: build_1c_saved_state_object_report
mode: read
description: "Build one read-only saved-state report: object comparison, required SQL payload exports, object detail analysis, and Markdown summary."
status: draft
command: scripts/build_1c_saved_state_object_report.ps1
output_schema: onec_saved_state_object_report.v1
preferred_for:
- "Agent needs to inspect saved-but-not-applied Designer changes."
- "User asks what changed in Configurator after saving but before applying."
safety:
- "Read-only SQL access."
- "Orchestrates compare_1c_saved_state_objects and analyze_1c_saved_state_object_details."
- "Exports only payload evidence needed for object-level comparison."
- "Returns public changes as 1C configurator objects, not SQL table names."
- "Includes agent_summary for compact agent-facing follow-up decisions."
- "Classifies payload parts with roles and filtered semantic term hints."
- "Writes a human-readable Markdown report by default."
- "Runs check_1c_saved_state_object_report as a final contract gate."
- "Does not perform SQL writes."
- id: check_1c_saved_state_object_report
mode: local
description: "Validate saved-state object report JSON, linked detail/comparison files, safety flags, agent_summary, payload roles, and semantic hints."
status: draft
command: scripts/check_1c_saved_state_object_report.py
output_schema: onec_saved_state_object_report_check.v1
safety:
- "Read-only validation."
- "Rejects missing linked comparison/detail/Markdown files."
- "Rejects unstable agent_summary array fields and unknown payload roles."
- "Rejects reports that do not preserve read-only safety flags."
- id: get_1c_saved_state_object_change
mode: local
description: "Return one changed 1C object from a saved-state object report by configurator-visible name."
status: draft
command: scripts/get_1c_saved_state_object_change.py
output_schema: onec_saved_state_object_change.v1
preferred_for:
- "Agent has a saved-state report and needs only one changed object."
- "User asks what changed in a specific object after saving in Designer."
safety:
- "Read-only JSON lookup."
- "Searches by full name, short name, synonym, and suffix/contains matches."
- "Returns candidates instead of guessing when a match is ambiguous."
- "Keeps SQL storage details as evidence under comparison/detail fields."
- id: list_1c_saved_state_object_changes
mode: local
description: "List changed 1C objects from a saved-state object report with compact filters."
status: draft
command: scripts/list_1c_saved_state_object_changes.py
output_schema: onec_saved_state_object_change_list.v1
preferred_for:
- "Agent needs to route a saved-state report before reading details."
- "User asks which changed objects are modules, forms, extension changes, or missing active parts."
safety:
- "Read-only JSON lookup."
- "Filters by layer, kind, extension, payload role, text diff, and active-missing state."
- "Returns compact 1C object summaries with payload roles and semantic terms."
- id: compare_1c_saved_state_object_reports
mode: local
description: "Compare two saved-state object reports and return what changed between agent observations in 1C object terms."
status: draft
command: scripts/compare_1c_saved_state_object_reports.py
output_schema: onec_saved_state_object_report_delta.v1
preferred_for:
- "Agent needs to know what changed since the previous saved-state report."
- "User continues editing Designer and asks what is new since the last check."
safety:
- "Read-only JSON comparison."
- "Compares agent_summary objects by 1C full_name and stable payload-part fingerprints."
- "Returns added, removed, changed, and unchanged 1C objects."
- "Keeps system changes separate from object changes."
- "Writes a Markdown delta report by default when --output is provided unless --skip-markdown is used."
- "Runs check_1c_saved_state_object_report_delta as a final contract gate when --output is provided unless --skip-check is used."
- id: watch_1c_saved_state_once
mode: read
description: "Build one timestamped saved-state report observation and compare it with the previous observation when available."
status: draft
command: scripts/watch_1c_saved_state_once.ps1
output_schema: onec_saved_state_watch_once.v1
preferred_for:
- "User is editing Designer and wants the agent to observe saved-state changes repeatedly."
- "Agent needs a new report plus delta from the previous observation in one command."
safety:
- "Read-only SQL access."
- "Runs build_1c_saved_state_object_report and compare_1c_saved_state_object_reports."
- "Stores each observation in a timestamped run directory."
- "Writes a Markdown watch summary by default unless -SkipMarkdown is used."
- "Runs check_1c_saved_state_watch_once as a final contract gate."
- "Does not perform SQL writes."
- id: render_1c_saved_state_watch_once_markdown
mode: local
description: "Render a one-shot saved-state watch manifest as compact Markdown."
status: draft
command: scripts/render_1c_saved_state_watch_once_markdown.py
output_schema: markdown
safety:
- "Read-only rendering."
- "Consumes onec_saved_state_watch_once.v1 JSON."
- "Shows report, delta, checks, counts, and safety."
- id: check_1c_saved_state_watch_once
mode: local
description: "Validate a one-shot saved-state watch manifest and linked report/delta checks."
status: draft
command: scripts/check_1c_saved_state_watch_once.py
output_schema: onec_saved_state_watch_once_check.v1
safety:
- "Read-only validation."
- "Rejects missing linked report, report check, delta, delta check, or mismatched counts."
- id: list_1c_saved_state_watch_runs
mode: local
description: "List timestamped saved-state watch runs under an output root."
status: draft
command: scripts/list_1c_saved_state_watch_runs.py
output_schema: onec_saved_state_watch_run_list.v1
preferred_for:
- "Agent needs the latest saved-state watch observation."
- "User asks what watch runs exist or which runs had delta changes."
safety:
- "Read-only filesystem listing."
- "Returns compact run summaries, linked artifact paths, check statuses, and delta counts."
- "Supports filters for runs with delta and runs with actual delta changes."
- "Writes a Markdown run-list report by default when --output is provided unless --skip-markdown is used."
- "Runs check_1c_saved_state_watch_run_list as a final contract gate when --output is provided unless --skip-check is used."
- id: check_1c_saved_state_watch_run_list
mode: local
description: "Validate saved-state watch run list JSON, linked Markdown, run directories, check statuses, latest item, and counts."
status: draft
command: scripts/check_1c_saved_state_watch_run_list.py
output_schema: onec_saved_state_watch_run_list_check.v1
safety:
- "Read-only validation."
- "Rejects missing linked Markdown when the list JSON declares one."
- "Rejects missing run directories, check status mismatches, latest mismatch, and count mismatches."
- id: get_1c_saved_state_latest_watch_run
mode: local
description: "Return the latest saved-state watch run under an output root."
status: draft
command: scripts/get_1c_saved_state_latest_watch_run.py
output_schema: onec_saved_state_latest_watch_run.v1
preferred_for:
- "Agent needs the most recent saved-state observation and linked artifacts."
- "User asks for the latest watch result or latest run with delta changes."
safety:
- "Read-only filesystem lookup."
- "Can require a run with delta or a run with actual delta changes."
- "Returns found=false instead of guessing when no run matches."
- "Writes a Markdown latest-run report by default when --output is provided unless --skip-markdown is used."
- "Runs check_1c_saved_state_latest_watch_run as a final contract gate when --output is provided unless --skip-check is used."
- id: render_1c_saved_state_latest_watch_run_markdown
mode: local
description: "Render latest saved-state watch run lookup JSON as compact Markdown."
status: draft
command: scripts/render_1c_saved_state_latest_watch_run_markdown.py
output_schema: markdown
safety:
- "Read-only rendering."
- "Consumes onec_saved_state_latest_watch_run.v1 JSON."
- "Shows matching requirements, found status, latest run artifacts, counts, and safety."
- id: check_1c_saved_state_latest_watch_run
mode: local
description: "Validate latest saved-state watch run lookup JSON and linked run/check artifacts."
status: draft
command: scripts/check_1c_saved_state_latest_watch_run.py
output_schema: onec_saved_state_latest_watch_run_check.v1
safety:
- "Read-only validation."
- "Accepts found=false when no run matches the requested requirements."
- "Rejects missing linked run directories, report files, or check status mismatches."
- id: render_1c_saved_state_watch_run_list_markdown
mode: local
description: "Render saved-state watch run list JSON as compact Markdown."
status: draft
command: scripts/render_1c_saved_state_watch_run_list_markdown.py
output_schema: markdown
safety:
- "Read-only rendering."
- "Consumes onec_saved_state_watch_run_list.v1 JSON."
- "Shows latest run, filters, check statuses, artifact links, and delta counts."
- id: check_1c_saved_state_object_report_delta
mode: local
description: "Validate saved-state report delta JSON, linked Markdown, safety flags, counts, object arrays, payload roles, and changed-part structure."
status: draft
command: scripts/check_1c_saved_state_object_report_delta.py
output_schema: onec_saved_state_object_report_delta_check.v1
safety:
- "Read-only validation."
- "Rejects missing linked Markdown when the delta JSON declares one."
- "Rejects unstable object arrays and unknown payload roles."
- "Rejects count mismatches and changed objects with identical before/after fingerprints."
- id: render_1c_saved_state_object_report_delta_markdown
mode: local
description: "Render saved-state report delta JSON as compact Markdown."
status: draft
command: scripts/render_1c_saved_state_object_report_delta_markdown.py
output_schema: markdown
safety:
- "Read-only rendering."
- "Consumes onec_saved_state_object_report_delta.v1 JSON."
- "Keeps changed 1C objects and system changes separate."
- id: render_1c_saved_state_object_report_markdown
mode: local
description: "Render saved-state object report JSON as a compact human-readable Markdown report."
status: draft
command: scripts/render_1c_saved_state_object_report_markdown.py
output_schema: markdown
safety:
- "Read-only rendering."
- "Consumes saved-state report, comparison, and detail JSON files."
- "Keeps SQL storage names as evidence while leading with 1C configurator objects."
- "Limits diff output by default."
- id: generate_bsl_snippet
mode: local
description: "Generate a small BSL snippet from a user task."
status: planned
- id: propose_1c_change
mode: local
description: "Create a reviewable change proposal. Never applies changes directly."
status: draft
policy: plugins/1c/connector/policies/change-workflow.yaml
- id: validate_1c_patch_workspace_semantics
mode: local
description: "Validate editable patch workspace BSL/Form.xml semantics before review or packaging."
status: draft
command: scripts/validate_1c_patch_workspace_semantics.py
output_schema: onec_patch_workspace_semantic_validation.v1
guarantees:
- "Parses Form.xml files and rejects invalid XML."
- "Parses BSL routine declarations and rejects duplicate routines."
- "Checks basic BSL block balance for procedures, functions, If/For/While/Try blocks."
- "Checks form command actions against routines in the matching form module when both files are present."
- id: edit_1c_bsl_routine
mode: local
description: "Append, replace, or upsert one BSL procedure/function under a patch workspace working/ file."
status: draft
command: scripts/edit_1c_bsl_routine.py
output_schema: onec_bsl_routine_edit.v1
safety:
- "Only edits BSL modules listed in the workspace manifest."
- "Only writes under working/."
- "Runs semantic workspace validation after the edit."
- "Rolls the file back by default when semantic validation fails."
- id: edit_1c_form_command
mode: local
description: "Append, replace, or upsert one Form.xml command under a patch workspace working/ file."
status: draft
command: scripts/edit_1c_form_command.py
output_schema: onec_form_command_edit.v1
safety:
- "Only edits Form.xml files listed in the workspace manifest."
- "Only writes under working/."
- "Runs semantic workspace validation after the edit."
- "Rolls the file back by default when semantic validation fails."
limits:
- "Edits only the root Commands collection; it does not add visible buttons/items yet."
- id: edit_1c_form_button
mode: local
description: "Append, replace, or upsert one visible Form.xml button under a named parent form item."
status: draft
command: scripts/edit_1c_form_button.py
output_schema: onec_form_button_edit.v1
safety:
- "Only edits Form.xml files listed in the workspace manifest."
- "Only writes under working/."
- "Requires the target form command to exist before adding the button."
- "Runs semantic workspace validation after the edit."
- "Rolls the file back by default when semantic validation fails."
- id: add_1c_form_button_workflow
mode: local
description: "Atomically add or update a BSL handler, Form.xml command, and visible Form.xml button."
status: draft
command: scripts/add_1c_form_button_workflow.py
output_schema: onec_form_button_workflow.v1
preferred_for:
- "User asks to add a visible form button."
- "Agent needs to create the BSL handler, command, and button together."
safety:
- "Only edits files listed in the workspace manifest."
- "Only writes under working/."
- "Snapshots all working/ manifest files before editing."
- "Restores the snapshot by default when any step fails."
- "Runs semantic workspace validation and returns a diff summary."
- id: create_1c_patch_bundle
mode: local
description: "Create a zip review bundle from a ready_for_review patch workspace without applying changes."
status: draft
command: scripts/create_1c_patch_bundle.py
output_schema: onec_patch_bundle_creation.v1
safety:
- "Requires preflight status ready_for_review."
- "Copies only modified working/ files and review evidence."
- "Does not write to source extension files, SQL, Config, ConfigSave, or ConfigCAS."
- "Runs bundle validation after creation."
- id: check_1c_patch_bundle
mode: local
description: "Validate a 1C patch review bundle directory and optional zip archive."
status: draft
command: scripts/check_1c_patch_bundle.py
output_schema: onec_patch_bundle_check.v1
safety:
- "Read-only validation."
- "Checks manifest, preflight, diff, copied modified-file hashes, and zip contents."
- id: create_1c_extension_staging_from_bundle
mode: local
description: "Create a disposable extension XML staging copy from a validated patch bundle."
status: draft
command: scripts/create_1c_extension_staging_from_bundle.py
output_schema: onec_extension_staging_creation.v1
safety:
- "Requires a valid patch bundle."
- "Copies the source extension directory to a staging directory."
- "Overlays only bundle modified files into staging."
- "Does not modify source extension files, SQL, Config, ConfigSave, or ConfigCAS."
- "Marks the staging copy as requiring disposable 1C validation."
- "Runs staging validation after creation."
- id: check_1c_extension_staging
mode: local
description: "Validate a disposable extension XML staging copy."
status: draft
command: scripts/check_1c_extension_staging.py
output_schema: onec_extension_staging_check.v1
safety:
- "Read-only validation."
- "Checks staging manifest schema and safety flags."
- "Checks staged file hashes against the bundle working hashes."
- "Checks source extension files still match the hashes recorded during staging."
- "Revalidates the source review bundle."
- id: create_1c_extension_validation_plan
mode: local
description: "Create a disposable-base validation plan for a staged 1C extension copy."
status: draft
command: scripts/create_1c_extension_validation_plan.py
output_schema: onec_extension_validation_plan.v1
safety:
- "Does not launch 1C or modify any base."
- "Requires a passing staging check."
- "Requires a passing runner config check when --runner-config is provided."
- "Marks production base, SQL writes, and source extension writes as forbidden."
- "Returns required 1C validation checks and evidence files for a future runner."
- id: check_1c_extension_runner_config
mode: local
description: "Validate safe runner configuration for disposable 1C extension validation."
status: draft
command: scripts/check_1c_extension_runner_config.py
output_schema: onec_extension_runner_config_check.v1
safety:
- "Read-only validation."
- "Rejects production-like base references."
- "Rejects secrets and credentials in runner config."
- "Requires explicit disposable_base_confirmed=true."
- id: create_1c_extension_validation_evidence
mode: local
description: "Create manual evidence templates for a 1C extension validation plan."
status: draft
command: scripts/create_1c_extension_validation_evidence.py
output_schema: onec_extension_validation_evidence_manifest.v1
safety:
- "Does not launch 1C or modify any base."
- "Requires validation plan status ready_for_disposable_validation."
- "Creates pending evidence templates only."
- "Does not mark validation as passed."
- id: check_1c_extension_validation_evidence
mode: local
description: "Check manual evidence files for a 1C extension validation plan."
status: draft
command: scripts/check_1c_extension_validation_evidence.py
output_schema: onec_extension_validation_evidence_check.v1
safety:
- "Read-only validation."
- "Requires every expected evidence file to exist."
- "Rejects evidence files that still contain pending templates."
- "Requires explicit passed/success/ok evidence status."
- "Rejects secret-like key/value text in evidence files."
- id: check_1c_extension_validation_release
mode: local
description: "Aggregate final validation gates for a staged 1C extension."
status: draft
command: scripts/check_1c_extension_validation_release.py
output_schema: onec_extension_validation_release_check.v1
safety:
- "Read-only validation."
- "Requires validation plan status ready_for_disposable_validation."
- "Requires staging check and evidence check to pass."
- "Never allows automatic production apply."
- "Returns validated_for_human_review only after all gates pass."
- id: render_1c_extension_validation_release_markdown
mode: local
description: "Render 1C extension validation release check JSON as Markdown."
status: draft
command: scripts/render_1c_extension_validation_release_markdown.py
output_schema: markdown
safety:
- "Read-only rendering."
- "Preserves the no automatic production apply safety message."