Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -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())
|
||||
@@ -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."
|
||||
Reference in New Issue
Block a user