Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions SKILL/references/config-and-backends.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ settings before CLI overrides.
- `format`: `DESIGNER` or `EDT`.
- `builder`: `DESIGNER` or `IBCMD`.
- `infobase.connection`: often `File=build/ib` for local automation.
- `infobase.unlock_code`: optional infobase locking code, propagated to DESIGNER as `/UC <value>`. Required when the configuration was sealed with "Установить пароль"; masked in logs. Place this in `v8project.local.yaml` together with `infobase.password`.
- `build.dynamicUpdate`: project-wide default for `/UpdateDBCfg -Dynamic+`. Off by default. CLI `build --dynamic` overrides it for a single invocation.
- `source-set`: ordered configuration and extension sources.
- `tools.platform.path` or `tools.platform.version`: 1C platform discovery hints.
- `tools.edt_cli.path`, `version`, and `interactive-mode`: EDT CLI discovery and execution mode.
Expand Down
12 changes: 12 additions & 0 deletions SKILL/references/project-workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ Use a full rebuild after branch switches, rebases, broad object moves, or suspic
v8-runner build --full-rebuild
```

Use dynamic update when the infobase has live HTTP services or background jobs that block the
exclusive lock required by the default static `/UpdateDBCfg`:

```bash
v8-runner build --dynamic
```

Equivalent project-wide default is `build.dynamicUpdate: true` in `v8project.yaml`. The CLI
flag overrides the config for one invocation. The platform refuses dynamic mode when the
change set requires restructuring; `v8-runner` surfaces that error verbatim and does not fall
back to a static update.

`build` is a common workflow. For EDT projects it may export EDT sources to Designer files before applying them through the configured backend. For Designer projects it applies Designer sources directly through the configured backend.

If `tools.client_mcp.extension` is configured, `build` also prepares that tool extension after the project source-set stage, including scoped `--source-set` builds. Source-backed tool extensions use their own change-detection state and are skipped when unchanged; use `build --full-rebuild` to force refresh. Do not add a tool extension as a project `source-set` or select it with `--source-set`.
Expand Down
8 changes: 6 additions & 2 deletions docs/CAPABILITIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,14 @@ v8-runner extensions [--name <SOURCE_SET>...]
### `build`

```bash
v8-runner build [--source-set <NAME>] [--full-rebuild]
v8-runner build [--source-set <NAME>] [--full-rebuild] [--dynamic]
```

- Без `--source-set` обрабатывает все configured `source-set` в canonical order.
- `--dynamic` (или `build.dynamicUpdate: true` в `v8project.yaml`) добавляет к
`/UpdateDBCfg` флаг `-Dynamic+`. Платформа применяет изменения без захвата
исключительной блокировки; на изменениях, требующих реструктуризации, DESIGNER возвращает
ошибку — fallback на статический режим не выполняется.
- С `--source-set` project stage анализирует и строит только указанный `source-set`; неизвестное
имя отклоняется как validation error.
- Для `DESIGNER` выбирает incremental, partial или full path по изменённым файлам выбранного scope.
Expand Down Expand Up @@ -308,7 +312,7 @@ v8-runner mcp serve http

| Инструмент | Основные поля запроса | Примечания |
| --- | --- | --- |
| `build_project` | `fullRebuild`, `sourceSet` | `fullRebuild=false`; `sourceSet` omitted значит все source-set |
| `build_project` | `fullRebuild`, `sourceSet`, `dynamicUpdate` | `fullRebuild=false`; `sourceSet` omitted значит все source-set; `dynamicUpdate` (опц.) переопределяет `build.dynamicUpdate` для одного вызова |
| `run_all_tests` | `full` | Компактный вывод по умолчанию |
| `run_module_tests` | `moduleName`, `full` | Отклоняет пустой `moduleName` |
| `dump_config` | `mode`, `extension`, `objects` | Пустой `mode` нормализуется в `INCREMENTAL` |
Expand Down
24 changes: 24 additions & 0 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ infobase:
connection: "File=build/ib"
user: Admin
password: secret
unlock_code: seal-42 # optional `/UC <значение>` для запароленных конфигураций

source-set:
- name: main
Expand All @@ -138,6 +139,7 @@ source-set:

build:
partialLoadThreshold: 20
dynamicUpdate: false # `/UpdateDBCfg -Dynamic+` по умолчанию

tools:
client_mcp:
Expand Down Expand Up @@ -314,6 +316,16 @@ tests:

Credentials самой информационной базы.

#### `infobase.unlock_code`

- Тип: строка
- Обязателен: нет

Кодовое слово (`Конфигурация → Установить пароль`), которое транслируется в DESIGNER как
`/UC <значение>`. Без него платформа отказывается выполнять административные операции на
запароленных конфигурациях. Значение маскируется в логах команд (`/UC ***`), поэтому его
безопасно держать в `v8project.local.yaml` рядом с `infobase.password`.

#### `infobase.dbms`

- Тип: объект
Expand Down Expand Up @@ -373,6 +385,18 @@ Validation rules:

Порог между partial и full load.

#### `build.dynamicUpdate`

- Тип: boolean
- По умолчанию: `false`

Включает режим динамического обновления (`/UpdateDBCfg -Dynamic+`) для `build`. Полезно,
когда в инфобазе живут HTTP-сервисы или фоновые задания и захват исключительной блокировки
нежелателен. Если изменения требуют реструктуризации, DESIGNER возвращает ошибку, и
`v8-runner` пробрасывает её наружу — fallback на статический режим не выполняется.

CLI-флаг `v8-runner build --dynamic` переопределяет это значение на одну команду.

CLI selector `v8-runner build --source-set <name>` использует `source-set[].name` как stable
runtime identity и не добавляет отдельное поле конфигурации. Если selector не задан, `build`
обрабатывает все `source-set`.
Expand Down
8 changes: 8 additions & 0 deletions src/cli/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ pub struct BuildArgs {
/// Limit build to one source-set from v8project.yaml
#[arg(long)]
pub source_set: Option<String>,

/// Apply changes via `/UpdateDBCfg -Dynamic+` (no exclusive lock).
///
/// Overrides `build.dynamicUpdate` from v8project.yaml for this run. The platform itself
/// refuses dynamic mode when restructuring is required; the runner surfaces that error
/// instead of falling back to a static update.
#[arg(long)]
pub dynamic: bool,
}

#[derive(Args, Debug)]
Expand Down
6 changes: 6 additions & 0 deletions src/cli/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,8 @@ fn map_build_request(args: &BuildArgs) -> BuildRequest {
BuildRequest {
full_rebuild: args.full_rebuild,
source_set: args.source_set.clone(),
// CLI flag is a one-shot override; absence means "fall back to project config".
dynamic_update: if args.dynamic { Some(true) } else { None },
}
}

Expand Down Expand Up @@ -2548,6 +2550,7 @@ mod tests {
map_build_request(&BuildArgs {
full_rebuild: true,
source_set: None,
dynamic: false,
})
.full_rebuild
);
Expand Down Expand Up @@ -2829,6 +2832,7 @@ mod tests {
command_name(&Command::Build(BuildArgs {
full_rebuild: false,
source_set: None,
dynamic: false,
})),
CommandName::Build
);
Expand Down Expand Up @@ -2899,6 +2903,7 @@ mod tests {
&Command::Build(BuildArgs {
full_rebuild: true,
source_set: None,
dynamic: false,
}),
None,
&presenter,
Expand Down Expand Up @@ -3027,6 +3032,7 @@ mod tests {
&Command::Build(BuildArgs {
full_rebuild: true,
source_set: None,
dynamic: false,
}),
None,
&presenter,
Expand Down
31 changes: 31 additions & 0 deletions src/config/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,37 @@ mod tests {
assert_eq!(config.build.partial_load_threshold, 7);
}

#[test]
fn load_config_reads_build_dynamic_update_and_infobase_unlock_code() {
let dir = tempdir().expect("tempdir");
let base = dir.path().join("base");
let work = dir.path().join("work");
let src = base.join("src");
std::fs::create_dir_all(&src).expect("src dir");
let config_path = dir.path().join("v8project.yaml");
std::fs::write(
&config_path,
format!(
"basePath: {}\nworkPath: {}\nformat: DESIGNER\nbuilder: DESIGNER\ninfobase:\n connection: \"File=/tmp/ib\"\n unlock_code: seal-1\nbuild:\n dynamicUpdate: true\nsource-set:\n - name: main\n type: CONFIGURATION\n path: src\n",
base.display(),
work.display()
),
)
.expect("write config");

let config = load_config(config_path.to_str(), None).expect("load config");

assert!(config.build.dynamic_update);
assert_eq!(config.infobase.unlock_code.as_deref(), Some("seal-1"));

// And the resulting V8Connection carries the unlock code into the platform layer.
let connection = config.v8_connection();
assert_eq!(connection.unlock_code.as_deref(), Some("seal-1"));
let args = connection.args();
let uc_index = args.iter().position(|arg| arg == "/UC").expect("/UC");
assert_eq!(args.get(uc_index + 1).map(String::as_str), Some("seal-1"));
}

#[test]
fn load_config_reads_test_timeout_from_exact_yaml_key() {
let dir = tempdir().expect("tempdir");
Expand Down
22 changes: 22 additions & 0 deletions src/config/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ pub struct InfobaseConfig {
/// Optional infobase password passed to platform utilities.
pub password: Option<String>,

/// Optional infobase unlock code propagated to DESIGNER calls as `/UC <value>`.
///
/// Required by configurations sealed with `Конфигурация → Установить пароль`; without the
/// matching code the platform refuses every administrative operation. The value is treated
/// as a secret and masked in command logs.
#[serde(default)]
pub unlock_code: Option<String>,

/// Optional DBMS contract for server-based infobases.
#[serde(default)]
pub dbms: Option<InfobaseDbmsConfig>,
Expand All @@ -79,6 +87,7 @@ impl InfobaseConfig {
connection: connection.into(),
user: None,
password: None,
unlock_code: None,
dbms: None,
}
}
Expand All @@ -98,6 +107,7 @@ impl InfobaseConfig {
connection: connection.into(),
user: None,
password: None,
unlock_code: None,
dbms: Some(dbms),
}
}
Expand Down Expand Up @@ -159,6 +169,7 @@ impl AppConfig {
let mut conn = V8Connection::from_connection_string(&self.infobase.connection);
conn.user = self.infobase.user.clone();
conn.password = self.infobase.password.clone();
conn.unlock_code = self.infobase.unlock_code.clone();
conn
}

Expand Down Expand Up @@ -226,12 +237,23 @@ impl SourceSetPurpose {
pub struct BuildConfig {
#[serde(default = "default_partial_load_threshold")]
pub partial_load_threshold: usize,

/// Default mode for `/UpdateDBCfg` during `build`.
///
/// When `true`, DESIGNER is invoked with `-Dynamic+`, which lets the platform apply
/// metadata changes without taking an exclusive infobase lock (useful when HTTP services
/// or background jobs are live). If the change set is incompatible with dynamic update
/// (e.g. restructuring), DESIGNER returns an error — the runner does NOT silently fall
/// back to a static update. CLI `--dynamic` overrides this field for a single invocation.
#[serde(default)]
pub dynamic_update: bool,
}

impl Default for BuildConfig {
fn default() -> Self {
Self {
partial_load_threshold: default_partial_load_threshold(),
dynamic_update: false,
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions src/config/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,9 @@ struct InfobaseSchema {
/// Optional infobase password passed to platform utilities.
#[serde(default, skip_serializing_if = "Option::is_none")]
password: Option<String>,
/// Optional unlock code propagated as `/UC <value>` to DESIGNER. Masked in command logs.
#[serde(default, skip_serializing_if = "Option::is_none")]
unlock_code: Option<String>,
/// Optional DBMS settings for server-based infobases.
#[serde(default, skip_serializing_if = "Option::is_none")]
dbms: Option<InfobaseDbmsSchema>,
Expand All @@ -463,6 +466,9 @@ struct PartialInfobaseSchema {
/// Optional local infobase password.
#[serde(default, skip_serializing_if = "Option::is_none")]
password: Option<String>,
/// Optional local infobase unlock code propagated as `/UC <value>`. Masked in command logs.
#[serde(default, skip_serializing_if = "Option::is_none")]
unlock_code: Option<String>,
/// Optional local DBMS settings override.
#[serde(default, skip_serializing_if = "Option::is_none")]
dbms: Option<PartialInfobaseDbmsSchema>,
Expand Down Expand Up @@ -522,6 +528,14 @@ struct BuildSchema {
)]
#[schemars(with = "usize")]
partial_load_threshold: Option<usize>,
/// Default `/UpdateDBCfg -Dynamic+` toggle for `build`. CLI `--dynamic` overrides this.
#[serde(
default,
deserialize_with = "deserialize_non_null_optional",
skip_serializing_if = "Option::is_none"
)]
#[schemars(with = "bool")]
dynamic_update: Option<bool>,
}

#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
Expand Down
2 changes: 2 additions & 0 deletions src/config/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,7 @@ mod tests {
}],
build: BuildConfig {
partial_load_threshold: 0,
dynamic_update: false,
},
tools: ToolsConfig::default(),
mcp: Default::default(),
Expand Down Expand Up @@ -2150,6 +2151,7 @@ mod tests {
connection: "Srvr=localhost;Ref=ib".to_owned(),
user: None,
password: None,
unlock_code: None,
dbms: None,
},
source_sets: vec![SourceSetConfig {
Expand Down
1 change: 1 addition & 0 deletions src/mcp/port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ mod tests {
&BuildRequest {
full_rebuild: true,
source_set: None,
dynamic_update: None,
},
)
.expect_err("busy workspace");
Expand Down
5 changes: 5 additions & 0 deletions src/mcp/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ pub struct McpBuildProjectRequest {
/// Optional source-set selector from v8project.yaml.
#[schemars(description = "Source-set name to build. When omitted, all source-sets are built.")]
pub source_set: Option<String>,
/// Optional one-shot override for `/UpdateDBCfg -Dynamic+`.
#[schemars(
description = "Override build.dynamicUpdate for this call: true applies changes without exclusive lock."
)]
pub dynamic_update: Option<bool>,
}

/// MCP request for `run_all_tests`.
Expand Down
2 changes: 2 additions & 0 deletions src/mcp/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ where
let use_case_request = BuildRequest {
full_rebuild: request.full_rebuild.unwrap_or(false),
source_set: request.source_set.clone(),
dynamic_update: request.dynamic_update,
};

match self
Expand Down Expand Up @@ -823,6 +824,7 @@ mod tests {
&McpBuildProjectRequest {
full_rebuild: Some(true),
source_set: Some("main".to_owned()),
dynamic_update: None,
},
)
.expect("success");
Expand Down
Loading