-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathanswers.json
More file actions
47 lines (47 loc) · 37.3 KB
/
answers.json
File metadata and controls
47 lines (47 loc) · 37.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
{
"plan_version": 1,
"metadata": {
"generator": "greentic-component/wizard-provider",
"template_version": "component-scaffold-v0.6.0",
"template_digest_blake3": "4fcfbee7de71be72c1a998ef26fd2fce7da2528857c456e89b66c88aff64979e",
"requested_abi_version": "0.6.0"
},
"target_root": "/tmp/wizard-demo",
"plan": {
"meta": {
"id": "greentic.component.scaffold",
"target": "component",
"mode": "scaffold"
},
"steps": [
{
"type": "ensure_dir",
"paths": [
"assets/i18n",
"schemas",
"src",
"tools"
]
},
{
"type": "write_files",
"files": {
"Cargo.toml": "[package]\nname = \"wizard-demo\"\nversion = \"0.1.0\"\nedition = \"2024\"\nlicense = \"MIT\"\nrust-version = \"1.91\"\ndescription = \"Greentic component wizard-demo\"\nbuild = \"build.rs\"\n\n[lib]\ncrate-type = [\"cdylib\", \"rlib\"]\n\n[package.metadata.greentic]\nabi_version = \"0.6.0\"\n\n[package.metadata.component]\npackage = \"greentic:component\"\n\n[package.metadata.component.target]\nworld = \"greentic:component/[email protected]\"\n\n[dependencies]\ngreentic-types = { version = \"0.4\" }\ngreentic-interfaces-guest = { version = \"0.4\", default-features = false, features = [\"component-v0-6\"] }\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\n\n[build-dependencies]\ngreentic-types = { version = \"0.4\" }\nserde_json = \"1\"\n",
"Makefile": "SHELL := /bin/sh\n\nNAME := $(shell awk 'BEGIN{in_pkg=0} /^\\[package\\]/{in_pkg=1; next} /^\\[/{in_pkg=0} in_pkg && /^name = / {gsub(/\"/ , \"\", $$3); print $$3; exit}' Cargo.toml)\nNAME_UNDERSCORE := $(subst -,_,$(NAME))\nABI_VERSION := $(shell awk 'BEGIN{in_meta=0} /^\\[package.metadata.greentic\\]/{in_meta=1; next} /^\\[/{in_meta=0} in_meta && /^abi_version = / {gsub(/\"/ , \"\", $$3); print $$3; exit}' Cargo.toml)\nABI_VERSION_UNDERSCORE := $(subst .,_,$(ABI_VERSION))\nDIST_DIR := dist\nWASM_OUT := $(DIST_DIR)/$(NAME)__$(ABI_VERSION_UNDERSCORE).wasm\nGREENTIC_COMPONENT ?= greentic-component\n\n.PHONY: build test fmt clippy wasm doctor\n\nbuild:\n\tcargo build\n\ntest:\n\tcargo test\n\nfmt:\n\tcargo fmt\n\nclippy:\n\tcargo clippy --all-targets --all-features -- -D warnings\n\nwasm:\n\tif ! cargo component --version >/dev/null 2>&1; then \\\n\t\techo \"cargo-component is required to produce a valid [email protected] wasm\"; \\\n\t\techo \"install with: cargo install cargo-component --locked\"; \\\n\t\texit 1; \\\n\tfi\n\tRUSTFLAGS= CARGO_ENCODED_RUSTFLAGS= $(GREENTIC_COMPONENT) build --manifest ./component.manifest.json\n\tWASM_SRC=\"\"; \\\n\tfor cand in \\\n\t\t\"$${CARGO_TARGET_DIR:-target}/wasm32-wasip2/release/$(NAME_UNDERSCORE).wasm\" \\\n\t\t\"$${CARGO_TARGET_DIR:-target}/wasm32-wasip2/release/$(NAME).wasm\" \\\n\t\t\"target/wasm32-wasip2/release/$(NAME_UNDERSCORE).wasm\" \\\n\t\t\"target/wasm32-wasip2/release/$(NAME).wasm\"; do \\\n\t\tif [ -f \"$$cand\" ]; then WASM_SRC=\"$$cand\"; break; fi; \\\n\tdone; \\\n\tif [ -z \"$$WASM_SRC\" ]; then \\\n\t\techo \"unable to locate wasm32-wasip2 component build artifact for $(NAME)\"; \\\n\t\texit 1; \\\n\tfi; \\\n\tmkdir -p $(DIST_DIR); \\\n\tcp \"$$WASM_SRC\" $(WASM_OUT); \\\n\t$(GREENTIC_COMPONENT) hash ./component.manifest.json --wasm $(WASM_OUT)\n\ndoctor:\n\t$(GREENTIC_COMPONENT) doctor $(WASM_OUT) --manifest ./component.manifest.json\n",
"README.md": "# wizard-demo\n\nGenerated by `greentic-component wizard` for [email protected].\n\n## Next steps\n- Extend QA flows in `src/qa.rs` and i18n keys in `src/i18n.rs`.\n- Generate/update locales via `./tools/i18n.sh`.\n- Rebuild to embed translations: `cargo build`.\n\n## QA ops\n- `qa-spec`: emits setup/update/remove semantics and accepts `default|setup|install|update|upgrade|remove`.\n- `apply-answers`: returns base response shape `{ ok, config?, warnings, errors }`.\n- `i18n-keys`: returns i18n keys used by QA/setup messaging.\n\n## ABI version\nRequested ABI version: 0.6.0\n\nNote: the wizard currently emits a fixed 0.6.0 template.\n",
"assets/i18n/en.json": "{\n \"qa.install.title\": \"Install configuration\",\n \"qa.install.description\": \"Provide values for initial provider setup.\",\n \"qa.update.title\": \"Update configuration\",\n \"qa.update.description\": \"Adjust existing provider settings.\",\n \"qa.remove.title\": \"Remove configuration\",\n \"qa.remove.description\": \"Confirm provider removal settings.\",\n \"qa.field.api_key.label\": \"API key\",\n \"qa.field.api_key.help\": \"Secret key used to authenticate provider requests.\",\n \"qa.field.region.label\": \"Region\",\n \"qa.field.region.help\": \"Region identifier for the provider account.\",\n \"qa.field.webhook_base_url.label\": \"Webhook base URL\",\n \"qa.field.webhook_base_url.help\": \"Public base URL used for webhook callbacks.\",\n \"qa.field.enabled.label\": \"Enable provider\",\n \"qa.field.enabled.help\": \"Enable this provider after setup completes.\",\n \"qa.field.confirm_remove.label\": \"Confirm removal\",\n \"qa.field.confirm_remove.help\": \"Set to true to allow provider removal.\",\n \"qa.error.required\": \"One or more required fields are missing.\",\n \"qa.error.remove_confirmation\": \"Removal requires explicit confirmation.\"\n}\n",
"assets/i18n/locales.json": "[\"ar\",\"ar-AE\",\"ar-DZ\",\"ar-EG\",\"ar-IQ\",\"ar-MA\",\"ar-SA\",\"ar-SD\",\"ar-SY\",\"ar-TN\",\"ay\",\"bg\",\"bn\",\"cs\",\"da\",\"de\",\"el\",\"en-GB\",\"es\",\"et\",\"fa\",\"fi\",\"fr\",\"fr-FR\",\"gn\",\"gu\",\"hi\",\"hr\",\"ht\",\"hu\",\"id\",\"it\",\"ja\",\"km\",\"kn\",\"ko\",\"lo\",\"lt\",\"lv\",\"ml\",\"mr\",\"ms\",\"my\",\"nah\",\"ne\",\"nl\",\"nl-NL\",\"no\",\"pa\",\"pl\",\"pt\",\"qu\",\"ro\",\"ru\",\"si\",\"sk\",\"sr\",\"sv\",\"ta\",\"te\",\"th\",\"tl\",\"tr\",\"uk\",\"ur\",\"vi\",\"zh\"]\n",
"build.rs": "#[path = \"src/i18n_bundle.rs\"]\nmod i18n_bundle;\n\nuse std::env;\nuse std::fs;\nuse std::path::Path;\n\n// Build-time embedding pipeline:\n// 1) Read assets/i18n/*.json\n// 2) Pack canonical CBOR bundle\n// 3) Emit OUT_DIR constants included by src/i18n.rs\nfn main() {\n let i18n_dir = Path::new(\"assets/i18n\");\n println!(\"cargo:rerun-if-changed={}\", i18n_dir.display());\n\n let locales = i18n_bundle::load_locale_files(i18n_dir)\n .unwrap_or_else(|err| panic!(\"failed to load locale files: {err}\"));\n let bundle = i18n_bundle::pack_locales_to_cbor(&locales)\n .unwrap_or_else(|err| panic!(\"failed to pack locale bundle: {err}\"));\n\n let out_dir = env::var(\"OUT_DIR\").expect(\"OUT_DIR must be set by cargo\");\n let bundle_path = Path::new(&out_dir).join(\"i18n.bundle.cbor\");\n fs::write(&bundle_path, bundle).expect(\"write i18n.bundle.cbor\");\n\n let rs_path = Path::new(&out_dir).join(\"i18n_bundle.rs\");\n fs::write(\n &rs_path,\n \"pub const I18N_BUNDLE_CBOR: &[u8] = include_bytes!(concat!(env!(\\\"OUT_DIR\\\"), \\\"/i18n.bundle.cbor\\\"));\\n\",\n )\n .expect(\"write i18n_bundle.rs\");\n}\n",
"component.manifest.json": "{\n \"$schema\": \"https://greenticai.github.io/greentic-component/schemas/v1/component.manifest.schema.json\",\n \"artifacts\": {\n \"component_wasm\": \"target/wasm32-wasip2/release/wizard_demo.wasm\"\n },\n \"capabilities\": {\n \"host\": {\n \"messaging\": {\n \"inbound\": true,\n \"outbound\": true\n },\n \"secrets\": {\n \"required\": []\n },\n \"telemetry\": {\n \"scope\": \"node\"\n }\n },\n \"wasi\": {\n \"clocks\": true,\n \"filesystem\": {\n \"mode\": \"none\",\n \"mounts\": []\n },\n \"random\": true\n }\n },\n \"config_schema\": {\n \"additionalProperties\": false,\n \"properties\": {},\n \"required\": [],\n \"type\": \"object\"\n },\n \"default_operation\": \"dwbase_echo\",\n \"describe_export\": \"describe\",\n \"dev_flows\": {\n \"default\": {\n \"format\": \"flow-ir-json\",\n \"graph\": {\n \"edges\": [\n {\n \"from\": \"start\",\n \"to\": \"end\"\n }\n ],\n \"nodes\": [\n {\n \"id\": \"start\",\n \"type\": \"start\"\n },\n {\n \"id\": \"end\",\n \"type\": \"end\"\n }\n ]\n }\n }\n },\n \"hashes\": {\n \"component_wasm\": \"blake3:0000000000000000000000000000000000000000000000000000000000000000\"\n },\n \"id\": \"com.example.wizard-demo\",\n \"limits\": {\n \"memory_mb\": 128,\n \"wall_time_ms\": 1000\n },\n \"name\": \"wizard-demo\",\n \"operations\": [\n {\n \"input_schema\": {\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"properties\": {\n \"input\": {\n \"default\": \"Hello from wizard-demo!\",\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"input\"\n ],\n \"title\": \"wizard-demo dwbase_echo input\",\n \"type\": \"object\"\n },\n \"name\": \"dwbase_echo\",\n \"output_schema\": {\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"message\"\n ],\n \"title\": \"wizard-demo dwbase_echo output\",\n \"type\": \"object\"\n }\n },\n {\n \"input_schema\": {\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"properties\": {\n \"mode\": {\n \"enum\": [\n \"default\",\n \"setup\",\n \"install\",\n \"update\",\n \"upgrade\",\n \"remove\"\n ],\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"mode\"\n ],\n \"title\": \"wizard-demo qa-spec input\",\n \"type\": \"object\"\n },\n \"name\": \"qa-spec\",\n \"output_schema\": {\n \"additionalProperties\": true,\n \"properties\": {\n \"description_i18n_key\": {\n \"type\": \"string\"\n },\n \"fields\": {\n \"items\": {\n \"type\": \"object\"\n },\n \"type\": \"array\"\n },\n \"mode\": {\n \"enum\": [\n \"setup\",\n \"update\",\n \"remove\"\n ],\n \"type\": \"string\"\n },\n \"title_i18n_key\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"mode\",\n \"fields\"\n ],\n \"type\": \"object\"\n }\n },\n {\n \"input_schema\": {\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": true,\n \"properties\": {\n \"answers\": {\n \"type\": \"object\"\n },\n \"current_config\": {\n \"type\": \"object\"\n },\n \"mode\": {\n \"type\": \"string\"\n }\n },\n \"title\": \"wizard-demo apply-answers input\",\n \"type\": \"object\"\n },\n \"name\": \"apply-answers\",\n \"output_schema\": {\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": true,\n \"properties\": {\n \"config\": {\n \"type\": \"object\"\n },\n \"errors\": {\n \"items\": {\n \"type\": \"string\"\n },\n \"type\": \"array\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"warnings\": {\n \"items\": {\n \"type\": \"string\"\n },\n \"type\": \"array\"\n }\n },\n \"required\": [\n \"ok\",\n \"warnings\",\n \"errors\"\n ],\n \"title\": \"wizard-demo apply-answers output\",\n \"type\": \"object\"\n }\n },\n {\n \"input_schema\": {\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"title\": \"wizard-demo i18n-keys input\",\n \"type\": \"object\"\n },\n \"name\": \"i18n-keys\",\n \"output_schema\": {\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"items\": {\n \"type\": \"string\"\n },\n \"title\": \"wizard-demo i18n-keys output\",\n \"type\": \"array\"\n }\n }\n ],\n \"profiles\": {\n \"default\": \"stateless\",\n \"supported\": [\n \"stateless\"\n ]\n },\n \"secret_requirements\": [],\n \"supports\": [\n \"messaging\"\n ],\n \"version\": \"0.1.0\",\n \"world\": \"greentic:component/[email protected]\"\n}",
"rust-toolchain.toml": "[toolchain]\nchannel = \"1.91.0\"\ncomponents = [\"clippy\", \"rustfmt\"]\ntargets = [\"wasm32-wasip2\", \"x86_64-unknown-linux-gnu\"]\nprofile = \"minimal\"\n",
"schemas/component.schema.json": "{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"properties\": {},\n \"required\": [],\n \"title\": \"wizard-demo component configuration\",\n \"type\": \"object\"\n}",
"src/i18n.rs": "use std::collections::BTreeMap;\nuse std::sync::OnceLock;\n\nuse crate::i18n_bundle::{unpack_locales_from_cbor, LocaleBundle};\n\n// Generated by build.rs: static embedded CBOR translation bundle.\ninclude!(concat!(env!(\"OUT_DIR\"), \"/i18n_bundle.rs\"));\n\n// Decode once for process lifetime.\nstatic I18N_BUNDLE: OnceLock<LocaleBundle> = OnceLock::new();\n\nfn bundle() -> &'static LocaleBundle {\n I18N_BUNDLE.get_or_init(|| unpack_locales_from_cbor(I18N_BUNDLE_CBOR).unwrap_or_default())\n}\n\n// Fallback precedence is deterministic:\n// exact locale -> base language -> en\nfn locale_chain(locale: &str) -> Vec<String> {\n let normalized = locale.replace('_', \"-\");\n let mut chain = vec![normalized.clone()];\n if let Some((base, _)) = normalized.split_once('-') {\n chain.push(base.to_string());\n }\n chain.push(\"en\".to_string());\n chain\n}\n\n// Translation lookup function used throughout generated QA/setup code.\n// Extend by adding pluralization/context handling if your component needs it.\npub fn t(locale: &str, key: &str) -> String {\n for candidate in locale_chain(locale) {\n if let Some(map) = bundle().get(&candidate)\n && let Some(value) = map.get(key)\n {\n return value.clone();\n }\n }\n key.to_string()\n}\n\n// Returns canonical source key list (from `en`).\npub fn all_keys() -> Vec<String> {\n let Some(en) = bundle().get(\"en\") else {\n return Vec::new();\n };\n en.keys().cloned().collect()\n}\n\n// Returns English dictionary for diagnostics/tests/tools.\npub fn en_messages() -> BTreeMap<String, String> {\n bundle().get(\"en\").cloned().unwrap_or_default()\n}\n",
"src/i18n_bundle.rs": "use std::collections::BTreeMap;\nuse std::fs;\nuse std::path::Path;\n\nuse greentic_types::cbor::canonical;\n\n// Locale -> (key -> translated message)\npub type LocaleBundle = BTreeMap<String, BTreeMap<String, String>>;\n\n// Reads `assets/i18n/*.json` locale maps and returns stable BTreeMap ordering.\n// Extend here if you need stricter file validation rules.\npub fn load_locale_files(dir: &Path) -> Result<LocaleBundle, String> {\n let mut locales = LocaleBundle::new();\n if !dir.exists() {\n return Ok(locales);\n }\n for entry in fs::read_dir(dir).map_err(|err| err.to_string())? {\n let entry = entry.map_err(|err| err.to_string())?;\n let path = entry.path();\n if path.extension().and_then(|ext| ext.to_str()) != Some(\"json\") {\n continue;\n }\n let Some(stem) = path.file_stem().and_then(|stem| stem.to_str()) else {\n continue;\n };\n // locales.json is metadata, not a translation dictionary.\n if stem == \"locales\" {\n continue;\n }\n let raw = fs::read_to_string(&path).map_err(|err| err.to_string())?;\n let map: BTreeMap<String, String> = serde_json::from_str(&raw).map_err(|err| err.to_string())?;\n locales.insert(stem.to_string(), map);\n }\n Ok(locales)\n}\n\npub fn pack_locales_to_cbor(locales: &LocaleBundle) -> Result<Vec<u8>, String> {\n canonical::to_canonical_cbor_allow_floats(locales).map_err(|err| err.to_string())\n}\n\n#[allow(dead_code)]\n// Runtime decode helper used by src/i18n.rs.\npub fn unpack_locales_from_cbor(bytes: &[u8]) -> Result<LocaleBundle, String> {\n canonical::from_cbor(bytes).map_err(|err| err.to_string())\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn pack_roundtrip_contains_en() {\n let mut locales = LocaleBundle::new();\n let mut en = BTreeMap::new();\n en.insert(\"qa.install.title\".to_string(), \"Install\".to_string());\n locales.insert(\"en\".to_string(), en);\n\n let cbor = pack_locales_to_cbor(&locales).expect(\"pack locales\");\n let decoded = unpack_locales_from_cbor(&cbor).expect(\"decode locales\");\n assert!(decoded.contains_key(\"en\"));\n }\n}\n",
"src/lib.rs": "#[cfg(target_arch = \"wasm32\")]\nuse std::collections::BTreeMap;\n\n#[cfg(target_arch = \"wasm32\")]\nuse greentic_interfaces_guest::component_v0_6::node;\n#[cfg(target_arch = \"wasm32\")]\nuse greentic_types::cbor::canonical;\n#[cfg(target_arch = \"wasm32\")]\nuse greentic_types::schemas::common::schema_ir::{AdditionalProperties, SchemaIr};\n#[cfg(target_arch = \"wasm32\")]\nuse greentic_types::schemas::component::v0_6_0::{ComponentInfo, I18nText};\n\n// i18n: runtime lookup + embedded CBOR bundle helpers.\npub mod i18n;\npub mod i18n_bundle;\n// qa: mode normalization, QA spec generation, apply-answers validation.\npub mod qa;\n\nconst COMPONENT_NAME: &str = \"wizard-demo\";\n#[cfg(target_arch = \"wasm32\")]\nconst COMPONENT_ORG: &str = \"com.example\";\n#[cfg(target_arch = \"wasm32\")]\nconst COMPONENT_VERSION: &str = \"0.1.0\";\n\n#[cfg(target_arch = \"wasm32\")]\n#[used]\n#[unsafe(link_section = \".greentic.wasi\")]\nstatic WASI_TARGET_MARKER: [u8; 13] = *b\"wasm32-wasip2\";\n\n#[cfg(target_arch = \"wasm32\")]\nstruct Component;\n\n#[cfg(target_arch = \"wasm32\")]\nimpl node::Guest for Component {\n // Component metadata advertised to host/operator tooling.\n // Extend here when you add more operations or capability declarations.\n fn describe() -> node::ComponentDescriptor {\n let input_schema_cbor = input_schema_cbor();\n let output_schema_cbor = output_schema_cbor();\n let mut ops = vec![\n node::Op {\n name: \"dwbase_echo\".to_string(),\n summary: Some(\"Handle a single message input\".to_string()),\n input: node::IoSchema {\n schema: node::SchemaSource::InlineCbor(input_schema_cbor.clone()),\n content_type: \"application/cbor\".to_string(),\n schema_version: None,\n },\n output: node::IoSchema {\n schema: node::SchemaSource::InlineCbor(output_schema_cbor.clone()),\n content_type: \"application/cbor\".to_string(),\n schema_version: None,\n },\n examples: Vec::new(),\n }\n ];\n ops.extend(vec![\n node::Op {\n name: \"qa-spec\".to_string(),\n summary: Some(\"Return QA spec for requested mode\".to_string()),\n input: node::IoSchema {\n schema: node::SchemaSource::InlineCbor(input_schema_cbor.clone()),\n content_type: \"application/cbor\".to_string(),\n schema_version: None,\n },\n output: node::IoSchema {\n schema: node::SchemaSource::InlineCbor(output_schema_cbor.clone()),\n content_type: \"application/cbor\".to_string(),\n schema_version: None,\n },\n examples: Vec::new(),\n },\n node::Op {\n name: \"apply-answers\".to_string(),\n summary: Some(\"Apply QA answers and optionally return config override\".to_string()),\n input: node::IoSchema {\n schema: node::SchemaSource::InlineCbor(input_schema_cbor.clone()),\n content_type: \"application/cbor\".to_string(),\n schema_version: None,\n },\n output: node::IoSchema {\n schema: node::SchemaSource::InlineCbor(output_schema_cbor.clone()),\n content_type: \"application/cbor\".to_string(),\n schema_version: None,\n },\n examples: Vec::new(),\n },\n node::Op {\n name: \"i18n-keys\".to_string(),\n summary: Some(\"Return i18n keys referenced by QA/setup\".to_string()),\n input: node::IoSchema {\n schema: node::SchemaSource::InlineCbor(input_schema_cbor.clone()),\n content_type: \"application/cbor\".to_string(),\n schema_version: None,\n },\n output: node::IoSchema {\n schema: node::SchemaSource::InlineCbor(output_schema_cbor),\n content_type: \"application/cbor\".to_string(),\n schema_version: None,\n },\n examples: Vec::new(),\n },\n ]);\n node::ComponentDescriptor {\n name: COMPONENT_NAME.to_string(),\n version: COMPONENT_VERSION.to_string(),\n summary: Some(format!(\"Greentic component {COMPONENT_NAME}\")),\n capabilities: Vec::new(),\n ops,\n schemas: Vec::new(),\n setup: None,\n }\n }\n\n // Single ABI entrypoint. Keep this dispatcher model intact.\n // Extend behavior by adding/adjusting operation branches in `run_component_cbor`.\n fn invoke(\n operation: String,\n envelope: node::InvocationEnvelope,\n ) -> Result<node::InvocationResult, node::NodeError> {\n let output = run_component_cbor(&operation, envelope.payload_cbor);\n Ok(node::InvocationResult {\n ok: true,\n output_cbor: output,\n output_metadata_cbor: None,\n })\n }\n}\n\n#[cfg(target_arch = \"wasm32\")]\ngreentic_interfaces_guest::export_component_v060!(Component);\n\n// Default user-operation implementation.\n// Replace this with domain logic for your component.\npub fn handle_message(operation: &str, input: &str) -> String {\n format!(\"{COMPONENT_NAME}::{operation} => {}\", input.trim())\n}\n\n#[cfg(target_arch = \"wasm32\")]\nfn encode_cbor<T: serde::Serialize>(value: &T) -> Vec<u8> {\n canonical::to_canonical_cbor_allow_floats(value).expect(\"encode cbor\")\n}\n\n#[cfg(target_arch = \"wasm32\")]\n// Accept canonical CBOR first, then fall back to JSON for local debugging.\nfn parse_payload(input: &[u8]) -> serde_json::Value {\n if let Ok(value) = canonical::from_cbor(input) {\n return value;\n }\n serde_json::from_slice(input).unwrap_or_else(|_| serde_json::json!({}))\n}\n\n#[cfg(target_arch = \"wasm32\")]\n// Keep ingress compatibility: default/setup/install -> setup, update/upgrade -> update.\nfn normalized_mode(payload: &serde_json::Value) -> qa::NormalizedMode {\n let mode = payload\n .get(\"mode\")\n .and_then(|v| v.as_str())\n .or_else(|| payload.get(\"operation\").and_then(|v| v.as_str()))\n .unwrap_or(\"setup\");\n qa::normalize_mode(mode).unwrap_or(qa::NormalizedMode::Setup)\n}\n\n#[cfg(target_arch = \"wasm32\")]\n// Minimal schema for generic operation input.\n// Extend these schemas when you harden operation contracts.\nfn input_schema() -> SchemaIr {\n SchemaIr::Object {\n properties: BTreeMap::from([(\n \"input\".to_string(),\n SchemaIr::String {\n min_len: Some(0),\n max_len: None,\n regex: None,\n format: None,\n },\n )]),\n required: vec![\"input\".to_string()],\n additional: AdditionalProperties::Allow,\n }\n}\n\n#[cfg(target_arch = \"wasm32\")]\nfn output_schema() -> SchemaIr {\n SchemaIr::Object {\n properties: BTreeMap::from([(\n \"message\".to_string(),\n SchemaIr::String {\n min_len: Some(0),\n max_len: None,\n regex: None,\n format: None,\n },\n )]),\n required: vec![\"message\".to_string()],\n additional: AdditionalProperties::Allow,\n }\n}\n\n#[cfg(target_arch = \"wasm32\")]\n#[allow(dead_code)]\nfn config_schema() -> SchemaIr {\n SchemaIr::Object {\n properties: BTreeMap::new(),\n required: Vec::new(),\n additional: AdditionalProperties::Forbid,\n }\n}\n\n#[cfg(target_arch = \"wasm32\")]\n#[allow(dead_code)]\nfn component_info() -> ComponentInfo {\n ComponentInfo {\n id: format!(\"{COMPONENT_ORG}.{COMPONENT_NAME}\"),\n version: COMPONENT_VERSION.to_string(),\n role: \"tool\".to_string(),\n display_name: Some(I18nText::new(\"component.display_name\", Some(COMPONENT_NAME.to_string()))),\n }\n}\n\n#[cfg(target_arch = \"wasm32\")]\nfn input_schema_cbor() -> Vec<u8> {\n encode_cbor(&input_schema())\n}\n\n#[cfg(target_arch = \"wasm32\")]\nfn output_schema_cbor() -> Vec<u8> {\n encode_cbor(&output_schema())\n}\n\n#[cfg(target_arch = \"wasm32\")]\n// Central operation dispatcher.\n// This is the primary extension point for new operations.\nfn run_component_cbor(operation: &str, input: Vec<u8>) -> Vec<u8> {\n let value = parse_payload(&input);\n let output = match operation {\n \"qa-spec\" => {\n let mode = normalized_mode(&value);\n qa::qa_spec_json(mode)\n }\n \"apply-answers\" => {\n let mode = normalized_mode(&value);\n qa::apply_answers(mode, &value)\n }\n \"i18n-keys\" => serde_json::Value::Array(\n qa::i18n_keys()\n .into_iter()\n .map(serde_json::Value::String)\n .collect(),\n ),\n _ => {\n let op_name = value\n .get(\"operation\")\n .and_then(|v| v.as_str())\n .unwrap_or(operation);\n let input_text = value\n .get(\"input\")\n .and_then(|v| v.as_str())\n .map(ToOwned::to_owned)\n .unwrap_or_else(|| value.to_string());\n serde_json::json!({\n \"message\": handle_message(op_name, &input_text)\n })\n }\n };\n encode_cbor(&output)\n}\n",
"src/qa.rs": "use greentic_types::i18n_text::I18nText;\nuse greentic_types::schemas::component::v0_6_0::{QaMode, Question, QuestionKind};\nuse serde_json::{json, Value as JsonValue};\n\n// Internal normalized lifecycle semantics used by scaffolded QA operations.\n// Input compatibility accepts legacy/provision aliases via `normalize_mode`.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum NormalizedMode {\n Setup,\n Update,\n Remove,\n}\n\nimpl NormalizedMode {\n pub fn as_str(self) -> &'static str {\n match self {\n Self::Setup => \"setup\",\n Self::Update => \"update\",\n Self::Remove => \"remove\",\n }\n }\n}\n\n// Compatibility mapping for mode strings from operator/flow payloads.\npub fn normalize_mode(raw: &str) -> Option<NormalizedMode> {\n match raw {\n \"default\" | \"setup\" | \"install\" => Some(NormalizedMode::Setup),\n \"update\" | \"upgrade\" => Some(NormalizedMode::Update),\n \"remove\" => Some(NormalizedMode::Remove),\n _ => None,\n }\n}\n\n// Primary QA authoring entrypoint.\n// Extend question sets here for your real setup/update/remove requirements.\npub fn qa_spec_json(mode: NormalizedMode) -> JsonValue {\n let (title_key, description_key, questions) = match mode {\n NormalizedMode::Setup => (\n \"qa.install.title\",\n Some(\"qa.install.description\"),\n vec![\n question(\"api_key\", \"qa.field.api_key.label\", \"qa.field.api_key.help\", true),\n question(\"region\", \"qa.field.region.label\", \"qa.field.region.help\", true),\n question(\n \"webhook_base_url\",\n \"qa.field.webhook_base_url.label\",\n \"qa.field.webhook_base_url.help\",\n true,\n ),\n question(\"enabled\", \"qa.field.enabled.label\", \"qa.field.enabled.help\", false),\n ],\n ),\n NormalizedMode::Update => (\n \"qa.update.title\",\n Some(\"qa.update.description\"),\n vec![\n question(\"api_key\", \"qa.field.api_key.label\", \"qa.field.api_key.help\", false),\n question(\"region\", \"qa.field.region.label\", \"qa.field.region.help\", false),\n question(\n \"webhook_base_url\",\n \"qa.field.webhook_base_url.label\",\n \"qa.field.webhook_base_url.help\",\n false,\n ),\n question(\"enabled\", \"qa.field.enabled.label\", \"qa.field.enabled.help\", false),\n ],\n ),\n NormalizedMode::Remove => (\n \"qa.remove.title\",\n Some(\"qa.remove.description\"),\n vec![question(\n \"confirm_remove\",\n \"qa.field.confirm_remove.label\",\n \"qa.field.confirm_remove.help\",\n true,\n )],\n ),\n };\n\n json!({\n \"mode\": match mode {\n NormalizedMode::Setup => QaMode::Setup,\n NormalizedMode::Update => QaMode::Update,\n NormalizedMode::Remove => QaMode::Remove,\n },\n \"title\": I18nText::new(title_key, None),\n \"description\": description_key.map(|key| I18nText::new(key, None)),\n \"questions\": questions,\n \"defaults\": {}\n })\n}\n\nfn question(id: &str, label_key: &str, help_key: &str, required: bool) -> Question {\n Question {\n id: id.to_string(),\n label: I18nText::new(label_key, None),\n help: Some(I18nText::new(help_key, None)),\n error: None,\n kind: QuestionKind::Text,\n required,\n default: None,\n }\n}\n\n// Used by `i18n-keys` operation and contract checks in operator.\npub fn i18n_keys() -> Vec<String> {\n crate::i18n::all_keys()\n}\n\n// Apply answers and return operator-friendly base shape:\n// { ok, config?, warnings, errors, ...optional metadata }\n// Extend this method for domain validation rules and config patching.\npub fn apply_answers(mode: NormalizedMode, payload: &JsonValue) -> JsonValue {\n let answers = payload.get(\"answers\").cloned().unwrap_or_else(|| json!({}));\n let current_config = payload\n .get(\"current_config\")\n .cloned()\n .unwrap_or_else(|| json!({}));\n\n let mut errors = Vec::new();\n match mode {\n NormalizedMode::Setup => {\n for key in [\"api_key\", \"region\", \"webhook_base_url\"] {\n if answers.get(key).and_then(|v| v.as_str()).is_none() {\n errors.push(json!({\n \"key\": \"qa.error.required\",\n \"msg_key\": \"qa.error.required\",\n \"fields\": [key]\n }));\n }\n }\n }\n NormalizedMode::Remove => {\n if answers\n .get(\"confirm_remove\")\n .and_then(|v| v.as_str())\n .map(|v| v != \"true\")\n .unwrap_or(true)\n {\n errors.push(json!({\n \"key\": \"qa.error.remove_confirmation\",\n \"msg_key\": \"qa.error.remove_confirmation\",\n \"fields\": [\"confirm_remove\"]\n }));\n }\n }\n NormalizedMode::Update => {}\n }\n\n if !errors.is_empty() {\n return json!({\n \"ok\": false,\n \"warnings\": [],\n \"errors\": errors,\n \"meta\": {\n \"mode\": mode.as_str(),\n \"version\": \"v1\"\n }\n });\n }\n\n let mut config = match current_config {\n JsonValue::Object(map) => map,\n _ => serde_json::Map::new(),\n };\n if let JsonValue::Object(map) = answers {\n for (key, value) in map {\n config.insert(key, value);\n }\n }\n if mode == NormalizedMode::Remove {\n config.insert(\"enabled\".to_string(), JsonValue::Bool(false));\n }\n\n json!({\n \"ok\": true,\n \"config\": config,\n \"warnings\": [],\n \"errors\": [],\n \"meta\": {\n \"mode\": mode.as_str(),\n \"version\": \"v1\"\n },\n \"audit\": {\n \"reasons\": [\"qa.apply_answers\"],\n \"timings_ms\": {}\n }\n })\n}\n",
"tools/i18n.sh": "#!/usr/bin/env bash\nset -euo pipefail\n\nROOT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/..\" && pwd)\"\nLOCALES_FILE=\"$ROOT_DIR/assets/i18n/locales.json\"\nSOURCE_FILE=\"$ROOT_DIR/assets/i18n/en.json\"\n\nlog() {\n printf '[i18n] %s\\n' \"$*\"\n}\n\nfail() {\n printf '[i18n] error: %s\\n' \"$*\" >&2\n exit 1\n}\n\nensure_codex() {\n if command -v codex >/dev/null 2>&1; then\n return\n fi\n if command -v npm >/dev/null 2>&1; then\n log \"installing Codex CLI via npm\"\n npm i -g @openai/codex || fail \"failed to install Codex CLI via npm\"\n elif command -v brew >/dev/null 2>&1; then\n log \"installing Codex CLI via brew\"\n brew install codex || fail \"failed to install Codex CLI via brew\"\n else\n fail \"Codex CLI not found and no supported installer available (npm or brew)\"\n fi\n}\n\nensure_codex_login() {\n if codex login status >/dev/null 2>&1; then\n return\n fi\n log \"Codex login status unavailable or not logged in; starting login flow\"\n codex login || fail \"Codex login failed\"\n}\n\nprobe_translator() {\n command -v greentic-i18n-translator >/dev/null 2>&1 || fail \"greentic-i18n-translator not found. Install it and rerun this script.\"\n local help_output\n help_output=\"$(greentic-i18n-translator --help 2>&1 || true)\"\n [[ -n \"$help_output\" ]] || fail \"unable to inspect greentic-i18n-translator --help\"\n if ! greentic-i18n-translator translate --help >/dev/null 2>&1; then\n fail \"translator subcommand 'translate' is required but unavailable\"\n fi\n}\n\nrun_translate() {\n while IFS= read -r locale; do\n [[ -n \"$locale\" ]] || continue\n log \"translating locale: $locale\"\n greentic-i18n-translator translate \\\n --langs \"$locale\" \\\n --en \"$SOURCE_FILE\" || fail \"translate failed for locale $locale\"\n done < <(python3 - \"$LOCALES_FILE\" <<'PY'\nimport json\nimport sys\nwith open(sys.argv[1], 'r', encoding='utf-8') as f:\n data = json.load(f)\nfor locale in data:\n if locale != \"en\":\n print(locale)\nPY\n)\n}\n\nrun_validate_per_locale() {\n local failed=0\n while IFS= read -r locale; do\n [[ -n \"$locale\" ]] || continue\n if ! greentic-i18n-translator validate --langs \"$locale\" --en \"$SOURCE_FILE\"; then\n log \"validate failed for locale: $locale\"\n failed=1\n fi\n done < <(python3 - \"$LOCALES_FILE\" <<'PY'\nimport json\nimport sys\nwith open(sys.argv[1], 'r', encoding='utf-8') as f:\n data = json.load(f)\nfor locale in data:\n if locale != \"en\":\n print(locale)\nPY\n)\n return \"$failed\"\n}\n\nrun_status_per_locale() {\n local failed=0\n while IFS= read -r locale; do\n [[ -n \"$locale\" ]] || continue\n if ! greentic-i18n-translator status --langs \"$locale\" --en \"$SOURCE_FILE\"; then\n log \"status failed for locale: $locale\"\n failed=1\n fi\n done < <(python3 - \"$LOCALES_FILE\" <<'PY'\nimport json\nimport sys\nwith open(sys.argv[1], 'r', encoding='utf-8') as f:\n data = json.load(f)\nfor locale in data:\n if locale != \"en\":\n print(locale)\nPY\n)\n return \"$failed\"\n}\n\nrun_optional_checks() {\n if greentic-i18n-translator validate --help >/dev/null 2>&1; then\n log \"running translator validate\"\n if ! run_validate_per_locale; then\n fail \"translator validate failed\"\n fi\n else\n log \"warning: translator validate command not available; skipping\"\n fi\n if greentic-i18n-translator status --help >/dev/null 2>&1; then\n log \"running translator status\"\n run_status_per_locale || fail \"translator status failed\"\n else\n log \"warning: translator status command not available; skipping\"\n fi\n}\n\n[[ -f \"$LOCALES_FILE\" ]] || fail \"missing locales file: $LOCALES_FILE\"\n[[ -f \"$SOURCE_FILE\" ]] || fail \"missing source locale file: $SOURCE_FILE\"\n\nensure_codex\nensure_codex_login\nprobe_translator\nrun_translate\nrun_optional_checks\nlog \"translations updated. Run cargo build to embed translations into WASM\"\n"
}
}
]
}
}