Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "v8-runner"
version = "0.4.2"
version = "0.5.0"
edition = "2021"

[[bin]]
Expand Down
8 changes: 6 additions & 2 deletions docs/CAPABILITIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,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 @@ -278,7 +282,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 @@ -121,6 +121,7 @@ infobase:
connection: "File=build/ib"
user: Admin
password: secret
unlock_code: seal-42 # optional `/UC <значение>` для запароленных конфигураций

source-set:
- name: main
Expand All @@ -132,6 +133,7 @@ source-set:

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

tools:
client_mcp:
Expand Down Expand Up @@ -318,6 +320,16 @@ tests:

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

#### `infobase.unlock_code`

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

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

#### `infobase.dbms`

- Тип: объект
Expand Down Expand Up @@ -375,6 +387,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
9 changes: 8 additions & 1 deletion docs/schemas/v8project.local.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,13 @@
"null"
]
},
"unlock_code": {
"description": "Optional local infobase unlock code propagated as `/UC <value>`. Masked in command logs.",
"type": [
"string",
"null"
]
},
"user": {
"description": "Optional local infobase user name.",
"type": [
Expand Down Expand Up @@ -570,7 +577,7 @@
"type": "object"
}
},
"$id": "https://raw.githubusercontent.com/alkoleft/v8-runner-rust/refs/tags/v0.4.2/docs/schemas/v8project.local.schema.json",
"$id": "https://raw.githubusercontent.com/alkoleft/v8-runner-rust/refs/tags/v0.5.0/docs/schemas/v8project.local.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"properties": {
Expand Down
13 changes: 12 additions & 1 deletion docs/schemas/v8project.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"BuildSchema": {
"additionalProperties": false,
"properties": {
"dynamicUpdate": {
"description": "Default `/UpdateDBCfg -Dynamic+` toggle for `build`. CLI `--dynamic` overrides this.",
"type": "boolean"
},
"partialLoadThreshold": {
"description": "Maximum changed-file count for partial Designer load before falling back to full load.",
"format": "uint",
Expand Down Expand Up @@ -228,6 +232,13 @@
"null"
]
},
"unlock_code": {
"description": "Optional unlock code propagated as `/UC <value>` to DESIGNER. Masked in command logs.",
"type": [
"string",
"null"
]
},
"user": {
"description": "Optional infobase user name passed to platform utilities.",
"type": [
Expand Down Expand Up @@ -623,7 +634,7 @@
"type": "object"
}
},
"$id": "https://raw.githubusercontent.com/alkoleft/v8-runner-rust/refs/tags/v0.4.2/docs/schemas/v8project.schema.json",
"$id": "https://raw.githubusercontent.com/alkoleft/v8-runner-rust/refs/tags/v0.5.0/docs/schemas/v8project.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"properties": {
Expand Down
8 changes: 8 additions & 0 deletions src/cli/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,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 @@ -702,6 +702,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 @@ -2471,6 +2473,7 @@ mod tests {
map_build_request(&BuildArgs {
full_rebuild: true,
source_set: None,
dynamic: false,
})
.full_rebuild
);
Expand Down Expand Up @@ -2759,6 +2762,7 @@ mod tests {
command_name(&Command::Build(BuildArgs {
full_rebuild: false,
source_set: None,
dynamic: false,
})),
CommandName::Build
);
Expand Down Expand Up @@ -2829,6 +2833,7 @@ mod tests {
&Command::Build(BuildArgs {
full_rebuild: true,
source_set: None,
dynamic: false,
}),
&presenter,
false,
Expand Down Expand Up @@ -2956,6 +2961,7 @@ mod tests {
&Command::Build(BuildArgs {
full_rebuild: true,
source_set: None,
dynamic: false,
}),
&presenter,
true,
Expand Down
31 changes: 31 additions & 0 deletions src/config/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,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 @@ -452,6 +452,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 @@ -474,6 +477,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 @@ -533,6 +539,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 @@ -1360,6 +1360,7 @@ mod tests {
}],
build: BuildConfig {
partial_load_threshold: 0,
dynamic_update: false,
},
tools: ToolsConfig::default(),
mcp: Default::default(),
Expand Down Expand Up @@ -2175,6 +2176,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 @@ -826,6 +827,7 @@ mod tests {
&McpBuildProjectRequest {
full_rebuild: Some(true),
source_set: Some("main".to_owned()),
dynamic_update: None,
},
)
.expect("success");
Expand Down
Loading