Complete name-first 1C adapter saved-state support

This commit is contained in:
2026-07-26 16:39:53 +03:00
parent b8c62fa8fa
commit aed134d817
44 changed files with 15436 additions and 697 deletions
+60 -2
View File
@@ -62,8 +62,66 @@ independent metadata properties and must be filtered as derived evidence.
The `test2` experiment on 2026-07-15 added a minimal calculation register and
its required chart of calculation types through Designer, then saved the
extension without applying it. The objects remain intentionally `saved_only`
for adapter regression checks.
extension without applying it. That historical fixture was later removed from
the live saved state. The observations below remain decoder evidence, but they
must not be interpreted as proof that the current `upo_test` still contains the
object.
The reproducible fixture requirements are declared in
`config/1c_metadata_kind_fixtures.json`. Validate the declaration offline:
```powershell
python scripts/check_1c_metadata_kind_fixtures.py
```
Probe the current adapter and test base:
```powershell
python scripts/check_1c_metadata_kind_fixtures.py --live
```
Use `--require-ready` only as a strict regression gate after the fixtures have
been loaded with 1C Designer. The checker never creates metadata and never
writes platform SQL. `CalculationRegister` and `Sequence` belong in the saved
`test2` extension. Legacy `Interface` requires a dedicated compatible test
base, supplied explicitly as
`--target-base interface_legacy_base=<base_id>`; it must not be simulated in a
modern configuration by editing `Config`, `ConfigCAS`, or `DBNames`.
At the 2026-07-26 verification point, the `upo_test` server required platform
`8.5.1.1236`. The exact Designer build is now installed at
`C:\Program Files\1cv8\8.5.1.1236\bin\1cv8.exe`; the previous client-version
blocker is closed.
Export the current saved `test2` sources through operating-system integrated
authentication only:
```powershell
.\scripts\export_1c_extension_sources.ps1 `
-OutputDirectory C:\Temp\upo-test-test2-source
```
The export helper verifies the exact executable version, refuses a non-empty
output directory, always passes `/WA+`, and has no user or credential
parameters. It only reads and exports the extension; it does not load, save, or
apply a configuration.
The current workstation account is not mapped to an infobase user on
`wsr\upo_test`, so the server export stops with
`Пользователь ИБ не идентифицирован`. A disposable local file infobase needs a
client license and stops with `Не найдена лицензия`. Therefore provisioning is
still blocked by execution environment, not by adapter code or platform
version. Continue on a licensed workstation under an already authorized
operating-system identity; do not add a user name or password to scripts,
manifests, or reports, and do not bypass the block with direct SQL.
An external structural reference is pinned to
`asosnoviy/go1cover@c7e3ea3d90396d341cb98285c29973bcc0a82eca`,
path `testresourse/cf/designer/src`. It contains examples of
`CalculationRegister`, `Sequence`, and legacy `Interface`. The upstream
repository is GPL-3.0; its files are reference input only and are not vendored
into this project. The pinned source and per-kind paths are declared in the
fixture manifest.
Observed read-only SQL signatures in `ConfigCASSave`:
+179
View File
@@ -0,0 +1,179 @@
# Политика хранилища по слоям конфигурации 1С
Каждая конфигурация имеет собственную политику: основная конфигурация — слой
`base`, каждое расширение — слой `extension:<GUID>`. Подключение основной
конфигурации к хранилищу не означает подключения расширения и наоборот.
Адаптер работает с метаданными 1С только через SQL. Он не считает SQL-таблицы
доказательством нативного захвата объекта в хранилище.
Заявки на захват и явные ручные подтверждения — это служебные данные адаптера,
а не данные 1С. Они хранятся в `/data/onec-repository-locks.json` в named
volume Docker `adapter-1c-data` и переживают пересоздание контейнера. Это
хранилище не используется для вывода о нативном захвате и не требует записи в
какую-либо базу 1С.
## Настройка
В `development_layers` для каждого фактически используемого слоя задаётся
`repository.connection_state`:
```json
{
"base": {
"repository": {"mode": "manual", "connection_state": "configured"},
"support": {"mode": "unknown"}
},
"extension:86d12be8-086c-11f0-925c-0050568c1b38": {
"repository": {"mode": "none", "connection_state": "not_configured"},
"support": {"mode": "unknown"}
}
}
```
Допустимые состояния:
| Состояние | Политика записи |
| --- | --- |
| `not_configured` | Слой не подключён к хранилищу; захват не требуется. Ограничения поддержки сохраняются. |
| `configured` | Подключение известно. В SQL-only режиме нужен запрос и явное подтверждение захвата. |
| `unavailable` | Подключение предполагается, но проверить его нельзя; нужен запрос и явное подтверждение захвата. |
| `unknown` | Не делать предположений: запись в слой блокируется до настройки. |
Для ручного подтверждения всегда передаётся точный объект и явный флаг
`user_confirmed_locked`. Имя пользователя хранилища берётся из
`repository_user` настройки данного слоя. Поэтому при заполненной настройке
его не нужно повторять в каждой заявке или подтверждении. Если клиент всё же
передаёт `confirmed_repository_user`, оно должно совпадать с настроенным
именем. Если имя не настроено, подтверждение попросит его явно.
## Сохранение факта подключения агентом
Агент может сохранить подтверждённый факт в служебной конфигурации адаптера
через `repository.layer.connection.set`. Метод не меняет SQL-базу 1С и требует
явного флага подтверждения.
Основная конфигурация подключена к хранилищу:
```json
{
"method": "repository.layer.connection.set",
"payload": {
"base_id": "neft",
"layer_id": "base",
"connection_state": "configured",
"repository_user": "ivanov",
"confirm_repository_connection_change": true
}
}
```
Расширение точно не подключено:
```json
{
"method": "repository.layer.connection.set",
"payload": {
"base_id": "neft",
"extension_guid": "86d12be8-086c-11f0-925c-0050568c1b38",
"connection_state": "not_configured",
"confirm_repository_connection_change": true
}
}
```
При `not_configured` адаптер устанавливает режим слоя `none`. При
`configured` или `unavailable` новый слой по умолчанию получает ручную
политику захвата. Рекомендуется сразу передать постоянный
`repository_user`: он сохраняется только в конфигурации адаптера для этого
слоя и используется во всех последующих ручных подтверждениях.
## Ручное подтверждение захвата
После `repository.lock.request` клиент должен использовать возвращённый
`next_call` без самостоятельного подбора имён полей. Поля из
`next_call.params` передаются как RPC `payload`; его эквивалент:
```json
{
"method": "repository.lock.confirm",
"payload": {
"base_id": "neft",
"request_id": "rreq-…",
"user_confirmed_locked": true
}
}
```
`objects`, `operation` и `layer_id` не передаются повторно: подтверждение
берёт их из неизменяемой сохранённой заявки. Если имя пользователя сохранено
для слоя, оно тоже не передаётся. Успешный ответ содержит `lock_session_id`;
его необходимо передать в preflight и операцию записи.
Ответ `repository.lock.confirm` содержит готовый `write_context`. Его можно
передать целиком либо верхнеуровневым полем `write_context`, либо алиасом
`repository_lock`; адаптер нормализует поля до проверки захвата:
```json
{
"base_id": "neft",
"target": {"module_id": "Config:<form-guid>.0"},
"repository_lock": {"lock_session_id": "rlock-…", "repository_object": "Document.Имя.Form.ИмяФормы", "layer_id": "base"}
}
```
Если одновременно переданы вложенное и верхнеуровневое значение, они должны
совпадать; иначе возвращается `repository_lock_context_conflict`.
## Проверка
`repository.layers.audit` обнаруживает основную конфигурацию и расширения из
live SQL и выдаёт для каждого слоя состояние хранилища, общий режим поддержки
и безопасное следующее действие:
```json
{
"method": "repository.layers.audit",
"payload": {"base_id": "neft"}
}
```
Ключевые действия в ответе:
- `repository_lock_not_required` — слой явно не подключён к хранилищу;
- `request_and_confirm_repository_capture` — перед записью требуется
подтверждение захвата;
- `configure_repository_connection_state` — слой нельзя менять, пока не
установлен один из статусов подключения.
Проверка поддержки выполняется отдельно от хранилища. Для конкретного объекта
используйте `metadata.write.preflight`: разрешение объекта определяет его
`origin`, и адаптер применяет политику найденного слоя, а не клиентский выбор
слоя.
## Захват формы владельца
Для формы документа, справочника, обработки или отчёта область захвата задаётся
точно, без сворачивания к владельцу:
```json
{
"method": "repository.lock.request",
"payload": {
"base_id": "neft",
"objects": ["Document.тл_Планировщик.Form.ФормаДокументаНовая"],
"operation": "modify"
}
}
```
До создания заявки `repository.lock.plan` и сама заявка разрешают форму через
live SQL, получают GUID формы и сохраняют точную каноническую область. После
ручного подтверждения эта сессия не даёт права на соседние формы или модуль
владельца.
Также принимается `Form.<GUID>`. Для него адаптер проверяет связь формы с
владельцем по live SQL; при отсутствии готовой производной записи владельца
выполняется ограниченный SQL-скан форм поддерживаемых типов владельцев. Если
владелец не найден, планирование и заявка завершаются `not_found`, а не
возвращают ложный `ready`.
+41 -1
View File
@@ -126,7 +126,10 @@ REST and MCP selector-chain smoke tests. The selector-chain smoke also checks,
when a routine name is available from module metadata, that
`metadata.resolve_overrides` returns `write_plan_evidence.next_resolution` for
`metadata.saved_state.modules.search` with the same `base_id` and routine query,
then follows that resolver. If a saved-state stream exposes `write_plan_target`,
plus a public object `ref`/`kind`/`name` and semantic
`layer=base_saved_state|extension_saved_state`; public follow-ups never require
`ConfigSave`, `ConfigCASSave`, or `owner_guid`. The smoke then follows that
resolver. If a saved-state stream exposes `write_plan_target`,
the smoke composes it with `write_plan_evidence.target` and checks the resulting
read-only `metadata.write.plan`; when the test base has no saved-state stream,
that last composition step records `skipped_no_saved_state_target`.
@@ -506,6 +509,23 @@ roles/users can read or write this metadata object". Do not depend on
`metadata.objects.list kind=Role`: some live bases do not expose roles as regular
metadata objects through that route.
For BSP data-access keys, keep the metadata object and concrete data record
separate:
```json
{
"base_id": "upo_test",
"kind": "object",
"object_ref": "Справочники.Номенклатура",
"record_ref": "00112233445566778899AABBCCDDEEFF"
}
```
Here `kind=object` is the `access_keys_query` area, not the metadata kind.
`object_ref` is resolved to the internal BSP SQL number and `record_ref` to the
record id. The same public pair works with `access_object_keys_resolve` and
`access_object_explain`; do not invent `object_sql_number` in MCP code.
For BSP role-right checks, `action=write` means add or modify rights. Read-only
roles must not be returned for `write`; query `action=read` separately when you
need visibility roles. Returned permissions keep `source_fields` from
@@ -634,6 +654,26 @@ Agent rules:
`metadata.form.command_button.write` for the complete form command + visible
button + handler workflow. `code.write` remains a compatibility shortcut for
simple module edits.
- For a metadata object's standard `synonym` or `comment`, call generic
`onec_request` with `method=metadata.object.property.write`, a public
`ref`/`kind` + `name`, and `allow_saved_state_write=true`. Prefer
`execution_mode=plan` first. The adapter resolves GUIDs and serialized paths;
MCP callers must not pass SQL tables or file names. Object rename and adding
a new synonym locale are intentionally unsupported.
- For an existing requisite, tabular section, dimension, or resource, add a
public `member_ref` such as
`Catalog.Номенклатура.Attribute.Артикул`. When only
`member_kind` + `member_name` is used and several identities match, preserve
the adapter's `ambiguous` result and retry with the full member path.
- To add a requisite, call `metadata.object.member.add` through `onec_request`
with a full `template_member_ref` from the same object and the new public
name/synonym. Never invent a GUID, Config table, collection path, or type
descriptor in MCP code; the new requisite inherits type/settings from the
named template.
- The same method adds a tabular-section column when
`template_member_ref` contains the full
`...TabularSection.<name>.Attribute.<column>` path. Keep the complete path so
duplicate checks and generated identity remain scoped to that table part.
- For unresolved module owners, inspect `diagnostics.owner_resolution` and
`counts.owner_scan_limit_hit`; narrow by `kind`/`name`/`guid` or increase
`owner_scan_limit` before falling back to broader searches.