Initial project import

This commit is contained in:
2026-08-14 09:40:51 +03:00
parent 00040e5ce4
commit d7099bf80d
146 changed files with 30509 additions and 1055 deletions
+340 -32
View File
@@ -2,22 +2,18 @@
## Configuration repository control
The current adapter release is SQL-only. It does not start Designer, call a
Windows runner, or inspect repository internals. External 1C execution is a
future-version capability and is disabled by default with
`ONEC_ADAPTER_ENABLE_EXTERNAL_1C=false`.
The adapter is SQL-only. It does not start, automate, emulate, or require
Designer/Configurator. Configurator actions are performed by a human and are
outside the adapter's execution boundary.
Repository operations are available through `repository.status`,
`repository.lock.plan`, `repository.lock`, `repository.lock.confirm`, `repository.lock.verify`,
`repository.unlock`, `repository.commit.plan`, and `repository.commit`.
Configuration is selected only by `payload.base_id`: the base runtime profile
declares `repository.backend=direct|karman_bridge`, the Designer executable,
infobase selector, endpoint, optional extension, users, and environment-variable
names containing transient passwords. No repository, bridge, or endpoint name is
hard-coded or inferred from naming conventions.
Configuration is selected only by `payload.base_id`. No Designer executable,
Configurator endpoint, bridge, or external 1C runner is used by this adapter.
`repository.lock_mode=automatic|manual` is also selected per base. Automatic
mode uses the configured runner. Manual mode requires no Designer or runner:
`repository.lock_mode=manual` is selected per base. Manual mode requires no
Designer integration:
`repository.lock.plan` returns the exact public development-object names to
lock in Configurator, and `repository.lock.confirm` records the user's explicit
confirmation for only that object set. Such a session is marked
@@ -43,13 +39,11 @@ further SQL writes through that session.
The adapter keeps a bounded audit trail of request creation, confirmation,
cancellation, and closure and exposes it to the administrative requests view.
Both backends invoke standard Designer repository commands. A Karman/Filebox
backend is an opaque native TCP transport and does not own credentials, object
locks, or repository transactions. For configured bases, saved-state apply is
blocked until the caller supplies an active adapter-owned `lock_session_id`.
Commit additionally requires `allow_repository_commit=true` and a non-empty
version comment. Unlock and commit operate only on the object set recorded for
that adapter session.
Repository coordination records only the human-confirmed scope. It does not
invoke Designer repository commands and does not prove a native lock. For
configured bases, saved-state apply is blocked until the caller supplies an
active adapter-owned `lock_session_id`; that session is coordination evidence,
not a native repository transaction.
Status: draft, read-only first.
@@ -64,6 +58,30 @@ Related work plan: `docs/1c-extension-layer-plan.md`.
- When XML-derived rules are promoted into the adapter, the runtime write path
must still resolve to concrete SQL storage targets such as `ConfigSave` or
`ConfigCASSave`, with explicit gates and readback verification.
- SQL readback is not proof that Configurator accepts, displays, saves, or
activates a change. Only a human may provide that confirmation.
- The adapter must reject, rather than fabricate, a `ConfigSave`/
`ConfigCASSave` container or signature whose SQL protocol is not proven.
## Data Composition Schema Query Writes
`scd.patch` changes only one proven direct XML scalar: a dataset `query`, a
calculated-field `expression`, or a resource `expression`. It resolves the
report and SCD by public 1C names, preserves the surrounding SQL payload bytes
and compression envelope, and never accepts a storage key from a caller.
For `execution_mode=apply`, `apply_and_verify`, or `apply_and_rollback`, a
missing saved SCD payload is prepared internally through the established
saved-state copy codec when `allow_sql_saved_state_prepare=true` is supplied.
The caller still explicitly authorizes the eventual edit with
`allow_saved_state_write=true` and `allow_sql_saved_state_apply=true`.
`apply_and_rollback` additionally requires
`allow_sql_saved_state_rollback=true`. It restores the exact apply backup, not
a newly encoded copy of the old query. If this request created the saved-state
overlay, it then removes only receipt-recorded rows after hash precondition
checks. A successful result proves SQL readback and cleanup only; it does not
prove Configurator visibility, acceptance, or activation.
## User Identity And Access Terminology
@@ -123,8 +141,8 @@ Password mutation is available only for the platform `infobase_user` layer:
`allow_administrator_password_change=true`;
- normally both mutations are blocked when `ONEC_ADAPTER_SERVICE_TOKEN` is
empty. A disposable isolated test stand may explicitly set
`ONEC_INFOBASE_USER_ADMIN_ALLOW_UNAUTHENTICATED=true`; this also permits an
unprotected runtime bridge endpoint and must never be enabled in production.
`ONEC_INFOBASE_USER_ADMIN_ALLOW_UNAUTHENTICATED=true`; it affects only this
isolated SQL operation and must never be enabled in production.
Both operations select the exact user through `infobase.user.get` and update
only `dbo.v8users.Data`. The clear-text password for `set` exists only in memory
@@ -411,6 +429,64 @@ Purpose:
Normal coding agents should write BSL through `code.write`, not through SQL,
storage rows, payload paths, or `metadata.module.write_apply`.
### Working-state and saved-state ownership rule
An agent works only with the effective working configuration. It reads the
saved development version when one exists and otherwise reads active code; it
does not determine whether `ConfigSave` or `ConfigCASSave` exists, and it must
not call a separate saved-state preparation operation as part of a normal code
change.
The adapter owns that transition. A `concrete_reference` returned by
`code.search` or `code.read` is an opaque proof of the exact active module
stream, extension, and layer—not permission to write active storage. After
preflight has resolved that reference, a normal apply must do the following
inside the adapter:
`code.write` accepts the same reference either directly or inside
`write_selector.target`. It verifies the supplied public `ref`, extension,
layer, canonical path, and routine against that exact stream, then routes it
to the saved copy without repeating `metadata.object.modules` discovery.
For one apply request the adapter authorizes the public extension scope once
before preparation. Its internal prepare and writer stages reuse that
process-bound authorization; they must not repeat broad support/owner scans
against `ConfigCASSave`. If authorization is absent, the request returns a
bounded `blocked` result before any support scan or saved-state mutation.
For a client transport deadline shorter than the adapter write budget, call
`adapter.job.start` with `method="code.write"` and the same public payload,
then poll `adapter.job.get`. This preserves the final structured write result;
the caller must not retry the SQL write while the job is running.
1. verify the exact extension/layer and active source stream;
2. create a missing saved-state copy through the proven
`Config`/`ConfigCAS` to `ConfigSave`/`ConfigCASSave` copy path;
3. apply the guarded edit only to saved state;
4. make a backup, validate BSL and the old-text/SHA preconditions, and reread
the written result; and
5. support the requested rollback, including removal or restoration of any
adapter-created saved-state copy.
`metadata.write.preflight` reports such a first write as `status=ready` with
`route.preparation.status=adapter_managed`,
`route.preparation.caller_action_required=false`, and
`saved_state.status=will_prepare`. `needs_prepare` is reserved for a route
whose copy protocol is not sufficiently proven for the adapter to execute.
The adapter must never write the active `Config` or `ConfigCAS` layer.
For a hash-keyed extension overlay, this internal copy includes the live
extension root manifest (`root_cas_key`) and the confirmed object parts. An
exact BSL stream reference remains the write target, but must not reduce the
saved-state preparation to that leaf stream alone.
The caller never invokes or waits for this preparation as a separate workflow:
`code.write` performs the bounded copy, write, verification, and readback as
one adapter operation. Its preparation gate is restricted to the confirmed
extension layer and public object; it does not perform a whole-extension
support scan before copying proven source parts.
`code.write` accepts 1C names and code text:
```json
@@ -428,11 +504,21 @@ storage rows, payload paths, or `metadata.module.write_apply`.
Contract:
- default `mode` is `apply`, and apply means save to the working
`ConfigSave`/`ConfigCASSave` layer, not production apply;
- default `mode` is `plan` and does not write SQL. `apply`,
`apply_and_verify`, and `apply_and_rollback` must be requested explicitly;
they save only to the working `ConfigSave`/`ConfigCASSave` layer, not to
production;
- every `code.write` response includes `write_mode.target=saved_state`,
`write_mode.activation_state=not_activated`, and
`write_mode.production_apply=false`;
- the normal agent view is the effective working configuration: a matching
`ConfigSave`/`ConfigCASSave` overlay is read ahead of active code. Agents
do not inspect, create, or select saved-state rows. For a first extension
write, the adapter takes the exact active module handle returned by
`code.search`/`code.read`, creates the proven `ConfigCASSave` copy inside
its guarded apply workflow, and then edits that copy. Preflight reports this
as `route.preparation.status=adapter_managed`; no separate prepare call is
required from the agent.
- saved-state `code.read` and `code.search` responses include
`current_state.source=saved_state` and
`current_state.activation_state=not_activated`;
@@ -455,7 +541,9 @@ Contract:
For embedded form modules the adapter writes only the scalar module token in
the saved form payload with `path_preserve_format`. Whole-form payload
canonicalization is forbidden because Designer may reject the form even if the
payload decoder can parse it.
payload decoder can parse it. A write plan for such a container returns a ready
`code.write` apply hint rather than asking the caller to invent a
`#stream:<index>`.
## Resolve Object
@@ -601,6 +689,22 @@ Important methods:
Constants are exposed as a typed `value`; enumeration rows include their
public value `name`, `synonym`, and `value_ref`. Business-process storage is
resolved through the platform `_BPr<N>` route internally.
- `additional_attributes.find` is the read-only entry point for additional
requisites. It reads a public `ChartOfCharacteristicTypes` selector (by
default `ДополнительныеРеквизитыИСведения`) and accepts `query` for a
description search. Its `empty_source` result is deliberately distinct from
`not_found`: it means that the resolved chart route has no records in the
selected infobase, so no property reference or value type can be claimed.
`additional_attributes.storage.resolve` requires a confirmed 32-hex
`property_ref` plus `owner_ref`. It searches candidate information registers
and returns a storage join only after live metadata proves dimensions
`Объект` and `Свойство`, a `Значение` resource, and that `Свойство` is a
reference to the selected PВХ. Otherwise it returns `unresolved` and never
guesses physical `_Fld...` columns.
For `ChartOfCharacteristicTypes`, `data.schema` and the find result also
expose chart-level `allowed_value_types` with its live Config evidence path.
This is explicitly not represented as the type of an individual property
record until that record's `ТипЗначения` route is decoded.
- `data.present` returns a compact presentation for one `record_ref`, and
`data.movements` reads register rows for a `recorder_ref`.
- BSP access-key methods use the same object/record separation without
@@ -728,6 +832,10 @@ Important methods:
`Модуль сервиса интеграции`. Handler names decoded by
`metadata.object.properties` can therefore be followed directly into their
live SQL module routines.
- Exact extension objects are resolved by the same public `kind` + `name` or
`ref` selectors as base objects. `metadata.object.modules` also returns form
modules owned by the selected object, with qualified 1C names and without
exposing CAS keys unless `include_storage=true`.
- The same module APIs expose the four configuration-level modules through the
public `Configuration.<Name>` selector: ordinary application, external
connection, managed application, and session. Runtime discovery reads the
@@ -859,6 +967,15 @@ Important methods:
`module_ref` values remain valid for narrow follow-up cleanup. The method
changes only adapter-local SQLite state, never the 1C SQL database; use
`dry_run=true` to inspect the matching count without deleting cache rows.
- `metadata.module_owner_cache.backfill` incrementally builds the reverse
`module_ref -> 1C object` map from current metadata. One call processes a
bounded object page and returns `next_cursor` (`kind_index`, `kind`,
`offset`) until `complete=true`. The discovered public owner is propagated
to existing lexical/vector code-index rows, including the matching
active/saved-state table pair. By default objects without code-index rows
are skipped so the operation remains fast; `deep=true` explicitly enables
their slower metadata-module decoding. This changes adapter-local SQLite
only.
- For backward compatibility, a legacy bare `guid` without an owner selector
may still identify a form. `object_guid` never gets that legacy treatment.
- Logical schema results are cached briefly. `refresh_cache=true` forces a live
@@ -905,7 +1022,11 @@ Important methods:
code fragment. `state` is passed through to `modules.search`; the MCP
`source_state=working` policy maps to this `state=working` mode.
- `code.read`: wraps module/routine reads for agent-facing code analysis. It
may set `source.kind=code_read`, but it must preserve the module `origin`
accepts either `routine_name` or the inclusive `line_start`/`line_end` range.
A focused request may use a previously decoded local code-index snapshot
(`source.kind=code_index_cache`, `freshness.verified_against_sql=false`) to
avoid decoding a large container again; pass `prefer_code_index=false` for
the normal live SQL decode path. It must preserve the module `origin`
evidence from `modules.read` so write planning can still distinguish base,
saved state, extension, or unresolved CAS references.
- `metadata.adapter.audit`: reports recognized metadata kinds, public kind
@@ -1000,6 +1121,9 @@ Working source state:
`comparison.both_present` plus `comparison.differs`. When `include_text=true`,
top-level `text` is the effective programming text: saved-state text if it
exists, otherwise active text. `text_source` names the layer used.
For extension form modules, the saved counterpart is matched by extension,
logical form owner, and form GUID rather than by assuming identical active
and saved CAS file names.
- `code.search state=both` also returns a mixed view for saved CommonForm code:
saved-state matches are listed first, active matches are fetched with an
independent `state=active` pass, and `counts.saved_matches` /
@@ -2103,6 +2227,15 @@ Selector rules:
row details. `include_storage=true` retains the low-level diagnostic response.
`plan` remains the default and performs no write; `apply` and
`apply_and_verify` still require `allow_sql_saved_state_prepare=true`.
- `metadata.saved_state.ensure` is the preferred idempotent public facade for
that operation. It resolves `ConfigSave` or `ConfigCASSave` from the public
object/extension selector, copies only missing active parts, and never asks a
user to create a missing save layer in Configurator. It remains a plan by
default; its apply modes use the same explicit SQL gate.
- `metadata.saved_state.ensure.rollback` removes only rows inserted by the
opaque receipt returned from an ensure apply. It SHA-checks every row first,
requires `allow_sql_saved_state_rollback=true`, and never touches active
`Config` or `ConfigCAS`.
- Every public RPC `next_resolution`/`next_call` entry uses `{method, params}`.
`payload` is reserved for the outer RPC request envelope and internal
apply-hint bodies; it must not be used as the arguments field of a public
@@ -2134,6 +2267,74 @@ Selector rules:
accepts the backward-compatible `table=ConfigSave|ConfigCASSave`, compares
saved rows with their active source by file part, size, and SHA1, and returns
per-file `changed`, `unchanged`, or `saved_only` statuses.
- `configuration.activation.status` is the read-only activation boundary for
`saved_state -> active`. It checks both layers by default, or accepts
`layer=base_saved_state|extension_saved_state`, and delegates to the live SQL
saved-state comparison above. It never uses the code cache or vector index as
authority. `activation_required=true` means at least one `changed` or
`saved_only` object exists. If the configured scan limit is reached without a
detected difference, the result is `inconclusive` rather than a false
`up_to_date`.
- `configuration.activation.plan` uses that live status to build a read-only
handoff. When changes exist, it returns a `{method, params}` review call, a
manual Designer action (`Обновить конфигурацию базы данных`, F7), and a live
verification call whose expected status is `up_to_date`. The current adapter
does not start Designer or mutate the active configuration, even when
`ONEC_ADAPTER_ENABLE_EXTERNAL_1C` is enabled. A future execution method must
use a dedicated Designer bridge and separate explicit confirmation.
- `configuration.activation.request` creates an expiring adapter-local request
only when the live status is `activation_required`. The request fingerprint
includes the exact pending storage files and their saved/active SHA1 values;
storage coordinates and hashes are not returned as the public activation
status. Requests and lifecycle events are stored outside the infobase in the
local SQLite database selected by `ONEC_ADAPTER_STATE_DB`, in
`configuration_activation_requests` and
`configuration_activation_events`. The legacy JSON path selected by
`ONEC_CONFIGURATION_ACTIVATION_STATE_FILE` is imported once and then remains
read-only.
- `configuration.activation.execute` currently accepts only `mode=debug` and
requires `confirm_activation=true` plus the exact request id. It repeats the
live SQL read and rejects the request if it expired, the base differs, the
pending state disappeared, or any fingerprinted file changed. A successful
result is `debug_accepted`; Designer is not started and the active
configuration is not changed. Use
`configuration.activation.request.status` to inspect the adapter-local
request state.
- `configuration.activation.request.cancel` requires `confirm_cancel=true` and
records a lifecycle event for the exact request. Cancellation is idempotent
and has no 1C side effect. `configuration.activation.audit` returns a bounded
base-scoped list of request states and events without exposing the internal
per-file fingerprint evidence.
- `configuration.activation.capabilities` reports only boolean/configuration
readiness: runner kind, presence of a Designer path and infobase selector,
external-1C enable flag, and supported workflow gates. It never returns the
executable path, runner URL, infobase selector, users, passwords, or tokens.
The base layer reports documented `/UpdateDBCfg` as `debug_only`; extension
activation reports `manual_only` until a verified platform command is
implemented.
- `configuration.activation.bridge.probe` performs an explicit debug-only
readiness call. For a local runner it checks that the configured Designer
executable exists and exactly one infobase selector is present. For an HTTP
runner it calls `/configuration/activation/debug` using the existing runner
token configuration. Both paths return only booleans and runner kind:
Designer is not started, credential values are not read by the local probe,
and paths, URLs, selectors, users, and secret names are not returned.
- `configuration.activation.execute` accepts optional `bridge_debug=true`.
After all request, expiry, confirmation, live-SQL, and fingerprint checks
pass, the adapter sends only `base_id`, semantic layer, request id, and the
64-hex fingerprint to the debug runner. The runner must return a matching
request/fingerprint plus a 64-hex receipt. A missing, mismatched, or
not-ready receipt blocks acceptance. A valid receipt records the
`bridge_debug_accepted` lifecycle event; it still does not start Designer or
change the active configuration.
- `configuration.activation.verify` closes the manual activation loop for an
exact request. It repeats the live SQL comparison with no cache. The result
is `not_activated` when the original fingerprint is still pending,
`changed_since_request` when pending files differ from the confirmed
fingerprint, and `verified_up_to_date` only when saved and active layers
align. SQL alignment does not prove that Designer performed activation—it
can also result from discarding or replacing saved changes—so the response
reports `activation_proven=false`.
- `metadata.saved_state.changes.list` is the read-only pending-change overview
across the base and extension saved-state layers. Use the semantic
`layer=base_saved_state|extension_saved_state` filter when only one layer is
@@ -2178,7 +2379,11 @@ Freshness statuses:
- `cache_hit_verified`: cache candidate was rechecked against current SQL
`payload_sha1` and `text_sha1`;
- `cache_hit_stale`: cache candidate exists, but current SQL no longer matches;
- `cache_refreshed_from_sql`: a stale cache row was rebuilt from current SQL
and will be reranked before it can be returned;
- `vector_candidate_unverified`: vector result is only a retrieval candidate.
- `dirty_with_live_fallback`: local coverage is incomplete, so the response
includes a direct live-SQL search for newly added code.
RPC methods:
@@ -2195,24 +2400,91 @@ RPC methods:
}
```
- `metadata.code_index.status`: reports cache/module/vector chunk counts;
- `metadata.code_index.search`: fast lexical search over cached BSL, verifying
candidates by default;
- `metadata.code_index.status`: reports cache/module/vector chunk counts,
pending outbox events, overlay tombstones, and the local snapshot token;
- `metadata.code_index.search`: strict lexical search over cached BSL. Strict
mode is the default: it overfetches, verifies against live SQL, refreshes and
discards stale rows, and switches to live SQL while the index is dirty;
- `metadata.code_index.verify`: verifies one `module_ref` against live SQL;
- `metadata.code_index.refresh_changed`: verifies search candidates and refreshes
stale modules from SQL;
- `metadata.code_vector.search`: searches cached module/routine chunks with
local hashing embeddings or supplied `query_embedding`, then revalidates by
default.
- `metadata.code_index.sync_pending`: processes exact dirty/outbox targets after
saved-state writes;
- `metadata.code_index.poll_changes`: detects Configurator/external changes.
Saved tables are checked by default; use `include_active=true` for a less
frequent active-configuration scan;
- `metadata.code_vector.pending`: returns current BSL chunks that still need an
embedding for a requested model, with `chunk_id` and `text_sha1`
preconditions. Optional `chunk_kinds=["routine"]` and `max_text_chars`
support fast incremental passes without falsely marking skipped long chunks
as embedded;
- `metadata.code_vector.embedding.upsert`: stores an external embedding only
while those preconditions still match. Changed or removed chunks return
`conflict`;
- `metadata.code_vector.search`: hybrid lexical/vector search over cached
module/routine chunks. It overfetches, verifies live SQL, reranks after stale
refreshes, suppresses active rows shadowed by saved state, and returns
name-first `object_ref`/`object_selector` fields.
If older global index rows have empty owners, run
`metadata.module_owner_cache.backfill` page by page. Search also performs a
cheap local owner lookup for top candidates. For base `Config`/`ConfigSave`
modules whose part name starts with a GUID, the adapter resolves that GUID
against current metadata, requires an exact identity match, and persists the
verified public owner. It never launches a configuration-wide owner scan on
the hot search path.
For active extension `ConfigCAS` modules, the adapter follows the current
extension manifest from the exact CAS content hash to the owning descriptor,
decodes its public kind/name, and returns the extension name/GUID in both
`owner.extension` and `object_selector`. The manifest relation and descriptor
are cached locally only after this current-SQL resolution. Hashes are never
presented as object names.
`strict=true` and `verify=true` are the safe defaults. `strict=false` is a
diagnostic/candidate mode and must not drive programming changes.
The adapter keeps revision, dirty/outbox, tombstone, and vector data in the
local adapter SQLite database. It does not add project tables or settings to
the 1C SQL database.
External embeddings are optional. The worker supports deterministic local
hashing for tests and any OpenAI-compatible embedding endpoint:
```powershell
python scripts\embed_1c_code_vectors.py `
--base-id upo_test `
--embedding-provider openai-compatible `
--embedding-model <code-embedding-model> `
--embedding-base-url http://<embedding-host>:<port> `
--json
python scripts\search_1c_code_vectors.py "где рассчитывается налог" `
--base-id upo_test `
--embedding-provider openai-compatible `
--embedding-model <code-embedding-model> `
--embedding-base-url http://<embedding-host>:<port> `
--embed-pending `
--json
```
API keys are read only from the configured environment variable and are not
written to SQLite, responses, or project files. The embedding model label and
vector dimensions are stored so vectors from different models cannot be mixed.
For Matryoshka-capable OpenAI-compatible models the code worker includes the
requested dimension in the cache label (for example `@d384`), slices a longer
response deterministically, and normalizes the stored vector again.
Operational modes:
- `fast`: cache plus SQL hash verification;
- `fast`: strict cache search plus SQL hash verification;
- `live`: direct SQL search/read, slower but authoritative;
- `background_refresh`: intended for long cache warming jobs.
Never apply code changes from cache or vector output alone. Use the returned
`read_selector` after freshness is `cache_hit_verified` or read live SQL again.
`read_selector` after freshness is `cache_hit_verified`, then run the normal
live write preflight. A non-null snapshot token identifies a clean local index;
it is not a native 1C repository lock.
## Saved-State Form Search And Write Target Resolve
@@ -2353,12 +2625,23 @@ Purpose:
- build a routine chain for a concrete object and method name across base and
extension modules;
- include modules of forms owned by the selected object, including extension
objects resolved from a public `ref`/`kind`/`name` without requiring an
extension GUID or CAS key;
- apply the same source policy as code reads: `state=working` prefers the saved
form-module counterpart with active fallback, `state=save` is saved-only,
`state=active` is active-only, and `state=both` can report both versions;
- mark every chain link with `activation_state=active|saved_state`, so a saved
routine is never mistaken for already applied runtime code;
- return public read selectors for every found routine without exposing storage
ids by default;
- expose `chain[].extension_action` for each routine link. For base
configuration links this is `operation_class=base_definition`;
- for extension links, normalize known action evidence into
`insert_before`, `insert_after`, `replace`, or `replace_with_control`;
- classify an unannotated routine of an object defined by the extension itself
as `extension_definition`; it is not an unresolved interception of a base
routine and therefore does not trigger the unknown-action write guard;
- when a routine is found in an extension but the action metadata is not yet
resolved, return `extension_action.status=unknown` and
`operation_class=unknown_extension_action`. The agent must not treat this as
@@ -2967,6 +3250,31 @@ Purpose:
- return backup ids, source metadata, sha1 and byte counts without returning
rollback payload hex.
## Saved-State Backup Retention
RPC method:
```json
{
"method": "storage.saved_state.backups.prune",
"payload": {
"base_id": "<base-id>",
"table": "ConfigCASSave",
"older_than_days": 30,
"keep_latest": 20,
"limit": 500,
"dry_run": true
}
}
```
The method only inspects adapter-local backup JSON files. It is base-scoped,
defaults to `dry_run=true`, preserves `keep_latest` matching backups, and never
writes the 1C database. Actual deletion additionally requires
`dry_run=false` and `confirm_delete=true`. Backups referenced by
`metadata.write.history` are never selected for deletion. If history lookup is
unavailable for a base, its matching backup files remain protected.
## Kind Smoke
Command:
+5
View File
@@ -2,6 +2,11 @@
Date: 2026-06-20
The maintained evidence log for saved-state encoding, extension overlays,
Configurator reload behaviour, and failed protocol paths is split by topic in
[`docs/1c-sql-protocol/`](1c-sql-protocol/README.md). This specification keeps
only universal format rules.
This document describes universal 1C SQL storage rules observed and verified so
far. It must not contain knowledge about a particular infobase object such as
`АвансовыйОтчет`, except as test evidence in a separate report.
+46
View File
@@ -0,0 +1,46 @@
# SQL protocol knowledge base for 1C configuration storage
This directory is the durable, evidence-first record for the 1C SQL adapter.
It complements [the general format specification](../1c-sql-format-spec.md).
Do not put all observations into one chronological note: add a fact to the
document for its layer, object type, codec, operation, or experiment.
## Non-negotiable runtime rule
The adapter works **only through SQL**. It may decode and encode only what was
observed in the target database and proven by round-trip checks. It must never
invent a metadata object, physical path, BSL fragment, form element, join,
codec, or cryptographic value. Unknown or ambiguous work returns an explicit
unsupported/protocol-incomplete result.
The Configurator remains a human-operated consumer of the saved-state overlay.
The adapter does not start it, automate it, or claim to change its in-memory
state.
## Map
- [Rules and evidence discipline](rules/evidence-and-safety.md)
- [Agent contract: do not invent](rules/agent-contract.md)
- [Configuration SQL layers](layers/configuration-storage.md)
- [Extension saved-state overlay](extensions/saved-state-overlay.md)
- [Common form and BSL module handling](objects/common-form-module.md)
- [Object component graph and child selectors](objects/component-graph.md)
- [Report object-module carrier](objects/report-object-module.md)
- [Payload envelope codec](codecs/payload-envelope.md)
- [`__configinfo` map and service atom](codecs/configinfo.md)
- [Configurator refresh behaviour](operations/configurator-cache.md)
- [Reproducible `upo_test/test2` experiment](experiments/upo-test-test2.md)
- [Known dead ends and prohibited shortcuts](research/known-dead-ends.md)
- [Template for the next experiment](templates/experiment-record.md)
- [Current adapter component map](implementation/adapter-components.md)
## Maintenance rule
After every material protocol investigation, record all three outcomes:
1. proven direction and the exact evidence;
2. failed direction and why it failed;
3. remaining unknowns and the next safe experiment.
Keep raw payloads and credentials out of git. Reference private learning
artifacts by opaque ID or SHA-1 only.
+35
View File
@@ -0,0 +1,35 @@
# `__configinfo`: file map and service atom
## Proven map
The decoded `E__configinfo` text contains logical file-name pairs:
```text
"<object-guid>.0",<base64 of 20-byte SHA-1>
```
The Base64 value is the SHA-1 of the complete raw `BinaryData` stream for that
logical file. Updating a module without replacing this map value produces the
Configurator error “Ошибка хеш-версии файла конфигурации”.
The map rewrite is deterministic and losslessly round-trip proven for the
studied extension form module.
## Service atom: current status
The root block also contains three observed binary atoms: two 48-byte values
and a 32-byte value. The 32-byte value changed across manual saves, including
when the module bytes returned exactly to a previous SHA-1. A random mutation
of its variable part triggered a platform licensing/crypto error.
Therefore:
- preserve all service atoms byte-for-byte during the proven incremental
protocol;
- do not synthesise, randomise, zero, or “recalculate” them;
- do not claim their reverse codec is known;
- record new observations in an experiment file before changing this rule.
The `test2` map-update experiment activated successfully while preserving the
existing service atom. This is activation evidence for preservation, not for
generation.
@@ -0,0 +1,27 @@
# Payload envelope codec
## Observed codec pipeline
For the studied configuration payloads:
```text
SQL BinaryData
→ raw-deflate (or detected alternative envelope)
→ UTF-8 text, sometimes BOM
→ brace-based 1C serialized value tree / container text
```
The decoder must retain envelope type, original encoding, BOM, line endings,
and all unmodified text. An encoder is valid only if an unchanged
decode→encode cycle returns byte-identical payload bytes.
## Lossless transform discipline
1. Decode source bytes.
2. Identify the exact BSL or value-tree slice with a proven extractor.
3. Replace only that slice.
4. Encode with the original codec metadata.
5. Calculate SHA-1 from the final raw stored bytes, never from decoded text.
Do not canonicalize braces, whitespace, strings, BOM, compression level, or
base64 blocks without an independently proven canonical writer.
@@ -0,0 +1,72 @@
# `upo_test` / `test2` saved-state experiment
## Scope
- Test base: `upo_test`
- Extension: `test2`
- Extension GUID: `fb26cf42-7609-11f1-828f-005056b0d483`
- Common form: `t_Форма`
- Form GUID: `77494708-43ea-4956-ac3c-199cfb035ad2`
- Independently manually edited form: `tt_Форма3`
- Form GUID: `99590008-addf-49fa-9ada-24962756d0cf`
These are experiment identifiers, not a universal hardcoded route.
## Follow-up cross-extension confirmation (2026-08-13)
The same logical-file protocol was exercised on a different extension and
object class:
- Extension: `фс_Отчеты`
- Extension GUID: `9b11f844-3d08-11f1-8287-005056b0d483`
- Object: `Report._ПоступлениеТовара`
- Object GUID: `c428f629-785a-4141-b038-f2192bb4580d`
- Guarded change: `//Пример - 3``//Тест - 3`
The active root was a hash-keyed `ConfigCAS` record, but decoding it exposed
the logical `O` / `O.0` map and the SHA-1 of each active payload. The adapter
created exactly `E__configinfo`, `E__O`, `E__O.0`, executed a public
`code.write` replacement and reread it. A controlled `apply_and_rollback`
passed before `apply_and_verify`. A human then confirmed `//Тест - 3` in the
Configurator extension editor.
This proves the canonical object-module overlay route for this observed
extension-root family. The implementation must retain the suffix of the exact
resolved BSL stream: a separately diagnosed production route for
`Report.УОП_ПечатьЦенниковАссортимента` resolves its manager module as `.2`,
not `.0`. This does not authorize a guessed suffix: the selected `O.S` entry
and its SHA-1 must be present in the decoded root map. Forms or an unknown
root that cannot be decoded into that exact logical map remain unproven.
## Outcomes
1. Writing a `.0` module alone made a comment visible after a reload but
failed activation with a hash-version error.
2. Pairing the module write with the matching `__configinfo` map update made
the comment activate into `ConfigCAS`; Configurator cleared
`ConfigCASSave` after successful application.
3. Building a complete three-file initial overlay while Configurator was
closed, then opening and applying it, succeeded for `change-10`.
4. A manual save in `tt_Форма3` produced a three-row pending overlay for that
form plus `__configinfo`. Incrementally adding `t_Форма` descriptor/module
while preserving the existing rows and map entries kept both edits.
5. A pending `__configinfo` map can point at a newer active module than an
earlier inspection of `ConfigCAS`; selecting the source through the pending
map avoids duplicate BSL comments.
## Failed paths retained as evidence
- Raw module-only write: invalid collection hash.
- Random service-atom bytes: licensing/crypto error.
- Treating the active module as authoritative while a pending map exists:
duplicate comment insertion.
- Assuming UI text means SQL save: the editor buffer can differ from
`ConfigCASSave`; verify SQL immediately after saving.
## Verification after every apply
1. `ConfigCASSave` for the extension becomes empty.
2. Extension root key in `_ExtensionsInfo`/`ConfigCAS` changes to the new
`__configinfo` SHA-1.
3. Active `ConfigCAS` contains the requested BSL text.
4. The active map points to the SHA-1 of the active module bytes.
@@ -0,0 +1,128 @@
# Extension saved-state overlay (`ConfigCASSave`)
> **Deployment and integration-test target:** `adapter-1c-mcp` runs on
> `docker.cin.su`. Use that host for deployment and live adapter checks;
> `test-docker` is staging only and requires an explicit request.
## Canonical object-module overlay: activation evidence
For extension GUID `E` and object GUID `O`, the overlay uses:
```text
E__configinfo
E__O
E__O.S
```
This route is now human-confirmed in two independently selected extension
object-module cases on `upo_test`:
| Extension | Object | Public test | Human confirmation |
|---|---|---|---|
| `test2` | Common form/module fixture | prior `change-10` experiment | Configurator applied overlay into `ConfigCAS` |
| `фс_Отчеты` | `Report._ПоступлениеТовара` object module | `//Пример - 3``//Тест - 3` | Configurator displayed `//Тест - 3` in the extension editor |
The descriptor and module are separate objects. A pending extension may contain
only rows changed manually by the user; the root `__configinfo` map can still
refer to unchanged active parts. An adapter must preserve those rows and all
their map entries.
For the separately observed hash-keyed overlay, the equivalent boundary is the
live extension `root_cas_key`: an automatic first-write copy must include that
root manifest together with the selected object parts. A leaf module key alone
is not a complete working-copy selection boundary.
## Hash-keyed first-write status
In `upo_test` / `фс_Отчеты`, copying the complete evidenced hash-key group
from `ConfigCAS` to `ConfigCASSave` under unchanged hash names produced a
byte-for-byte SQL readback, but Configurator continued to read the active
`ConfigCAS` module. Therefore a same-name hash copy is **not** a working-copy
protocol.
The active extension root has since been decoded as the equivalent logical
file map: it contains `"<object-guid>[.suffix]",Base64(SHA-1(payload))`
pairs, including the target descriptor and the selected BSL module. For the evidenced
object-module family the initial overlay maps exactly three active files to:
```text
root manifest -> E__configinfo
object descriptor -> E__O
object `.S` module -> E__O.S, where `.S` is the suffix of the exact resolved
BSL stream (for example `.0` or `.2`)
```
The adapter must reject any incomplete route and must never fall back to
unchanged hash names. The map and all service atoms are copied verbatim on the
first overlay; subsequent writes update only the proven file-SHA reference.
### Exact prepare/write algorithm
1. Resolve the extension, public object `O`, active descriptor and active `.S`
BSL stream from the extension manifest.
2. Decode the selected active root. Require exactly one map entry for `O` and
exactly one for the selected `O.S`; require their SHA-1 values to equal the selected
active descriptor/module payloads.
3. If the overlay is absent, atomically copy only those three sources as
`E__configinfo`, `E__O`, and `E__O.S`. Do not copy the whole extension.
4. If it exists, preserve pending rows and maps; never overwrite another
change. Prepare may add only missing object parts.
5. Replace BSL only after the exact old fragment, source SHA-1, extension,
public object and module stream agree. For an object module, update the one
`O.S` SHA-1 value in `E__configinfo` in the same transaction and preserve
service atoms byte-for-byte.
6. Re-read the BSL and both changed rows. `apply_and_rollback` must restore
the module and remove adapter-created first-overlay rows.
`code.write` callers provide only public selectors and the replacement. The
adapter owns storage mapping, preparation, paired update and rollback.
## Rejected approaches (retain as regression hazards)
- Copying `ConfigCAS` hash rows to `ConfigCASSave` under the same names. It
produced correct SQL readback but Configurator ignored it for `фс_Отчеты`.
- Writing only the BSL module payload. The map continues to point to the old
SHA-1 and Configurator reports a collection/hash-version error.
- Generating `__configinfo`, its service atoms, or logical file mappings from
names alone. The adapter must first decode the actual selected root map.
- Replacing the entire compressed module container. Only the declared BSL
prefix codec is allowed; opaque stream tail bytes must remain unchanged.
- Resolving an already selected object-module path through generic metadata
path traversal. Its module suffix is a BSL container, so this can fail before the
saved-state writer sees the exact module route.
## Proven incremental write protocol
For a form module change:
1. Read the current `E__configinfo` from `ConfigCASSave` if it exists;
otherwise derive a complete initial overlay from active `ConfigCAS`.
2. Resolve the form descriptor and module hashes from that map.
3. Patch the module with a unique BSL anchor and lossless payload codec.
4. Replace exactly the Base64(SHA-1(raw module bytes)) value paired with
logical name `O.0` in `E__configinfo`.
5. Preserve every unrelated map entry and service atom byte-for-byte.
6. In one transaction insert missing `E__O`/`E__O.0` rows and update
`E__configinfo`, with compare-and-set SHA-1 preconditions.
The adapter core exposes a pure `build_extension_saved_state_pair_plan` helper
for steps 35. It refuses a plan when the ConfigInfo map does not reference the
current saved stream, so a later transaction cannot silently overwrite a
divergent human overlay.
For an already existing single-part pending stream, the paired writer locks the
module and `__configinfo`, verifies both preconditions, writes both payloads
and their `DataSize` values in one transaction, keeps independent rollback
evidence, and verifies both rows afterwards. `__configinfo` itself is never a
primary editable stream. Missing overlay rows still use the separate prepare
route before this writer may update them.
This protocol was activation-proven in the `test2` experiment. It is not yet
a universal proof for every 1C platform version or every extension object
class; new classes require their own evidence record.
## Important overlay behaviour
An external SQL overlay write does not set the Configurators in-memory dirty
flag. Its visibility therefore depends on the current session. See
[Configurator refresh behaviour](../operations/configurator-cache.md).
@@ -0,0 +1,24 @@
# Current adapter component map
This map says where to extend the project without losing protocol boundaries.
It is not a substitute for reading the linked source before editing it.
| Component | Location | Responsibility | Evidence boundary |
|---|---|---|---|
| RPC/service orchestration | `plugins/1c/connector/adapter_1c_server.py` | Base resolution, live SQL calls, saved-state plans/apply, verification, backups | Must keep active layers read-only |
| Payload codec | `plugins/1c/parser/payload.py` | Envelope detection, decompression, lossless encode metadata | Require byte-identical unchanged round trip |
| Brace parser | `plugins/1c/parser/*` | Decode 1C serialized brace/value trees | A parse tree is not semantic proof by itself |
| Extension routes | `plugins/1c/parser/extensions.py` and adapter extension resolvers | `_ExtensionsInfo` and `ConfigCAS` manifest routes | Never infer part role from suffix alone |
| DBNames mapping | `plugins/1c/parser/dbnames.py` | Metadata-to-physical SQL role evidence | Names/roles must come from live DBNames evidence |
| Storage access | adapter storage helpers | Single/multi-part row reads, hashes, paired extension map updates, compare-and-set and backups | Writes only to allowed saved-state tables |
| Policy | `plugins/1c/connector/policies/` | Runtime boundary and base-access rules | Policy must match tested capability, not aspiration |
| Regression tests | `tests/1c/` and `scripts/smoke_1c_*` | Preserve routes and observed protocol rules | Add fixture/test before promoting a decoder rule |
## Required update order for a new object type
1. Add an experiment record with before/after SQL evidence.
2. Add/extend a decoder fixture and test.
3. Add the narrow codec or route resolver.
4. Add a writer only after lossless round-trip, paired-index handling, rollback,
and human activation verification.
5. Update the relevant topic document and this map if ownership changed.
@@ -0,0 +1,39 @@
# Configuration storage layers
The following SQL tables have the same observed storage-row shape:
`FileName`, `Creation`, `Modified`, `Attributes`, `DataSize`, `BinaryData`,
`PartNo`.
| Layer | Role | Direct adapter writes |
|---|---|---|
| `Config` | Active base configuration | Forbidden |
| `ConfigSave` | Pending base configuration changes | Controlled `upo_test` only |
| `ConfigCAS` | Active extension content-addressed store | Forbidden |
| `ConfigCASSave` | Pending extension configuration overlay | Controlled `upo_test` only |
`PartNo` must be read and preserved. The currently proven writer handles a
single-part stream only; a multi-part stream is unsupported until a
table-aware round-trip codec exists.
## Addressing rules
Base `Config` objects are commonly addressed by metadata GUID. Extension active
objects are reached through `_ExtensionsInfo` → extension root in `ConfigCAS`
→ root manifest → object part SHA-1 key. Do not infer a semantic role from a
suffix such as `.0`; inspect the payload and route evidence.
For normal content-addressed rows, `ConfigCAS.FileName` was observed to equal
SHA-1 of the stored bytes. Always compute and compare the hash rather than
trusting the name: an activation experiment left a legacy alias whose name was
an old key while its bytes had the new SHA-1.
## Write sequence
1. Resolve public object/form/module against live SQL.
2. Read active bytes and any existing saved-state rows.
3. Prepare only missing saved rows; never overwrite a users existing rows.
4. Encode the changed stream losslessly.
5. Update every proved companion index in the same SQL transaction.
6. Read back and validate hashes and semantic exact-match count.
7. Tell the human which Configurator scope to reload.
@@ -0,0 +1,28 @@
# Common form modules
## Proven form facts
The `test2` common forms `t_Форма` and `tt_Форма3` are distinct objects with
distinct GUIDs and descriptors. A label must be decoded from the descriptor;
do not shorten or normalize it by guesswork (`tt_Форма3` is not `t_Форма3`).
A form `.0` payload can contain more than a simple standalone BSL string. The
adapter must use the proven container extractor and preserve all non-BSL
segments, including form settings and command metadata.
## Module patch rules
- Obtain the BSL region from the decoded container, not from a global text
search over compressed bytes.
- Count the requested anchor in the relevant BSL region.
- Replace a unique exact fragment once; report ambiguity otherwise.
- Re-encode using the source payloads detected codec and line-ending style.
- Read back, re-extract BSL, and verify the target occurrence count.
## Pending-state resolution
When `ConfigCASSave` contains a `__configinfo` map, that map is authoritative
for the pending overlay even if the corresponding module row is absent. The
active `ConfigCAS` source must be selected through the saved map before a new
delta row is created. Reading only active content can incorrectly conclude
that a pending comment is absent and cause a duplicate insertion.
@@ -0,0 +1,55 @@
# Граф составляющих объекта конфигурации
Объект конфигурации — это корень, а не минимальная единица работы. Его
составляющие: модуль объекта, формы и их модули, макеты, схема компоновки
данных (СКД), а после декодирования СКД — наборы данных, запросы, поля,
ресурсы и варианты. Для записи нужен адрес именно составляющей и доказанный
физический носитель этой составляющей.
## Публичная инвентаризация
`metadata.object.components` — read-only фасад над уже доказанными SQL
декодерами. Он принимает обычный селектор владельца (`ref` либо `kind` и
`name`, при необходимости `extension`) и возвращает граф:
- корень `metadata_object`;
- только реально найденные `module`, `form`, `template` и `scd`;
- публичные `path` и `read_selector` каждого потомка;
- `unresolved` для областей, которые не были подтверждены.
Метод не создаёт потомок потому, что он обычно есть у такого типа объекта.
Например, пустой ответ `metadata.object.modules` у общей формы означает
«модуль этим маршрутом не найден», а не повод назвать форму модулем.
Пример запроса:
```json
{
"method": "metadata.object.components",
"payload": {
"base_id": "upo_test",
"extension": "test2",
"ref": "Report.tt_Отчет",
"include_storage": false
}
}
```
`read_selector` можно передать следующему read-only методу. Это не является
разрешением на запись: запись выбирает компонент по его `path`, повторно
подтверждает конкретный carrier/stream и разрешается только для семейства
контейнеров с доказанным byte-preserving кодеком.
## Иерархия и кодеки
Логический BSL один и тот же независимо от владельца, но его физический
носитель может различаться. Поэтому не нужны отдельные эвристики «для формы»
и «для отчёта»: нужен реестр семейств carrier-кодеков. Один кодек применяется
к нескольким типам объектов лишь после доказательства одинаковых границ,
непрозрачных атомов и round-trip проверки.
Текущий пример: модуль общего формы в `upo_test/test2` имеет доказанный
write-кодек saved-state; обнаруженный поток модуля отчёта — только
read-only structural codec. Второй компонент может быть найден и прочитан,
но не получит разрешение на SQL-запись, пока его служебный хвост не будет
декодирован.
@@ -0,0 +1,115 @@
# Модуль объекта отчёта в расширении
Статус: чтение, точное разрешение владельца и контролируемая запись короткого
фрагмента поддержаны для доказанного hash-keyed saved-state маршрута.
Наблюдение в `upo_test`, расширение `test2`, отчёт `tt_Отчет`: сохранённый файл
`<extension-guid>__<report-guid>.0` является `raw_deflate` контейнером из пяти
потоков. BSL-модуль расположен в потоке `4`.
Поток начинается читаемым UTF-8-комментарием, но последующий текст содержит
нулевые байты и смешанное представление символов. Общий потоковый декодер
позволяет найти комментарий, однако его обратное кодирование меняет байты
неизменённого хвоста BSL. Экспериментальная запись показала это в Конфигураторе
и была немедленно восстановлена из парной резервной копии.
Правило: наличие читаемого BSL-фрагмента не доказывает возможность записи.
Для потока с `NUL` адаптер возвращает
`mixed_encoding_module_stream_unsupported` и не создаёт SQL-изменений.
Это не означает, что для каждого отчёта нужен свой кодер: один доказанный
кодек может обслуживать все модули с одинаковым физическим носителем.
Два ручных образца определили безопасную границу записи: редактируется только
объявленный UTF-8-префикс, а непрозрачный хвост и остальные потоки сохраняются
побайтно. Для hash-keyed overlay рабочий слой создаётся доказанным копированием
подтверждённых ключей `ConfigCAS → ConfigCASSave`; `__configinfo` для него не
создаётся и не предполагается.
Текущая реализация `parser.cas_payload.stream_blocks_with_data` ищет похожие
заголовки регулярным выражением по всему распакованному буферу. В потоке
отчёта такие последовательности встречаются и внутри данных, поэтому это
эвристика для чтения, а не структурный декодер. Нельзя использовать её индекс
потока как основание для обратной записи.
Структурный read-only декодер `decode_declared_utf8_bsl_prefix` подтверждён на
этом образце: пять последовательных блоков; пятый имеет `declared_1 = 68` и
`declared_2 = 512`. Первые 68 байт — UTF-8 BOM и точный BSL-текст, оставшиеся
444 байта — непрозрачный служебный хвост. Декодер вернул только:
`// protocol-report-baseline-1` и `// protocol-report-manual-change-4`.
## Пара ручных образцов `2 → 3`
Образцы `samples/manual-change-2.json` и `samples/manual-change-3.json`
содержат raw-deflate байты, сохранённые человеком в Конфигураторе. В
распакованном контейнере длиной 1283 байта замена цифры `2` на `3` изменила
BSL ровно в смещении `838` (`0x32 → 0x33`). Одновременно платформа изменила
шесть служебных диапазонов: `110..113`, `230..252`, `437..464`, `590..593`,
`598..601`, `716..719`. Трёхбайтовое значение повторяется в нескольких
местах, а два диапазона содержат связанные Base64-представления.
Это доказывает, что нельзя перепаковывать поток общим writer'ом. Отдельный
fixed-width кодек меняет только первые `declared_1` байт: короткий текст
дополняется пробелами внутри этого поля, хвост и размер члена не меняются.
Рост префикса или структурная правка процедуры явно отклоняются.
## Полный объявленный поток: переменная длина
Нельзя переносить ограничение fixed-width с описанного выше носителя на все
объектные BSL-модули. На рабочем маршруте `upo / фс_Отчеты /
Report.УОП_ПечатьЦенниковАссортимента / .2 / stream:4` подтверждён другой
контейнер: у выбранного BSL-потока `declared_1 == declared_2 == 36101` и
`opaque_tail_bytes == 0`. Это полный UTF-8 поток, а не префикс перед
непрозрачными данными.
Для такого носителя адаптер использует обычный структурный stream writer:
он меняет текст, пересобирает оба объявленных размера в заголовке и сдвигает
только последующие байты контейнера. Локальная обратная проверка целевой
замены `НоваяСтрока.Выбран = Истина;` на более длинный фрагмент дала размер
потока `36101 → 36198`, новый заголовок `36198/36198`, одно новое вхождение и
нулевое старое. Все байты до заголовка выбранного потока сохранились.
Правило выбора кодека: fixed-width применяется **только** если доказан
ненулевой непрозрачный хвост; если `declared_1 == declared_2` и хвоста нет,
безопасна контролируемая замена переменной длины через структурный writer.
Неизвестный или частично декодированный контейнер остаётся заблокированным,
а не переводится в переменную длину по предположению.
## Правило публичного маршрута
Если объектный модуль состоит только из комментариев, это всё равно BSL-модуль:
у него нет маркеров `Процедура`/`Функция`, но его наличие подтверждает
структурный UTF-8-префикс в потоке. Адаптер обязан вернуть владельца и точный
селектор чтения, не заставляя клиента искать поток. При записи он обязан
использовать только fixed-width кодек, а не общий stream writer, который
перезаписывает непрозрачный хвост. Парное обновление `__configinfo` допустимо
только в отдельно подтверждённом каноническом layout.
## Повтор `code.write` после успешной записи
Повтор одного и того же публичного `code.write` не является новой операцией.
До автоматической подготовки `ConfigCASSave` адаптер читает указанную
процедуру в `effective_working`. Если старого фрагмента уже нет, а точный
новый фрагмент присутствует ровно один раз в этой же процедуре, результат —
`status: already_applied`, `applied: false`. В этом случае запрещены и
подготовка saved-state, и новая SQL-запись.
Это правило предотвращает опасный путь: повторный запрос нельзя начинать с
активного `ConfigCAS`, потому что его копирование способно заново построить
рабочую копию из доизменённого источника и скрыть факт уже выполненной
операции. Если оба фрагмента отсутствуют, новый фрагмент встречается
несколько раз либо процедура не подтверждена, идемпотентность не
предполагается: применяется обычная безопасная ошибка `not_found`/
`ambiguous` или диагностика маршрута.
## Цепочка версий `2 → 3 → 4`
Третий live-SQL образец подтвердил повторяемую часть протокола. 20-байтовое
Base64-поле в каждой новой версии равно SHA-1 сырого файла предыдущей версии:
запись `3` хранит SHA-1 записи `2`, а запись `4` — SHA-1 записи `3`. Это
доказанная ссылка версии, а не случайный текст. Его контрольный SHA-1:
`fc84f0a9ef17034f8d82f44c5f9b07064864b524`.
Рядом расположен 16-байтовый токен, который меняется при каждом сохранении и
дублируется фрагментами в трёх служебных местах. Алгоритм его создания не
декодирован: адаптер его не генерирует и не изменяет. Его нельзя считать
основанием для создания или изменения `__configinfo` в hash-keyed overlay.
@@ -0,0 +1,12 @@
{
"schema": "onec_report_module_stream_sample.v1",
"origin": "Configurator manual save in upo_test/test2/tt_Отчет; no adapter write",
"logical_change": "// protocol-report-manual-change-2",
"physical_file_name": "<extension-guid>__<report-guid>.0",
"compression": "raw_deflate",
"raw_sha1": "89827a3c7fa07ae50a268d20a7cee34195d1d7a7",
"raw_bytes": 618,
"inflated_sha1": "6385a80972841eb8a97df43596ed15dd16966495",
"inflated_bytes": 1283,
"raw_base64": "7VLJbhNBEG2iPlnyBXHgBKMRByDTcc/S0zNCBtnEie2MvI2XwQJFPat3J/ZkV4TCgV/gCge+ggMfwE9w4MaBTzDtbLIFQYgzT+rq6q5+9apVNZvNXoMVAG6BCyQT+ByyIVw7NLyAkExs8idv+Zpx2kdO+7Zy4V/RFCxcOws0of/q4cvcCnj/+OzRfJ+jC0YgBOMlVRwKl+wl+o9Pn09USZawJIoSPrUyL5qqOQ39SafeLxxF5YlbpLvtDc9JJhMZfdUYD+L8gdHqWa2CWqNuX8aHekQ766GjlUqpIKodbzQcJ5M+lZKJE1MSZeK6MnMZ0onMkKYGKnJVipFHNdPHpoI9F4tSs1jp9OS6TTZ9u0GmTlVtDtrtmm9YuTTPYRjEoExHGtVVpOnYQKYcYkRNrCvU1TSdeaJU2S8PGo2jdaVXtVvTeF1TUsNi02rtVKp/mWONV7JVydojpRo6qtVNPW8acbe+29JLBWNvnoXyqjXKufwj/Dcm0RHzVA/JpumFLlYJ8xVROp760f6w5tml4n6l1GnQYSNbdFyl0MN/yHHd56vxUJbG46q/i31+AGwQgwkIAANDflrst2bc2G9dkgMiK4y5SP6H6Yq53iG3S2raTWqplLAzGcdjbzxAk2BnPImRy6bBoDsK5uq/CQ/ZaI8NkNdhoyhACtY9X2WUiFLZjywrzvhReJDP94ZRJqjn28pqPlcz0qdwjZcUg21wD2ZgGj6FCrcYlqHA/SfQgoIgfN2y30l3efzZ5R09f4dhlvuLvC1ILnkGvMNtGd7mUQJz8P4vTMLPBJrwe/YNl/jwxT4TBPAf5/gJ"
}
@@ -0,0 +1,12 @@
{
"schema": "onec_report_module_stream_sample.v1",
"origin": "Configurator manual save in upo_test/test2/tt_Отчет; no adapter write",
"logical_change": "// protocol-report-manual-change-3",
"physical_file_name": "<extension-guid>__<report-guid>.0",
"compression": "raw_deflate",
"raw_sha1": "357c39a55e8f66c3efd8f7aac968a4db5d5eb5fb",
"raw_bytes": 620,
"inflated_sha1": "eb72cadfe12f68969cd278901a6f83e627ee1a53",
"inflated_bytes": 1283,
"raw_base64": "7VLLbhMxFDWVV5GyQSxYwWjEApRxa8+MxzOqAkrV0HQ6ah6TVyMQ8jyTNMmkybSlVBWCBb8AWxZ8BQs+gBV/wIIdCz4huE8lgiLEmiP5+trX555r3TubzV6CJQBugHNkM/gMxJSuHBadQ8pmNsSTN2LNBO2DoH1bOvcvaSqWrpw5mrT79P6T4hKofHn34HQ/RQ+MQASSBVUcSRfsBfqPj5+ONYUoWJFlBZ84hZ2mZk2jYNKt724exeWJZ7O9zmO/nc1mCkbOTAZp6dBs9Z3WplZj3i6h+lGQs8l2vZ0YNNnTUr5hr0/zJ0o2c2wpMqGeR7jHkUEJR7oWasjTGEY+060AWyr2PSwrTbvS7ZO6SzcCt0Gn7arWHHQ6tcB0inmRwzSpybiBdGZoSDewiSwSYcQsbKjM03WD+7JSOSgPGo2jdbVfdVvTdF1XV4Z202mNK9W/zLEsKunt2EallIvD/caWneB4bLJ+tdOuEcMXWZioWmeCKz4ifmNRA3Ff8xGxLD/ysEZ5oMrKi2kQHwxrvrttH1S2uw02bKzZbU/d7OM/5Ljq8+V4qAvjcdnf+T7fAy5IwQSEgIOhOM33Wzev7behkJASlXMPkX+YrlToPRd2QU2/Tm1lRRpPkjTxkwGahONkkiKPT8NBbxSeqv8mPOSjfT5AfpeP4hBp2PADjTMqK+Ugdpy0EMTRYanUH8aFsF7qqLlSsWbmT+CyKCkFz8AdWIB5+BCqwmJYhpLwV6EDJUn6uuW+VW6L+KOLO3b2DsM14c/ztiC94JnwlrBleFNEKSzCu78wqThTaMHva6+FxPvP7itJAv9xhp8="
}
@@ -0,0 +1,35 @@
# Configurator refresh behaviour
## Observed session model
`ConfigCASSave` is an SQL overlay, but writing it outside Configurator does not
set Configurators in-memory changed/dirty state.
Observed consequences:
| State before adapter write | Minimal human action after write |
|---|---|
| Object already existed in saved-state | Close and reopen that object |
| Adapter created the first pending object for an extension | Close and reopen the extension |
| Adapter created the first pending object for base configuration | Close and reopen the configuration |
If the user manually edits and saves any object in an extension, Configurator
marks the extension changed; reopening another object can then load its
`ConfigCASSave` overlay.
## Required adapter response
Write results should return machine-readable guidance:
```json
{
"configurator_refresh": {
"required": true,
"scope": "object|extension|configuration",
"action": "close_reopen_object|close_reopen_extension|close_reopen_configuration"
}
}
```
This guidance does not claim that the adapter controls Configurator; it merely
reports the minimum observed reload boundary.
@@ -0,0 +1,27 @@
# Known dead ends and prohibited shortcuts
## Do not repeat
- Do not write only a module `.0` row: `__configinfo` then references stale
bytes and activation fails.
- Do not use random, zeroed, copied-from-unrelated, or guessed HashVersion
service atoms. A controlled random test produced a licensing/crypto error.
- Do not write `Config` or `ConfigCAS` directly, even in tests.
- Do not use a suffix such as `.0` as proof of “object module”.
- Do not infer an object name from a GUI tree label, table suffix, or an
approximate Russian name.
- Do not overwrite all saved-state rows when adding a delta: preserve user
work in other objects and every map entry.
- Do not treat an open Configurator screen as SQL evidence.
## Open questions
- The generation algorithm and ownership of the 32-byte `__configinfo`
service atom are unknown. Preservation is proven for the studied flow;
generation is not.
- Multi-part storage streams need a dedicated row-layout codec.
- The extension protocol must be reproduced on another object class and a
second platform build before being declared generally supported.
- Configurators in-memory dirty-state implementation is inferred from
behaviour, not decoded from platform source. Only the reload guidance is
operationally relied upon.
@@ -0,0 +1,51 @@
# Contract for an agent using the 1C SQL adapter
The agent is a consumer of evidence returned by the adapter. It must never
turn a plausible interpretation into a fact.
## Fundamental adapter rule
The adapter is a **SQL codec**, not an expert system for 1C. It reads and
writes only according to the versioned configuration-storage specification
that has been decoded from live SQL and recorded in this knowledge base.
It may expose a semantic name only when that mapping is proven by the decoder.
For an unknown carrier, field, child object, byte range, checksum, or service
atom, the only valid result is `unsupported`, `partial`, or `ambiguous` with
the observed evidence. It must not synthesize a structure, BSL, or value to
make an operation appear complete.
## Required behaviour
- Start from a public 1C name/ref supplied by the user.
- Ask the adapter to resolve the live route; internal GUIDs, SQL numbers and
file names remain adapter implementation details.
- Use public `code.read`, `code.search`, and `code.write` for normal BSL work.
Never pass or request `ConfigCAS`, `ConfigCASSave`, a payload hash, a
canonical saved filename, or a stream index. Those are diagnostic evidence,
not an agent-facing selector contract.
- Read the target bytes before proposing any edit.
- Quote the exact proven fragment, its count, and the selected layer.
- For a write, require a plan/preflight and retain the returned rollback and
refresh guidance.
- State `unknown`, `ambiguous`, or `protocol_incomplete` when evidence is
absent. Ask for a larger fragment or a human Configurator action instead of
guessing.
## Forbidden behaviour
- Invent BSL procedures, form controls, field paths, joins, storage tables,
module streams, extension ownership, or `__configinfo` atoms.
- Claim that a SQL saved-state edit is active before active-layer verification.
- Claim a repository lock merely because an adapter request was recorded.
- Tell the user that the Configurator UI has refreshed unless the required
close/reopen boundary was completed by the human.
- Write directly to active configuration or application tables.
- Work around a public-route failure by retrying against an internal module
reference. Report the public `not_found`, `ambiguous`, `unsupported`, or
`protocol_incomplete` result so the adapter can be corrected.
## Write-result language
Use the adapters `configurator_refresh` object verbatim in human-facing
instructions. Do not collapse `object`, `extension`, and `configuration` into
the same generic “restart” advice.
@@ -0,0 +1,55 @@
# Evidence and safety rules
## Evidence levels
| Level | Meaning | Permitted use |
|---|---|---|
| Observed | Read from live SQL once | Diagnosis only |
| Reproduced | Seen in independent before/after saves | Decoder rule candidate |
| Round-trip proven | Decode → unchanged encode returns identical bytes | Safe read/transform component |
| Activation proven | A human Configurator applied it and active SQL verified it | Controlled `upo_test` writer component |
No rule may be promoted because a name, suffix, or payload shape “looks right”.
## Codec boundary
The configuration-storage specification is the adapter's sole authority for
decoding and encoding. A writer is enabled only when the relevant version of
that specification defines every changed byte and every dependent integrity
atom, and round-trip evidence proves the encoder. Any remaining opaque atom is
preserved byte-for-byte; if a requested edit requires changing it, the write is
unsupported until the specification is extended by a controlled experiment.
## Allowed mutation boundary
- The adapter runtime uses SQL only.
- `upo_test` may receive controlled writes to `ConfigSave` and
`ConfigCASSave` only.
- `Config`, `ConfigCAS`, and application data are never direct write targets.
- A write must have a live target resolution, optimistic SHA-1 precondition,
reversible evidence, atomic transaction, and readback verification.
- Repository coordination is a separate policy; an adapter marker is not a
native repository lock.
## Exact edits
For BSL fragment replacement, provide one of:
- an old fragment occurring exactly once;
- a larger unique surrounding fragment;
- a proven structural container path/offset plus original SHA-1.
If a fragment occurs zero or more than once, do not choose arbitrarily. Return
the count and candidate contexts. Deletion follows the same rule.
## What an agent must report
Every proposal and applied result must state:
- active and saved layers used;
- public target and SQL evidence retained internally;
- original and new SHA-1 values;
- paired files changed;
- exact-match count;
- rollback reference;
- Configurator refresh guidance.
@@ -0,0 +1,41 @@
# Protocol experiment record template
Create one file per material experiment under `experiments/`.
```markdown
# <date> — <short target and action>
## Scope
- Base and classification:
- Extension/object public names:
- Runtime/platform build:
- Authority for mutation:
## Before
- Active source table/key/SHA-1:
- Saved-state files and SHA-1:
- Relevant descriptor/map entries:
## Exact action
- Public target resolution evidence:
- Old fragment / structural selector and occurrence count:
- Payload codec and round-trip result:
- Paired files written in one transaction:
## After SQL evidence
- Readback SHA-1 values:
- Map/reference validation:
- Unrelated pending files preserved:
## Human Configurator verification
- Reload action:
- Visible result:
- Apply result:
- Active/saved postcondition:
## Outcome
- Proven fact:
- Failed hypothesis:
- Remaining unknown:
- Follow-up regression fixture/test:
```
+121
View File
@@ -0,0 +1,121 @@
# 1C Adapter: universal write dispatcher and typed handlers
## Decision
The public write contract stays universal and name-first:
```text
code.write / metadata.write
-> resolve public object and extension layer
-> plan and gates
-> select one typed handler
-> prepare saved state internally when required
-> apply, SQL-readback, rollback evidence
```
The caller never selects an SQL table, saved file, stream, payload codec, or
handler. If no handler has a proven capability for the requested object type
and operation, the dispatcher returns `unsupported_write_target` with a public
explanation. It must not fall back to a generic byte rewrite.
## Current state
The behaviour is already logically separated, but is physically concentrated
in `plugins/1c/connector/adapter_1c_server.py` (about 3.5 MB). The main
dispatcher is `metadata_write` and currently branches to:
| Public target | Existing internal writer |
|---|---|
| BSL module | `metadata_module_write_apply` |
| BSL embedded in a managed form | `form_embedded_module_handler_write_apply` |
| Form element/property | `metadata_form_element_write_apply` |
| Form command/button caption | `metadata_form_command_button_write` |
| Scalar object/member property | `metadata_object_property_write` |
| Add object member | `metadata_object_member_add` |
| Scheduled-job schedule | `metadata_scheduled_job_schedule_write` |
This is a suitable functional base. The problem is coupling: routing,
saved-state preparation, result shaping, codecs, SQL writes, and HTTP/RPC
dispatch live in one module, so a change in one type is too likely to affect
another.
## Target module layout
```text
plugins/1c/connector/
adapter_1c_server.py # HTTP, RPC registration, composition root only
write/
contracts.py # WriteIntent, WritePlan, WriteResult, capability errors
dispatcher.py # universal metadata.write dispatch; no SQL codecs
gates.py # layer, repository, optimistic-hash and mode gates
saved_state.py # Config→ConfigSave / ConfigCAS→ConfigCASSave prepare + receipt rollback
registry.py # handler registration and deterministic selection
handlers/
module.py
embedded_form_module.py
form_element.py
form_command.py
object_property.py
object_member.py
scheduled_job.py
unsupported.py
storage/
sql_saved_state.py # transactions, guarded row copy, backup/readback
extension_routes.py # active-to-saved route and cache refresh
```
`parser/` remains the place for pure payload decoding/encoding. A handler may
use a parser codec only where its round-trip proof exists; SQL access is
provided through a narrow context rather than imported globals.
## Handler contract
Each handler implements the same four operations:
1. `can_handle(intent, evidence) -> supported | unsupported | ambiguous`.
2. `plan(intent, evidence) -> WritePlan` with exact guards and no mutation.
3. `apply(plan, context) -> WriteResult` only after shared gates succeed.
4. `rollback(result, context)` when the handler created reversible state.
The dispatcher selects exactly one handler. Zero handlers yields
`unsupported_write_target`; multiple handlers yield `ambiguous_write_handler`.
Handlers never select another extension layer after dispatch.
`metadata.write.capabilities` also returns `registered_handlers`. This makes
the runtime registry visible beside the broader capability matrix and prevents
an API claim from silently drifting away from the installed handlers.
## Invariants owned centrally
- public name/ref and extension scope resolve before handler selection;
- only saved layers are writable;
- saved-state preparation is internal and idempotent;
- optimistic hash, audit event, backup/receipt, SQL readback and rollback
policy are common infrastructure;
- SQL readback is not described as Configurator activation;
- low-level storage fields are redacted from name-first responses.
## Safe migration order
1. **Completed:** add storage-free `write/contracts.py`, typed handler
declarations under `write/handlers/`, and `write/registry.py`; connect
`metadata.write` to the registry while delegating to existing writers
unchanged. The registry has a deny-by-default result for unknown target
kinds.
2. **In progress:** add `write/context.py`; the scheduled-job route now enters
its typed handler through this explicit context. The handler still delegates
to the single existing implementation until its body moves in one change.
3. Move `saved_state.py` and `storage/sql_saved_state.py` first. The recently
proven hash-keyed extension prepare/rollback smoke is its acceptance test.
4. Extract the least coupled handlers: scheduled job, object property, object
member.
5. Extract form element and form command handlers.
6. Extract module and embedded-form-module handlers last; retain their exact
payload codec and paired-write tests.
7. Reduce `metadata_write` to validation, plan/gate orchestration and one
registry call. Keep the old public API names and response schema intact.
Do not split by copying code into parallel paths. Each extraction must move one
authoritative implementation, keep the existing tests green, and add one
handler-level `plan → apply_and_rollback → readback` test in `upo_test` when a
matching fixture exists.
+224
View File
@@ -0,0 +1,224 @@
# Аналитика адаптера 1С: передача проекта
Дата актуализации: 2026-08-07.
## Назначение
`adapter-observer` — независимый read-only веб-интерфейс аналитики для SQL-only адаптера 1С. Он читает журналы REST и MCP, получает разрешённые снимки метаданных через публичный API адаптера и не имеет прямого доступа к SQL-базе 1С.
Сервис не является зависимостью `adapter-1c-rest` или `adapter-1c-mcp`: остановка либо обновление observer не должна влиять на работу адаптера.
## Описание адаптера 1С
Адаптер 1С — SQL-only сервис для чтения метаданных и выполнения строго контролируемых операций с saved-state конфигурации 1С. Он работает через подтверждённые структуры SQL-хранилища; не запускает Configurator и не должен выдумывать метаданные, маршруты или двоичные payload.
Основные части:
- `adapter-1c-rest` — REST API адаптера;
- `adapter-1c-mcp` — MCP-шлюз, который вызывает REST API;
- `adapter-1c-audit` — аудит-контур REST;
- `adapter-observer` — независимая аналитика журналов и разрешённых read-only вызовов.
Код адаптера находится в текущем репозитории:
```text
plugins/1c/connector/adapter_1c_server.py реализация REST/RPC методов
plugins/1c/connector/contracts/openapi.yaml публичный HTTP-контракт
plugins/1c/mcp/adapter_1c_mcp.py MCP-шлюз
plugins/1c/observer/ аналитика адаптера
```
### Где развёрнут адаптер
| Контур | REST | MCP | Docker-хост | Назначение |
| --- | --- | --- | --- | --- |
| Production / внешний | `http://docker.cin.su:8011` | `http://docker.cin.su:8021/mcp` | `docker.cin.su` | Основной внешний адаптер и observer. |
| Staging / тестовый | `http://docker-test.cin.su:8011` при наличии тестового стека | `http://docker-test.cin.su:8021/mcp` при наличии тестового стека | `test-docker` | Проверка перед production. |
| Изолированная тестовая база | `base_id=upo_test` | через соответствующий MCP | выбранный контур | Разрешены контролируемые тесты и rollback. |
Production-контейнеры на `docker.cin.su`:
```text
adapter-1c-rest порт 8011
adapter-1c-mcp порт 8021
adapter-1c-audit внутренний аудит REST
adapter-observer порт 8031
```
## Как проверять новые функции адаптера
### До развёртывания
1. Изменить реализацию в `plugins/1c/connector/adapter_1c_server.py` и зафиксировать публичный контракт в `plugins/1c/connector/contracts/openapi.yaml`.
2. Добавить либо обновить unit/smoke-тест в `tests/1c/` или `scripts/smoke_1c_*.py`.
3. Для новой метадаты или SQL-маршрута сначала получить доказательства из live SQL в `upo_test`; при неполном codec вернуть `unsupported`/`protocol_incomplete`, а не предполагать данные.
4. Прогнать тесты и smoke-проверку на `upo_test`.
### Staging-проверка
Развернуть обновлённые `adapter-1c-rest` и при необходимости `adapter-1c-mcp` на `test-docker`. Не использовать staging вместо external MCP production без явного запроса.
Проверять новый read-only метод через REST/RPC с явным `base_id=upo_test`. Проверка write-маршрута должна пройти обязательные plan/preflight/apply/rollback-gates и не даёт права заявлять, что Configurator принял или активировал изменение.
Полезные проверки:
```powershell
# Контейнеры и порты выбранного контура
docker --host ssh://test-docker ps --format '{{.Names}} {{.Image}} {{.Status}} {{.Ports}}'
# Контракт/доступные методы на REST
Invoke-WebRequest -UseBasicParsing http://docker-test.cin.su:8011/methods
# Health observer после тестового вызова
Invoke-WebRequest -UseBasicParsing http://docker-test.cin.su:8031/health
```
Пути и аргументы нового метода нельзя составлять по догадке: использовать только его документированный контракт и подтверждённые публичные селекторы.
## Как обновлять аналитику вместе с адаптером
Каждое изменение адаптера нужно оценивать как изменение наблюдаемого контракта.
| Изменение адаптера | Что изменить в observer |
| --- | --- |
| Новый метод аудита или новый статус | Проверить `event_view`, фильтр статусов, группировку summary и русские подписи. |
| Новый read-only метод для объекта | Добавить его в жёсткий allowlist `/api/object/action` только после проверки публичного селектора и безопасного ответа. |
| Новый тип метаданных | Добавить его в `treeGroups`, если он должен быть виден в дереве. |
| Новое поле длительности | Оставить в API машинское значение, а в UI провести через `duration()`. |
| Изменение схемы audit JSONL | Сохранить обратную совместимость: неизвестные поля показывать только в деталях, отсутствующие поля считать необязательными. |
| Новый write-маршрут | Не добавлять кнопку выполнения в observer. Допустимо отобразить только подтверждённую capability/статус после отдельного проектного решения. |
Обязательная последовательность релиза:
1. Сначала обновить адаптер и проверить его новый метод на `upo_test`.
2. Убедиться, что REST/MCP audit содержит безопасную запись вызова без SQL, BSL, payload и секретов.
3. Обновить observer в staging; открыть новый сценарий в UI и проверить, что метод не классифицируется как `exception` ошибочно.
4. Обновить observer на production вместе с совместимой версией адаптера.
5. Проверить `/health`, журнал, аналитику и конкретный объект в дереве.
Observer не должен требовать одновременный рестарт адаптера. При выпуске только frontend/observer достаточно пересоздать `adapter-observer`; REST и MCP остаются запущенными.
## Что сделано
### Интерфейс
- Журнал REST-запросов с фильтрами по методу, базе, статусу, периоду и минимальной длительности.
- Аналитика p50/p95, медленных методов, исключений и ожидаемых безопасных отказов.
- Корреляция MCP ↔ REST по `request_id`.
- Дерево метаданных конфигурации с разделом «Справочники».
- Для каждого доступного справочника отображаются read-only действия:
- Карточка;
- Свойства;
- Реквизиты;
- Формы;
- Команды;
- Модули;
- Макеты;
- Связи.
- Результат действия открывается в диалоге с названием операции, объектом, статусом и длительностью.
- Поиск по уже загруженному списку справочников и счётчик `Показано: N из M`.
### Время выполнения
- Во всех пользовательских представлениях миллисекунды форматируются в секунды, минуты и часы.
- Фильтр минимальной длительности вводится в секундах.
- В технических API-полях сохраняется `duration_ms`: это контрактное машинное значение, не пользовательская подпись.
### Производительность
- Observer отдаёт до 1000 объектов за один запрос к `metadata.objects.list`.
- Для базы `upo` загружается 798 доступных справочников из 825 объектов одного типа одним запросом; 27 объектов скрыты адаптером как отсутствующие/нечитаемые.
- Проверенное время live-сканирования этого списка: около 18,7 секунды. Это время адаптера и SQL-чтения, а не рендеринга кнопок в браузере.
### Безопасность
- Observer вызывает только жёстко заданный allowlist read-only методов для строки справочника.
- Новые действия не выполняют запись, подготовку saved-state, активацию конфигурации или операции Configurator.
- В интерфейсе не восстанавливаются исторические запросы из audit JSONL.
## Что ещё нужно сделать
Приоритетный следующий этап:
1. Добавить быстрый серверный поиск справочника по имени, чтобы не ожидать полное live-сканирование при работе с одним объектом.
2. Добавить отображение прогресса при загрузке больших разделов: число прочитанных объектов, текущая страница и время ожидания.
3. Вынести перечень разрешённых действий и русские названия в отдельную конфигурацию/контракт, а не хранить в фронтенд-коде.
4. Добавить компактные пользовательские карточки результатов действий вместо показа полного JSON; JSON сохранить как диагностическую вкладку.
5. Добавить тесты UI/HTTP для сценария: открыть «Справочники» → загрузить → увидеть кнопки → выполнить «Карточка».
6. Добавить version/release marker в `/health` и UI, чтобы быстро отличать старую Docker-сборку от актуальной.
7. Добавить снимки и сравнение аналитики между релизами: список методов, покрытие метаданных, p50/p95 и изменения ошибок.
8. До публикации вне внутренней сети определить аутентификацию, роли, срок хранения audit-данных и экспортируемые поля.
Не реализовывать без отдельного разрешения:
- повтор исторических write/activation/repository-запросов;
- прямое подключение observer к SQL 1С;
- запуск или автоматизацию Configurator;
- вывод BSL-текста, SQL-полей, паролей или ключей из журналов.
## Исходные файлы
```text
plugins/1c/observer/
observer_server.py HTTP API и безопасный allowlist действий
web/index.html оболочка интерфейса
web/assets/app.js UI, форматирование времени, дерево, действия
web/assets/style.css стили
Dockerfile образ observer
core/deploy/docker/adapter-observer/
compose.yaml отдельный Docker Compose стек
.env.example пример runtime-переменных
docs/runbooks/adapter-observer.md
эксплуатационный контракт и ограничения
```
## Docker-развёртывание
### Текущий production-хост
- Docker host: `docker.cin.su`.
- Контейнер: `adapter-observer`.
- URL: `http://docker.cin.su:8031/`.
- Образ: `adapter-observer:latest`.
- Порт: `8031`.
- Внешняя сеть адаптера: `adapter-1c_default`.
- Read-only тома журналов:
- `adapter-1c_adapter-1c-data``/audit:ro`;
- `adapter-1c-mcp_adapter-1c-mcp-data``/mcp-audit:ro`.
- Собственный state-том: `adapter-observer_adapter-observer-state`.
### Команда обновления
Из корня текущего репозитория:
```powershell
$env:DOCKER_HOST = 'ssh://docker.cin.su'
docker compose `
--project-directory 'Z:\codex\LLM\core\deploy\docker\adapter-observer' `
-f 'Z:\codex\LLM\core\deploy\docker\adapter-observer\compose.yaml' `
up -d --build adapter-observer
```
После обновления:
```powershell
Invoke-WebRequest -UseBasicParsing http://docker.cin.su:8031/health
docker --host ssh://docker.cin.su ps --filter 'name=^/adapter-observer$'
```
Команда пересоздаёт только `adapter-observer`. Не запускать `docker compose down` в проектах REST/MCP адаптера и не перезапускать `adapter-1c-rest` или `adapter-1c-mcp` ради обновления аналитики.
### Тестовый хост
Для staging используется тот же стек с `DOCKER_HOST='ssh://test-docker'` и URL `http://docker-test.cin.su:8031/`.
## Проверки после переноса
1. `GET /health` возвращает `status: ok` и показывает файлы REST/MCP audit.
2. Открыть «Дерево объектов» и загрузить `upo`.
3. Нажать «Читать» у «Справочники»: блок должен остаться раскрытым.
4. Убедиться, что видна строка вида `798 объектов · N с` без единицы `мс`.
5. У первой строки должны быть восемь кнопок действий.
6. Нажать «Карточка»: открывается диалог с успешным статусом и читаемой длительностью.
@@ -0,0 +1,54 @@
# Проверка векторного поиска по коду 1С
Дата проверки: 2026-07-26. База: `upo_test`.
## Контур
- актуальный SQL-derived индекс адаптера: 1 080 модулей, 22 538 chunks;
- очередь изменений: 0, snapshot чистый;
- модель: `Qwen3-Embedding-0.6B-GGUF`, `Q8_0`;
- runtime: `llama.cpp`, CPU-only на Ryzen 9 5900X;
- endpoint: `http://docker-gpu.cin.su:8082/v1/embeddings`;
- вектор модели: 1024, сохраняемый Matryoshka-срез: 384;
- RTX 4090 сервисом эмбеддингов не используется.
## Измерения
Прогретый короткий запрос к endpoint: 0,030,08 с. Индексация 100 процедур и
функций длиной до 1 000 символов заняла 54,52 с, ошибок и конфликтов нет.
Контрольные запросы к строгому поиску:
| Запрос | Top-1 | Время |
|---|---|---:|
| проверить является ли объект документом | `ЭтоДокумент` | 0,70 с |
| преобразовать число из строки | `ЧислоИзСтроки` | 0,44 с |
| добавить реквизит в HTML представление | `ДобавитьРеквизитКHTML` | 0,50 с |
Каждый результат был повторно проверен по live SQL. Чтение `read_selector`
вернуло точное тело `ЭтоДокумент`, а не сохранённый текст из векторного кеша.
## Решение по отдельной векторной БД
Пока не добавлять. SQLite остаётся достаточным для текущего инкрементального
контура и проще связывает `chunk_id`, `text_sha1`, outbox и проверку
актуальности. Векторный кеш не является источником истины.
Повторно оценить ANN-хранилище после заполнения не менее 5 000 актуальных
384-мерных vectors. Практические триггеры:
- p95 строгого поиска выше 1 секунды;
- более 50 000 актуальных chunks;
- SQLite-файл адаптера больше 1 ГиБ из-за embeddings.
Если триггер сработает, Qdrant/pgvector должен быть только производной копией:
ключ `chunk_id`, обязательный `text_sha1`, namespace базы/модели/размерности,
удаление через outbox. Перед ответом адаптер всё равно проверяет live SQL.
## Выявленный следующий приоритет
Глобально найденные активные модули пока часто имеют `object_ref=null`.
Безопасный `read_selector` и имя процедуры присутствуют, но публичное имя
владельца 1С не восстановлено. Это не проблема вектора; это неполный
`metadata.module_owner_cache`. Следующая доработка — фоновое построение
name-first карты владельцев и lazy backfill только для top-кандидатов.
+121 -1
View File
@@ -1,7 +1,8 @@
# 1C Agent Coding Contract
This contract is the default rule set for coding agents that work through the
1C adapter.
1C adapter. The agent calls it only through the `onec_request` MCP tool; the
REST SQL adapter is MCP's private downstream transport.
## Default View
@@ -13,6 +14,57 @@ This contract is the default rule set for coding agents that work through the
- Objects can exist only in saved-state and can later be activated or canceled.
Do not hide them from the agent view.
### Empty saved-state layer
An empty saved-state layer is normal before the first edit. In that case an
agent still uses only public selectors and reports the adapter result; it must
not obtain an active `ConfigCAS`/`Config` module reference and retry by hand.
Known adapter limitation (2026-08-02): for an extension-owned object module
with no prepared saved-state row, public `code.read` can return
`source_missing` even though the active module is proven to exist. Treat this
as an adapter defect, not as evidence that the 1C object or BSL is absent.
Use a developer-owned diagnostic check to investigate it; do not expose its
storage coordinates to a coding agent.
## Required adapter acceptance fixture
`upo_test` must contain one isolated extension-owned `Report` with a decoded
`object_module`, a unique BSL anchor, and no shared business role. This fixture
is created and maintained by a human in Configurator; the SQL-only adapter must
not fabricate it. It is the required target for the public acceptance sequence:
```text
code.read(ref, extension, module_ordinal=1)
→ code.write(old, new, apply_and_rollback)
→ code.search verifies new text
→ rollback and saved-state cleanup
```
The request must contain no `layer`, table, file name, module ref, payload hash,
or stream index. Until the fixture exists, unit tests prove routing only; they
do not prove a live extension Report write.
### Verified base-module smoke
On 2026-08-02 the public sequence was verified on `upo_test` against base
`Report.АвтоматическиеСкидки`, `module_ordinal=1`: a unique comment replacement
completed in about 25 seconds with `verified_and_rolled_back`. The adapter
auto-prepared `ConfigSave`, read back the write, rolled back the BSL change,
then removed the exact rows it had prepared. The public result contains the
opaque `prepare_receipt_id` and `prepare_cleanup`; the final saved-state status
was `empty`. This validates the base route only, not the extension Report route.
Live check on 2026-08-02 found 19 extensions but zero extension-owned Reports
in `upo_test`; therefore the required extension acceptance fixture is currently
missing and the extension write branch remains unaccepted.
Live check on 2026-08-13 found an extension BSL stream for
`Report.УОП_ИнвентаризационнаяОпись`, but its `role_status=unconfirmed`.
That is read-only evidence, not an acceptance fixture: do not write through an
ordinal, storage reference, or guessed Configurator role. A human must add or
identify one extension Report with a decoded `object_module` before the live
extension `apply_and_rollback` test can run.
## Compare Views
- Use `state=both` or `source_state=all` only when the task needs a comparison
@@ -33,6 +85,34 @@ Use public names and selectors:
3. `code.read` with `state=working` to read the module or routine.
4. `code.read` with `state=both` only for an explicit saved-vs-active check.
For a report form, keep the descriptions separate:
1. `metadata.object.forms(ref=Report.<name>, source_state=working)` lists the
report's form references.
2. `metadata.object.form.details` with that same public report ref and the
returned form name reads the form description (attributes, parameters,
commands, items, and the form module).
3. `metadata.form.decode(view=structure)` is an optional compact static
projection. It must retain `unresolved` parent/child links where no SQL
codec has proved them.
A form command does not have its own module. Its handler is a named routine in
the form module only when the decoder returns an evidenced command link.
Use the `read_selector.selector_token` returned by discovery with the method
declared in that selector. Do not copy GUIDs, module refs, table names, or file
names into a follow-up request. A module ordinal, display name, or storage-derived role
is not a Configurator-tree path. If a result has `role_status=unconfirmed` or
`configurator_path_status=unconfirmed`, the agent must not name, write, or
infer its tree owner; report the missing decoder evidence instead.
When checking object commands, call `metadata.object.commands` with the same
public `ref`/`kind`+`name` and `extension` selector. The adapter resolves the
extension name and reads its active metadata itself. A prior `not_found` from a
route that did not carry the extension context is not evidence that the report
has no commands. Conversely, a BSL stream suffix alone is never evidence of a
command or of its module path.
Agents should ask for and report object names, routine names, and code text.
They should not ask users for SQL tables, storage file names, stream indexes, or
saved-state write flags during normal coding work.
@@ -52,12 +132,52 @@ Supported public edit shapes:
say "save this code" and send the desired code text. It must not ask whether SQL
saved-state apply flags are allowed.
For extension object modules, preparation maps the active payload route to its
canonical saved-state filename internally. A public `code.write` therefore
continues with `extension`, `ref`, module role, and the proven replacement
only; it must never ask the caller to supply the canonical filename or stream.
Every successful `code.write` response must show:
- `write_mode.target=saved_state`;
- `write_mode.activation_state=not_activated`;
- `write_mode.production_apply=false`.
### Repository-controlled extension writes
For a repository-controlled extension, use the public sequence below and stop
when it asks for a human Configurator action:
```text
code.search(extension, ref, old fragment)
→ repository.lock.plan(extension, ref)
→ repository.lock.request
→ human captures the exact object in Configurator
→ repository.lock.confirm
→ code.write(extension, ref, module_ordinal, old, new, repository_lock)
→ code.search readback
```
The lock plan for `upo / фс_Отчеты1 /
Report.фс_ПродовольственнаяКорзина` resolves exactly one object:
`Отчет.фс_ПродовольственнаяКорзина`, in extension layer
`extension:8e02accd-8a34-11f1-8294-005056b0d483`. SQL cannot prove a native
Configurator capture; only a human confirmation can continue this route.
`metadata.write.preflight` currently plans canonical high-level
`metadata.write` targets and may return `needs_route` for this compatibility
`code.write` shape before saved-state preparation. This is not permission to
use its low-level payload hint. Use `code.write(mode=plan)` instead: a first
extension edit may return public `needs_prepare`, which means to confirm the
repository lock and reissue the same public request in an apply mode. Keep
using the public `code.write` route; report any different route failure to
adapter developers.
If it returns `extension_saved_state_prepare_protocol_unproven`, do not retry
or supply technical coordinates. The adapter has not yet proven the exact
saved-state preparation codec for this extension layout; report it for adapter
development. Do not infer that the extension was not saved in Configurator.
## Hidden Storage Details
The form module container marker `///----` is adapter-owned storage syntax.
+132
View File
@@ -0,0 +1,132 @@
# Актуальный векторный поиск по коду 1С
Цель: семантический поиск по локальному SQL-индексу адаптера без потери
актуальности кода. Вектор является только производным кешем: перед выдачей
адаптер сверяет найденные фрагменты с текущим состоянием конфигурации и
отбрасывает либо переиндексирует устаревшие записи.
## Выбранная модель
- `Qwen/Qwen3-Embedding-0.6B-GGUF`, квантование `Q8_0`;
- OpenAI-compatible endpoint на `http://docker-gpu.cin.su:8082`;
- `llama.cpp`, `--embedding --pooling last`;
- CPU-only (`--n-gpu-layers 0`), чтобы не менять работающие GPU-сервисы;
- endpoint возвращает 1024 измерения, клиент использует Matryoshka-срез до
запрошенных 384 измерений и повторно нормализует его.
Образ `llama.cpp` зафиксирован digest, а модель — официальным repository/quant
селектором. Это исключает незаметную смену runtime при повторном deploy.
Модель и образ публичные, endpoint работает в изолированном тестовом контуре
без токена. Постоянный кеш модели хранится вне git в
`Z:\LLM\models\cache\llama.cpp`.
## Развёртывание
Проверить итоговую конфигурацию:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/deploy_embeddings.ps1 -ConfigOnly
```
Запустить сервис с загрузкой образа:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/deploy_embeddings.ps1 -Pull
```
Первый запуск скачивает модель в постоянный кеш и поэтому может занять
несколько минут. Скрипт ждёт `/health`, проверяет имя модели и делает реальный
запрос к `/v1/embeddings`.
## Обновление векторов
Сначала адаптер должен содержать актуальные текстовые chunks. Затем worker
забирает только отсутствующие либо изменившиеся фрагменты:
```powershell
python scripts/embed_1c_code_vectors.py `
--adapter-url http://docker.cin.su:8011/rpc `
--base-id upo_test `
--embedding-provider openai-compatible `
--embedding-model qwen3-embedding-0.6b `
--dimensions 384 `
--embedding-base-url http://docker-gpu.cin.su:8082 `
--limit 500 `
--batch-size 8 `
--chunk-kind routine `
--max-text-chars 4000 `
--json
```
Worker по умолчанию индексирует `routine`: процедуры и функции дают наиболее
точный контекст для программирования и заметно быстрее пересчитываются при
частых изменениях. Для диагностического покрытия модульных фрагментов можно
добавить второй `--chunk-kind module`; это более дорогой отдельный проход.
`--max-text-chars` не обрезает код молча: длинные chunks пропускаются в этом
проходе и остаются pending. Их нужно разбивать на окна отдельной задачей либо
индексировать в период низкой нагрузки с большим лимитом.
Размерность входит в имя кеша (`openai-compatible:qwen3-embedding-0.6b@d384`),
поэтому векторы разных размеров никогда не смешиваются.
## Поиск
```powershell
python scripts/search_1c_code_vectors.py `
"где рассчитывается сумма документа перед проведением" `
--adapter-url http://docker.cin.su:8011/rpc `
--base-id upo_test `
--embedding-provider openai-compatible `
--embedding-model qwen3-embedding-0.6b `
--dimensions 384 `
--embedding-base-url http://docker-gpu.cin.su:8082 `
--limit 10 `
--json
```
Поиск вызывается с `strict=true` и `verify=true`. Сохранённые изменения
Конфигуратора перекрывают активную конфигурацию, а удалённые/изменённые chunks
не возвращаются по старому вектору.
Для активного кода расширений хеш `ConfigCAS` разрешается через текущий
manifest расширения в descriptor объекта. Результат содержит обычные
`kind/name`, а также `extension` и `extension_guid`; вызывающему коду не нужно
работать с CAS-хешами как с именами объектов.
Перед первым глобальным поиском после обновления адаптера нужно постранично
заполнить локальную карту владельцев:
```json
{
"method": "metadata.module_owner_cache.backfill",
"payload": {
"base_id": "upo_test",
"limit": 50,
"kind_index": 0,
"offset": 0
}
}
```
Следующий вызов получает `kind_index` и `offset` из `next_cursor`. Повторять до
`complete=true`. Операция читает актуальные метаданные, но пишет только в
локальный SQLite адаптера. Найденные имена сразу добавляются в существующие
строки лексического и векторного индексов; перестраивать embeddings не нужно.
По умолчанию объекты без строк code index быстро пропускаются. `deep=true`
нужен только для отдельного фонового заполнения владельцев неиндексированных
модулей и не должен использоваться в интерактивном поиске.
Для `Qwen3-Embedding` клиент автоматически добавляет к векторизуемому запросу
англоязычную инструкцию поиска по исходному коду 1С, как рекомендует карточка
модели. В `query` адаптера остаётся исходный русский текст, поэтому лексическая
часть гибридного поиска не загрязняется служебным префиксом.
## Отдельная векторная БД
На текущем этапе не требуется. Векторы хранятся рядом с индексом адаптера в
SQLite и выбираются линейным сканированием. Это проще и гарантирует атомарную
проверку актуальности. Отдельный ANN-движок имеет смысл только после замера
десятков тысяч актуальных chunks и неприемлемой задержки; источником истины всё
равно остаётся 1С/SQL, а ANN должен хранить `chunk_id` и `text_sha1` как
проверяемую производную копию.
@@ -6,7 +6,7 @@ form decoder reached zero missing items and zero mismatches.
## Target
- Base: `upo_test`
- Adapter: `http://docker-gpu.cin.su:8011`
- Adapter: `http://docker.cin.su:8011`
- Saved-state table: `ConfigCASSave`
- Form payload file:
`f96a0c45-3eff-11f1-8287-005056b0d483__25c39fbf-35a4-4b43-8e3f-cd1f91082c88.0`
@@ -104,7 +104,7 @@ payload = {
"timeout_seconds": 60,
"max_items": 5000
}
req = urllib.request.Request("http://docker-gpu.cin.su:8011/rpc", data=json.dumps({"method":"metadata.write_learning.capture_after","payload":payload}, ensure_ascii=False).encode("utf-8"), headers={"Content-Type":"application/json"})
req = urllib.request.Request("http://docker.cin.su:8011/rpc", data=json.dumps({"method":"metadata.write_learning.capture_after","payload":payload}, ensure_ascii=False).encode("utf-8"), headers={"Content-Type":"application/json"})
print(urllib.request.urlopen(req, timeout=90).read().decode("utf-8"))
'@ | python -
```
@@ -116,7 +116,7 @@ Then run:
import json, urllib.request
for method in ("metadata.write_learning.diff", "metadata.write_learning.infer_rule"):
payload = {"learning_id": "form-command-binding-standard-customize-form"}
req = urllib.request.Request("http://docker-gpu.cin.su:8011/rpc", data=json.dumps({"method": method, "payload": payload}, ensure_ascii=False).encode("utf-8"), headers={"Content-Type":"application/json"})
req = urllib.request.Request("http://docker.cin.su:8011/rpc", data=json.dumps({"method": method, "payload": payload}, ensure_ascii=False).encode("utf-8"), headers={"Content-Type":"application/json"})
print(urllib.request.urlopen(req, timeout=90).read().decode("utf-8"))
'@ | python -
```
@@ -151,7 +151,7 @@ payload = {
"timeout_seconds": 60,
"max_items": 5000
}
req = urllib.request.Request("http://docker-gpu.cin.su:8011/rpc", data=json.dumps({"method":"metadata.write_learning.capture_after","payload":payload}, ensure_ascii=False).encode("utf-8"), headers={"Content-Type":"application/json"})
req = urllib.request.Request("http://docker.cin.su:8011/rpc", data=json.dumps({"method":"metadata.write_learning.capture_after","payload":payload}, ensure_ascii=False).encode("utf-8"), headers={"Content-Type":"application/json"})
print(urllib.request.urlopen(req, timeout=90).read().decode("utf-8"))
'@ | python -
```
@@ -163,7 +163,7 @@ Then run:
import json, urllib.request
for method in ("metadata.write_learning.diff", "metadata.write_learning.infer_rule"):
payload = {"learning_id": "form-command-binding-local-apply-command"}
req = urllib.request.Request("http://docker-gpu.cin.su:8011/rpc", data=json.dumps({"method": method, "payload": payload}, ensure_ascii=False).encode("utf-8"), headers={"Content-Type":"application/json"})
req = urllib.request.Request("http://docker.cin.su:8011/rpc", data=json.dumps({"method": method, "payload": payload}, ensure_ascii=False).encode("utf-8"), headers={"Content-Type":"application/json"})
print(urllib.request.urlopen(req, timeout=90).read().decode("utf-8"))
'@ | python -
```
+11 -5
View File
@@ -1,5 +1,11 @@
# 1C Form Discovery And Editing
The semantic object/form boundary for agent responses is defined in
[`1c-metadata-structure.md`](1c-metadata-structure.md). In particular, an
owner returns references to forms; form parameters, attributes, commands,
elements, and form-module facts are read through the form's own public
selector. Do not duplicate a decoded form as an invented subtree of its owner.
This runbook adapts the MOXCEL discovery loop to managed forms. The goal is a
full SQL-side form decoder and safe saved-state editing through the test
extension, with XML exports used only as evidence fixtures.
@@ -7,7 +13,7 @@ extension, with XML exports used only as evidence fixtures.
## Current Baseline
- Default base: `upo_test`.
- Default adapter endpoint: `http://docker-gpu.cin.su:8011`.
- Default adapter endpoint: `http://docker.cin.su:8011`.
- Primary test extension/object fixture:
`фс_ДоработкиОбщее` /
`DataProcessor.фс_НастройкаУсловногоОформления`.
@@ -142,7 +148,7 @@ Decode a concrete saved-state form payload:
```powershell
python scripts/smoke_1c_write_matrix.py `
--base-url http://docker-gpu.cin.su:8011 `
--base-url http://docker.cin.su:8011 `
--base-id upo_test `
--table ConfigCASSave `
--file-name <form-file-name> `
@@ -177,7 +183,7 @@ Build a decoder coverage and gap profile for an object form:
```powershell
python scripts/profile_1c_forms.py `
--adapter-url http://docker-gpu.cin.su:8011 `
--adapter-url http://docker.cin.su:8011 `
--base-id upo_test `
--kind Catalog `
--name ЗадачиАссистентаУправления `
@@ -223,7 +229,7 @@ Run the existing source-aware route smoke:
```powershell
python scripts/smoke_1c_saved_state_write_routes.py `
--base-url http://docker-gpu.cin.su:8011 `
--base-url http://docker.cin.su:8011 `
--base-id upo_test `
--table ConfigCASSave `
--file-name <form-file-name> `
@@ -234,7 +240,7 @@ Then run the matrix smoke:
```powershell
python scripts/smoke_1c_write_matrix.py `
--base-url http://docker-gpu.cin.su:8011 `
--base-url http://docker.cin.su:8011 `
--base-id upo_test `
--table ConfigCASSave `
--file-name <form-file-name> `
+124
View File
@@ -0,0 +1,124 @@
# Структура метаданных конфигурации 1С
Этот документ задаёт модель, которой должны следовать агенты и публичные
ответы SQL-адаптера. Он описывает семантические метаданные, а не физические
имена SQL-записей, пути контейнеров или номера потоков.
## Главное правило
У конфигурации нет единого шаблона дочерних узлов для всех классов объектов.
Набор узлов определяется классом объекта и подтверждается двумя источниками:
1. документацией платформы 1С для соответствующего класса;
2. живым чтением выбранного объекта через адаптер.
Не добавляйте в ответ ни табличную часть, ни форму, ни модуль только потому,
что они есть у другого объекта. Если живой декодер не подтвердил конкретный
дочерний объект, верните явный неполный результат, а не пустой вымышленный
узел.
## Связь объекта и формы
Форма — самостоятельный объект метаданных, на который владелец ссылается в
своей коллекции `Формы`. В кратком описании владельца возвращается только
ссылка/идентичность формы: имя, GUID и доступный публичный селектор. Полное
описание формы читается отдельным запросом.
```text
Отчет
└─ Формы
└─ <ссылка на Форму>
Форма
├─ Реквизиты
├─ Параметры
├─ Команды
├─ Модуль формы
└─ Элементы
```
Параметры формы не являются её реквизитами. Это декларация параметризации при
открытии формы. Команда формы не имеет отдельного модуля: её имя обработчика
является свойством команды и разрешается в модуле формы. Аналогично имя
обработчика события элемента — свойство элемента, а не дочерний узел дерева.
Подчинённая команда владельца (`Отчет.Команды.<Имя>`) отличается от команды
формы. Если документация и живая база подтверждают модуль команды, его надо
сообщать как свойство этой команды, не выдавая за форму или за модуль формы.
## Отчёт
Официальная методическая документация 1С подтверждает, что отчёт может иметь
реквизиты и табличные части. Табличная часть, в свою очередь, имеет реквизиты.
Для отчёта допустима следующая *классовая* схема; конкретные экземпляры
показываются только после живого чтения:
```text
Отчет
├─ Реквизиты
├─ Табличные части
│ └─ Реквизиты табличной части
├─ Формы → отдельные описания Форм
├─ Команды
├─ Макеты
├─ Модуль объекта
└─ Модуль менеджера
```
СКД — это тип/содержимое макета, а не обязательная отдельная ветвь любого
отчёта. Не создавайте узел СКД, если в живом объекте не подтверждён
соответствующий макет.
## Публичные маршруты адаптера
Для объекта `Отчет.<Имя>` адаптер использует следующие раздельные операции:
1. `metadata.object.forms` — читает подтверждённые ссылки отчёта на формы;
возвращаемые строки содержат идентичность формы, а не её элементы,
реквизиты, параметры или текст модуля.
2. `metadata.object.form.details` — по владельцу и имени формы получает
развёрнутое описание формы.
3. `metadata.form.decode` — декодирует одну конкретную форму и возвращает её
параметры, реквизиты, элементы, команды, события и сводку встроенного
модуля.
Передавайте `configuration_view=effective_working` либо не передавайте view:
MCP установит этот режим сам. Он означает логическую рабочую конфигурацию с
учётом базовой конфигурации, сохранённых изменений и расширений; он не
разрешает агенту выбирать `ConfigSave` или `ConfigCASSave`.
Таким образом, ссылка `Отчет → Форма` не должна заменяться копией описания
формы внутри объекта отчёта. Агрегированный `metadata.object.full` удобен для
обзора, но для работы с формой агент обязан сохранять её отдельный публичный
селектор и при необходимости вызвать один из двух form-методов выше.
Текущая реализация подтверждает маршрут для `Report`: в
`RELATED_SECTION_RULES` форма имеет категорию `Form`, а
`metadata.object.forms` возвращает только `guid`, `name`, `synonyms` и счётчики
частей. Детали добавляет только `metadata.object.form.details` через отдельный
вызов `metadata.form.decode`.
## Источники
- [Иерархия объектов конфигурации и подчинённые объекты — 1С:EDT](https://its.1c.ru/db/content/edtdoc/src/topics/i030.html)
- [Отчёты, реквизиты и табличные части — методическая поддержка 1С](https://its.1c.ru/db/content/metod8dev/src/platform81/startersdev/i8102579.htm)
- [Формы: реквизиты, параметры, команды и модуль — руководство разработчика 1С](https://its.1c.ru/db/v8310doc/bookmark/dev/TI000000391)
- [Явное объявление параметров формы — стандарт 1С](https://its.1c.ru/db/content/v8std/src/1%C2%A0200/700/i8100741.htm)
## Проверка перед изменением
Перед тем как менять форму или модуль, агент обязан выполнить публичное
чтение владельца, затем получить форму её собственным селектором. Отсутствие
SQL-подключения, нераспознанный контейнер или неоднозначная связь — это
`unresolved`/`protocol_incomplete`, а не разрешение достроить структуру по
документации.
`metadata.form.decode` с `view=structure` возвращает компактные доказанные
факты по статическим элементам. Пока SQL-кодек логического родителя не
доказан, поля `parent` и `children` имеют статус `unresolved`; нельзя строить
их из технической глубины или пути сериализованного контейнера.
Запросы `runtime.form.elements.inspect` и `runtime.form.inspect` возвращают
`runtime_inspection_unsupported`. SQL-адаптер не открывает формы, не запускает
обработчики и не выдаёт созданные СКД во время работы элементы за статические
метаданные.
+1 -1
View File
@@ -240,7 +240,7 @@ changes:
```powershell
python scripts\profile_1c_tabular_templates.py `
--adapter-url http://docker-gpu.cin.su:8011 `
--adapter-url http://docker.cin.su:8011 `
--base-id upo_test `
--inventory-json reports\1c-template-baselines\upo_test_configuration_tabular_templates.json `
--output-json reports\1c-template-baselines\upo_test_tabular_template_profiles.json `
+61 -2
View File
@@ -116,8 +116,11 @@ SQL удобен как быстрый источник данных, но не
canonical path до процедуры, областью является эта процедура/функция; иначе
весь текущий saved-модуль.
По умолчанию `code.write` делает `mode=apply`, но это apply в saved-state
слой (`ConfigSave`/`ConfigCASSave`), а не применение конфигурации в runtime.
По умолчанию `code.write` делает безопасный `mode=plan` и не пишет в SQL.
Только явно переданный `mode=apply`, `apply_and_verify` или
`apply_and_rollback` может записать saved-state слой
(`ConfigSave`/`ConfigCASSave`); это всё равно не применение конфигурации в
runtime.
Адаптер сам выставляет save-first gates и сам выбирает физический маршрут.
Физические детали возвращаются только при `include_storage=true` для
диагностики. Ответ `code.write` всегда содержит `write_mode`: target
@@ -134,6 +137,62 @@ source `saved_state`, activation_state `not_activated`.
добирается отдельным проходом, а `counts.saved_matches` и
`counts.active_matches` показывают покрытие по слоям.
### Repository lock and first extension edit
Для изменения объекта в расширении, подключенном к хранилищу, агент сначала
использует только публичные вызовы:
```text
code.search → repository.lock.plan → repository.lock.request
→ человек захватывает объект в Конфигураторе → repository.lock.confirm
→ code.write → code.search (readback)
```
Если у extension-модуля ещё нет saved-state строки, `code.write(mode=plan)`
возвращает `needs_prepare` и
`diagnostics.next_action=confirm_repository_lock_then_apply`. Это нормальный
первый-edit маршрут: после подтверждённого lock тот же публичный `code.write`
в apply-режиме сам подготовит saved-state. Агент не передаёт `module_ref`,
`stream_index`, таблицу или имя технического файла.
Исключение: `extension_saved_state_prepare_protocol_unproven` означает, что
автоматическая подготовка запрещена. Это не доказательство того, что
расширение не сохраняли: адаптер ещё не доказал точный prepare-кодек для
наблюдаемой extension-layout. Агент не создаёт контейнер через SQL и передаёт
случай разработчикам адаптера без технических координат.
Acceptance extension write считается пройденным только при наличии в
`upo_test` отдельного extension-owned `Report` с object module и успешном
публичном `code.write(..., apply_and_rollback)` без storage-координат. Успех
base-модуля в `ConfigSave` не доказывает ветку `ConfigCAS → ConfigCASSave`.
`upo` не используется для автоматических проверочных записей. Контролируемые
`apply_and_rollback` проверки разрешены только в `upo_test`.
Для регрессии первого extension-edit используйте публичный smoke (без SQL
таблиц, key, module_ref или GUID в запросе):
```powershell
python scripts\smoke_1c_extension_saved_state_prepare.py --apply
```
Он проверяет `plan → prepare/readback → rollback → immediate plan` на
`upo_test / фс_ДоработкиОбщее / Catalog.Номенклатура`. После rollback не
должно остаться saved-state строк, а повторный план должен быть `plan_ready`.
### Safe adapter deployment
Перед Docker-обновлением скрипт развёртывания запрашивает `/health` и ждёт
`runtime.state=ready` и `runtime.active_rpc_count=0`. При остановке REST
переходит в `draining`; уже начатые запросы продолжают выполняться до пяти
минут. Не используйте `-SkipDrainCheck`, кроме аварийного случая, когда
ответственный подтвердил отсутствие активной записи.
После обновления проверяются REST `/health?base_id=upo_test` и MCP `/health`.
JSONL-аудит REST хранится в `/data/adapter-audit.jsonl`, MCP — в
`/data/mcp-audit.jsonl`; оба периодически сворачиваются в
`/data/adapter-audit-reports/latest.json` на соответствующем хосте.
Если фрагмент повторяется, агент должен передать более узкий контекст
(`routine_name`) или заменить процедуру целиком. Адаптер в такой ситуации
возвращает `ambiguous_fragment`, `scope` и `counts.occurrences`, и не
+37 -4
View File
@@ -52,7 +52,7 @@ MCP server configuration.
The MCP proxy does not hard-code the 1C adapter address. Pass it with:
```text
ONEC_ADAPTER_URL=http://docker-gpu.cin.su:8011
ONEC_ADAPTER_URL=http://docker.cin.su:8011
```
Optional adapter bearer token:
@@ -75,6 +75,37 @@ core/deploy/docker/adapter-1c-mcp/.env.example
For real deployment, create a non-committed `.env` next to the compose file and set the actual adapter URL/token there.
## REST adapter on docker.cin.su
The REST SQL adapter is deployed from:
```text
core/deploy/docker/adapter-1c/compose.yaml
```
Its published port is set in a non-committed REST environment file:
```text
ADAPTER_1C_HOST_PORT=8011
```
SQL-base administration is available in the REST adapter at:
```text
http://docker.cin.su:<ADAPTER_1C_HOST_PORT>/admin
```
With the default port this is `http://docker.cin.su:8011/admin`. The page is
used to add or update SQL connections for named bases. Do not put connection
passwords in Git or documentation. If the port changes, use the new port both
for this page and in `ONEC_ADAPTER_URL`.
If that port is occupied, select a free port there and set the MCP URL to the
same value, for example `ONEC_ADAPTER_URL=http://docker.cin.su:18011`.
Keep `ONEC_SQL_BASES_JSON` (or the mounted `/data/onec-sql-bases.json`) only
in that external runtime configuration; never place SQL passwords in this
repository.
## Deploy
Deploy both REST adapter and MCP proxy, then run live verification when a test
@@ -83,6 +114,8 @@ base is available:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass `
-File scripts\deploy_1c_adapter_stack.ps1 `
-RestEnvFile <path-to-non-committed-rest-env> `
-McpEnvFile <path-to-non-committed-mcp-env> `
-BaseId <base-id-from-project-context>
```
@@ -692,7 +725,7 @@ Saved-state BSL write smoke:
```powershell
python scripts\smoke_1c_code_write_saved_state.py `
--adapter-url http://docker-gpu.cin.su:8011 `
--adapter-url http://docker.cin.su:8011 `
--base-id <base-id-from-project-context> `
--extension <extension-name> `
--object-type CommonForm `
@@ -712,7 +745,7 @@ Agent working-view report:
```powershell
python scripts\report_1c_agent_working_view.py `
--adapter-url http://docker-gpu.cin.su:8011 `
--adapter-url http://docker.cin.su:8011 `
--base-id <base-id-from-project-context> `
--extension <extension-name> `
--object-type CommonForm `
@@ -738,7 +771,7 @@ Optional live selector-chain smoke against a real adapter/base:
python scripts\smoke_1c_mcp_selector_chain.py `
--live `
--transport rest `
--adapter-url http://docker-gpu.cin.su:8011 `
--adapter-url http://docker.cin.su:8011 `
--base-id <base-id-from-project-context> `
--json `
--no-report
+130
View File
@@ -0,0 +1,130 @@
# Adapter Observer
`adapter-observer` is an independent, read-only web service for adapter telemetry.
It must never connect to 1C SQL, call write methods, modify `/data`, or be a
runtime dependency of `adapter-1c-rest` or `adapter-1c-mcp`.
## Inputs and guarantees
- It mounts REST and MCP Docker volumes at `/audit:ro` and `/mcp-audit:ro` and
reads only rotated `adapter-audit.jsonl*` / `mcp-audit.jsonl*` files.
- The source events contain public selector summaries, status/error, request
correlation id, timings, and safe result summaries. They intentionally omit
BSL text, raw SQL payloads, storage keys, and credentials.
- A missing or malformed log is an Observer condition, never an adapter error.
- The UI distinguishes `exception` from expected safe outcomes such as
`blocked`, `unsupported`, and `invalid_argument`.
## Tabs
- **Журнал запросов**: filterable individual REST events and their safe details.
- **Аналитика**: p50/p95, slow method ranking, grouped non-OK fingerprints and
evidence-based next actions.
- **Покрытие**: contract for adding a future `metadata.adapter.audit` snapshot.
## Retry boundary
Audit JSONL deliberately has no complete request payload. Do not reconstruct
or guess it from selector summaries. A future replay button may repeat only a
request captured by Observer itself with an explicit read-only allowlist. It
must never replay a write request from logs.
## Deployment
On `docker.cin.su`, inspect the existing REST container and volume first:
```text
docker inspect adapter-1c-rest
docker volume ls
```
Deploy only the Observer stack from `core/deploy/docker/adapter-observer`.
It uses external volume `adapter-1c_adapter-1c-data` read-only and port 8031
by default. Do not run `down` against the adapter compose project.
```text
docker compose --env-file .env -f compose.yaml up -d --build
curl http://localhost:8031/health
```
## Development contract
When adapter telemetry changes, preserve backwards parsing: unknown fields are
shown in event details; known metrics remain optional. Before adding a special
visualization, record its input schema and add fixture JSONL tests. The general
journal must continue working for unknown adapter methods.
## Key decisions for future agents
1. Keep Observer a separate Compose project, port, image and failure domain.
Never add it as a dependency to REST/MCP and never restart those containers
while deploying it.
2. The volumes are externally named `adapter-1c_adapter-1c-data` and
`adapter-1c-mcp_adapter-1c-mcp-data` on `docker.cin.su`; Observer mounts
them only as `/audit:ro` and `/mcp-audit:ro`.
3. Audit events are evidence, not replay payloads. The historical journal can
link to an object by a public selector but cannot reconstruct omitted fields.
4. Treat job lifecycle statuses `accepted`, `running`, `done` and `cancelled`
as operational state, not failures. Expected rejections are shown separately
from adapter exceptions.
5. Retain both `duration_ms` (Observer-facing REST wall time) and optional
result timings. Do not manufacture nested timings if the adapter did not
return them; the first precise per-span waterfall requires an Observer-owned
read-only proxy/session trace.
## Delivery plan
### Delivered MVP
- Rotated REST JSONL reader, safe event details and filters.
- p50/p95/max latency by method, error fingerprint grouping and guidance.
- Read-only Docker deployment and health endpoint.
- MCP-to-REST request correlation by `request_id`; absent REST pair is shown as
a transport boundary, not a decoder failure.
- A bounded read-only coverage refresh (`help.methods` and
`metadata.adapter.audit`) stored in Observer's own state volume, with the
latest 50 snapshots per base and a schema/method/count delta.
- A best-effort background coverage snapshot every 900 seconds for `upo_test`.
It has a 300-second minimum interval and must never affect UI availability.
### Next safe increments
1. Add an Observer-owned, read-only allowlist proxy. It can save complete
*sanitized* read request payloads for user-initiated replay and form a true
parent/child waterfall; no historical write replay.
2. Periodically invoke `metadata.adapter.audit` through that proxy and persist
versioned coverage snapshots in an Observer-owned SQLite database.
3. Add regression screens: compare coverage/schema/method catalog before and
after an adapter release, with an explicit `not comparable` state.
4. Add retention, export and role controls before exposing the journal outside
the internal network.
### Explicit non-goals until separately approved
- No replay of historical write, activation, repository or password operations.
- No raw request/response capture solely to make replay convenient.
- No automatic remediation, SQL optimization, Configurator launch or mutation.
- No claim that a slow request is an adapter defect without repeated evidence.
### Items commonly forgotten in observability work
- **Clock semantics:** preserve source UTC timestamp and Observer receive time;
never compare durations across hosts as if clocks were synchronized.
- **Asynchrony:** display job poll calls separately from end-to-end job time;
polling volume must not dominate failure charts.
- **Cardinality:** bound selector/error fingerprints so arbitrary object names
cannot create an unbounded metrics index.
- **Release correlation:** record Observer version and adapter health/method
catalog snapshot beside each periodic coverage run.
- **Availability:** health, disk/read errors and log rotation failures of
Observer must appear in its own diagnostics, not as adapter failures.
- **Retention and access:** define data lifetime, exported fields and viewer
permissions before making port 8031 internet-facing or adding Caddy routes.
## Review checklist
- Does a proposed feature work with omitted payload fields rather than guessing?
- Does it classify expected rejection separately from exception/transport loss?
- Does it preserve `request_id` and public selector provenance?
- Is every newly persisted field redacted and bounded by retention?
- Can the Observer be stopped without affecting adapter requests?
@@ -0,0 +1,50 @@
# Прикладной эталон дополнительных реквизитов для разработки
## Цель
Опубликовать в изолированной базе `upo_test` read-only endpoint, который
читает ПВХ средствами платформы 1С. Это эталон для разработки и регрессионного
сравнения SQL-адаптера; адаптер к endpoint не подключается.
## Изолированное расширение
1. В Конфигураторе создайте новое расширение `AdapterAdditionalAttributesBridge`.
2. Добавьте серверный общий модуль `ДополнительныеРеквизитыReadOnly` и вставьте
содержимое `plugins/1c/bridge/additional_attributes_readonly.bsl`.
3. Добавьте HTTP-сервис `AdapterRuntimeBridge` с URL `/runtime-bridge`.
4. Добавьте URL-шаблон `rpc`, метод `POST`, и обработчик
`ДополнительныеРеквизитыHTTP.ОбработатьRPC`.
5. Добавьте серверный модуль `ДополнительныеРеквизитыHTTP` и вставьте
`plugins/1c/bridge/additional_attributes_http_handler.bsl`.
6. Ограничьте публикацию тестовой сетью и отдельным техническим пользователем
только с правами чтения ПВХ и справочника `СтруктурныеЕдиницы` вместе с его
табличной частью `ДополнительныеРеквизиты`.
7. Обновите конфигурацию базы из расширения и опубликуйте HTTP-сервис.
Не меняйте существующие `Chatbot`, биллинг, телефонию или сервисы обмена.
## Проверка endpoint
```json
{"method":"additional_attributes.find","payload":{"base_id":"upo_test","query":"Ответственное направление","include_deleted":false,"limit":10}}
```
Ожидается `status=found` и как минимум `ref`, `description`,
`identifier_for_formula`, `property_set`, `value_type`.
Затем:
```json
{"method":"additional_attributes.storage.resolve","payload":{"base_id":"upo_test","property_ref":"<UUID свойства>","owner_ref":"Catalog.СтруктурныеЕдиницы"}}
```
Ожидается `status=confirmed` и источник
`Справочник.СтруктурныеЕдиницы.ДополнительныеРеквизиты`.
## Использование результата
Сравните ответ endpoint с `data.list`, `data.schema`,
`additional_attributes.find` и `additional_attributes.storage.resolve` SQL
адаптера. В код адаптера переносятся только подтверждённые общие правила
разбора метаданных и SQL-маршрута; адрес endpoint, учётные данные и вызовы
платформы в адаптер не добавляются.