//! Per-category catalog key schemas (ADR-0019 §8.3). //! //! Every catalog key the friendly-error layer references at //! runtime is enumerated here together with its expected //! placeholder set. The validator //! (`tests::keys_validate_against_catalog`) walks this list and //! asserts: //! //! - the key exists in the catalog; //! - every placeholder declared here appears at least once in //! the template; //! - no placeholder appears in the template that isn't declared //! here (catches typos in either direction); //! - every catalog key (outside the `_test.*` sanity group) is //! declared here (catches dead YAML entries). //! //! Adding a new translation site is a two-step change: add the //! key + placeholders here, add the YAML entry. Either alone //! fails the validator. //! //! ## Convention //! //! Each error entry in the catalog has: //! //! - a `.headline` template — used in both short and verbose //! modes; //! - optionally a `.hint` template — surfaced only in verbose //! mode. //! //! Single-line errors (object-not-found, already-exists, //! invalid-value) have no hint; the headline carries the whole //! message. //! //! Other categories (`help.*`, `ok.*`, `client_side.*`, //! `replay.*`, `parse.*`, modal labels, …) get added to this //! list as the migration sweep (ADR-0019 §9) lands them. /// `(key, expected_placeholders)`. Sorted by key for grep-ability. pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[ // ---- Pre-submit diagnostics (ADR-0027) ---- ("diagnostic.alias_used_as_column", &["name"]), ("diagnostic.ambiguous_column", &["column", "qualifiers"]), ("diagnostic.auto_column_overridden", &["column", "type"]), ("diagnostic.compound_arity_mismatch", &["op", "left_n", "right_n"]), ("diagnostic.cte_arity_mismatch", &["cte", "declared", "actual"]), ("diagnostic.duplicate_cte", &["name"]), ("diagnostic.eq_null", &[]), ("diagnostic.insert_arity_mismatch", &["expected", "actual"]), // ADR-0033 §8.1 / Amendment 5: Form B (no column list) variant. // Cited from issue #1 — every column must be supplied positionally. ( "diagnostic.insert_arity_mismatch_form_b", &["expected", "actual"], ), // ADR-0036 Amendment 1 / issue #17: simple-mode Form B variants. ( "diagnostic.insert_arity_mismatch_form_b_simple", &["expected", "columns", "skipped", "actual"], ), ( "diagnostic.insert_arity_mismatch_all_auto", &["table", "actual"], ), ("diagnostic.not_null_missing", &["column"]), ("diagnostic.like_numeric", &["column", "type"]), ("diagnostic.projection_alias_misplaced", &["alias", "clause"]), ("diagnostic.table_used_as_column", &["name"]), ("diagnostic.type_mismatch", &["column", "type"]), ("diagnostic.unknown_column", &["name", "table"]), ("diagnostic.unknown_qualifier", &["qualifier"]), ("diagnostic.unknown_table", &["name"]), // ---- Friendly-error translations of engine messages // ---- (ADR-0019; ADR-0032 §11.5). ("engine.aggregate_misuse", &["name"]), ("engine.ambiguous_column", &["column"]), ("engine.compound_arity_mismatch", &["op"]), ("engine.group_by_required", &[]), ("engine.no_such_column", &["name"]), ("engine.no_such_table", &["name"]), ("engine.recursive_cte_malformed", &[]), ("engine.scalar_subquery_too_many_rows", &[]), // ---- Already-exists collisions (anchor: "already exists") ---- ("error.already_exists.column.headline", &["table", "column"]), ("error.already_exists.relationship.headline", &["name"]), ("error.already_exists.table.headline", &["name"]), // ---- CHECK violations ---- ("error.check.insert.headline", &["table", "column"]), ("error.check.insert.hint", &["column"]), ( "error.check.insert.hint_with_rule", &["value", "rule", "column"], ), ("error.check.update.headline", &["table", "column"]), ("error.check.update.hint", &["column"]), ( "error.check.update.hint_with_rule", &["value", "rule", "column"], ), // ---- FK violations (anchor: "referenced by") ---- ( "error.foreign_key.child_side.insert.headline", &["parent_table", "parent_column", "value"], ), ( "error.foreign_key.child_side.insert.hint", &["parent_table", "parent_column"], ), ( "error.foreign_key.child_side.update.headline", &["parent_table", "parent_column", "value"], ), ( "error.foreign_key.child_side.update.hint", &["parent_table", "parent_column"], ), ( "error.foreign_key.parent_side.delete.headline", &["table", "child_table"], ), ("error.foreign_key.parent_side.delete.hint", &[]), ( "error.foreign_key.parent_side.update.headline", &["table", "child_table"], ), ("error.foreign_key.parent_side.update.hint", &[]), // ---- Generic engine refusal ---- ("error.generic.headline", &["operation"]), ("error.generic.hint", &["table"]), ("error.generic.hint_no_table", &[]), // ---- Invalid-value errors (pre-engine, single-line) ---- ( "error.invalid_value.arity.headline", &["expected", "actual"], ), ("error.invalid_value.empty_insert.headline", &[]), ("error.invalid_value.empty_update.headline", &[]), // ---- Not-found errors (anchor: "no such ...") ---- ("error.not_found.column.headline", &["table", "column"]), ("error.not_found.column_unqualified.headline", &["column"]), ("error.not_found.relationship.headline", &["name"]), ("error.not_found.table.headline", &["name"]), // ---- NOT NULL violations ---- ("error.not_null.insert.headline", &["table", "column"]), ("error.not_null.insert.hint", &["column"]), ("error.not_null.update.headline", &["table", "column"]), ("error.not_null.update.hint", &["column"]), // ---- Type mismatch ---- ( "error.type_mismatch.change_column.headline", &["table", "column", "src_type", "target_type"], ), ( "error.type_mismatch.change_column.hint", &["target_type"], ), ( "error.type_mismatch.insert.headline", &["value", "expected_type"], ), ( "error.type_mismatch.insert.hint", &["table", "column", "expected_type"], ), ( "error.type_mismatch.update.headline", &["value", "expected_type"], ), ( "error.type_mismatch.update.hint", &["table", "column", "expected_type"], ), // ---- Help text ---- ("help.cli_banner", &[]), // In-app `help` — framing + per-command entries keyed by // each CommandNode's `help_id` (ADR-0024 §help_id). ("help.intro", &[]), ("help.dsl_section", &[]), ("help.types_reference", &[]), ("help.detail_hint", &[]), ("help.unknown_topic", &["topic"]), ("help.app.quit", &[]), ("help.app.help", &[]), ("help.app.hint", &[]), ("help.app.rebuild", &[]), ("help.app.save", &[]), ("help.app.new", &[]), ("help.app.load", &[]), ("help.app.export", &[]), ("help.app.import", &[]), ("help.app.mode", &[]), ("help.app.messages", &[]), ("help.app.undo", &[]), ("help.app.redo", &[]), ("help.app.copy", &[]), ("help.ddl.create", &[]), ("help.ddl.create_m2n", &[]), ("help.ddl.sql_create_table", &[]), ("help.ddl.sql_drop_table", &[]), ("help.ddl.sql_create_index", &[]), ("help.ddl.sql_drop_index", &[]), ("help.ddl.sql_alter_table", &[]), // Advanced-mode SQL CREATE TABLE / DROP TABLE no-op notes (ADR-0035 §4). ("ddl.create_skipped_exists", &["name"]), ("ddl.drop_skipped_absent", &["name"]), // Advanced-mode SQL CREATE INDEX / DROP INDEX no-op notes (ADR-0035 §4d). ("ddl.create_index_skipped_exists", &["name"]), ("ddl.drop_index_skipped_absent", &["name"]), ("help.ddl.drop", &[]), ("help.ddl.add", &[]), ("help.ddl.rename", &[]), ("help.ddl.change", &[]), ("help.data.show", &[]), ("help.data.seed", &[]), ("help.data.insert", &[]), ("help.data.update", &[]), ("help.data.delete", &[]), ("help.data.replay", &[]), ("help.data.explain", &[]), // ---- Hint panel ambient typing assistance (ADR-0022 §6) ---- ("hint.ambient_complete", &[]), ( "hint.ambient_error_with_usage", &["message", "usage"], ), ("hint.ambient_expected", &["expected"]), ("hint.getting_started", &[]), ("hint.block.heading", &[]), ("hint.block.what", &[]), ("hint.block.example", &[]), ("hint.block.concept", &[]), // Tier-3 teaching blocks (ADR-0053 D3) — Phase-B exemplars. ("hint.cmd.insert.what", &[]), ("hint.cmd.insert.example", &[]), ("hint.cmd.insert.concept", &[]), ("hint.cmd.add_relationship.what", &[]), ("hint.cmd.add_relationship.example", &[]), ("hint.cmd.add_relationship.concept", &[]), ("hint.err.foreign_key.child_side.what", &[]), ("hint.err.foreign_key.child_side.example", &[]), ("hint.err.foreign_key.child_side.concept", &[]), // Phase C batch 5 — runtime error-class hints. ("hint.err.foreign_key.parent_side.what", &[]), ("hint.err.foreign_key.parent_side.example", &[]), ("hint.err.foreign_key.parent_side.concept", &[]), ("hint.err.unique.what", &[]), ("hint.err.unique.example", &[]), ("hint.err.unique.concept", &[]), ("hint.err.not_null.what", &[]), ("hint.err.not_null.example", &[]), ("hint.err.not_null.concept", &[]), ("hint.err.check.what", &[]), ("hint.err.check.example", &[]), ("hint.err.check.concept", &[]), ("hint.err.type_mismatch.what", &[]), ("hint.err.type_mismatch.example", &[]), ("hint.err.type_mismatch.concept", &[]), ("hint.err.not_found.what", &[]), ("hint.err.not_found.example", &[]), ("hint.err.not_found.concept", &[]), ("hint.err.already_exists.what", &[]), ("hint.err.already_exists.example", &[]), ("hint.err.already_exists.concept", &[]), ("hint.err.generic.what", &[]), ("hint.err.generic.example", &[]), ("hint.err.invalid_value.what", &[]), ("hint.err.invalid_value.example", &[]), // Phase C batch 1 — app-lifecycle command hints. ("hint.cmd.quit.what", &[]), ("hint.cmd.quit.example", &[]), ("hint.cmd.help.what", &[]), ("hint.cmd.help.example", &[]), ("hint.cmd.help.concept", &[]), ("hint.cmd.hint.what", &[]), ("hint.cmd.hint.example", &[]), ("hint.cmd.rebuild.what", &[]), ("hint.cmd.rebuild.example", &[]), ("hint.cmd.rebuild.concept", &[]), ("hint.cmd.save.what", &[]), ("hint.cmd.save.example", &[]), ("hint.cmd.new.what", &[]), ("hint.cmd.new.example", &[]), ("hint.cmd.load.what", &[]), ("hint.cmd.load.example", &[]), ("hint.cmd.export.what", &[]), ("hint.cmd.export.example", &[]), ("hint.cmd.export.concept", &[]), ("hint.cmd.import.what", &[]), ("hint.cmd.import.example", &[]), ("hint.cmd.mode.what", &[]), ("hint.cmd.mode.example", &[]), ("hint.cmd.mode.concept", &[]), ("hint.cmd.messages.what", &[]), ("hint.cmd.messages.example", &[]), ("hint.cmd.messages.concept", &[]), ("hint.cmd.undo.what", &[]), ("hint.cmd.undo.example", &[]), ("hint.cmd.undo.concept", &[]), ("hint.cmd.redo.what", &[]), ("hint.cmd.redo.example", &[]), ("hint.cmd.copy.what", &[]), ("hint.cmd.copy.example", &[]), // Phase C batch 2 — DDL command hints. ("hint.cmd.create_table.what", &[]), ("hint.cmd.create_table.example", &[]), ("hint.cmd.create_table.concept", &[]), ("hint.cmd.create_m2n.what", &[]), ("hint.cmd.create_m2n.example", &[]), ("hint.cmd.create_m2n.concept", &[]), ("hint.cmd.add_column.what", &[]), ("hint.cmd.add_column.example", &[]), ("hint.cmd.add_column.concept", &[]), ("hint.cmd.add_index.what", &[]), ("hint.cmd.add_index.example", &[]), ("hint.cmd.add_index.concept", &[]), ("hint.cmd.add_constraint.what", &[]), ("hint.cmd.add_constraint.example", &[]), ("hint.cmd.add_constraint.concept", &[]), ("hint.cmd.drop_table.what", &[]), ("hint.cmd.drop_table.example", &[]), ("hint.cmd.drop_table.concept", &[]), ("hint.cmd.drop_column.what", &[]), ("hint.cmd.drop_column.example", &[]), ("hint.cmd.drop_column.concept", &[]), ("hint.cmd.drop_relationship.what", &[]), ("hint.cmd.drop_relationship.example", &[]), ("hint.cmd.drop_relationship.concept", &[]), ("hint.cmd.drop_index.what", &[]), ("hint.cmd.drop_index.example", &[]), ("hint.cmd.drop_index.concept", &[]), ("hint.cmd.drop_constraint.what", &[]), ("hint.cmd.drop_constraint.example", &[]), ("hint.cmd.drop_constraint.concept", &[]), ("hint.cmd.rename_column.what", &[]), ("hint.cmd.rename_column.example", &[]), ("hint.cmd.rename_column.concept", &[]), ("hint.cmd.change_column.what", &[]), ("hint.cmd.change_column.example", &[]), ("hint.cmd.change_column.concept", &[]), // Phase C batch 3 — DML command hints. ("hint.cmd.update.what", &[]), ("hint.cmd.update.example", &[]), ("hint.cmd.update.concept", &[]), ("hint.cmd.delete.what", &[]), ("hint.cmd.delete.example", &[]), ("hint.cmd.delete.concept", &[]), ("hint.cmd.show_data.what", &[]), ("hint.cmd.show_data.example", &[]), ("hint.cmd.show_data.concept", &[]), ("hint.cmd.show_table.what", &[]), ("hint.cmd.show_table.example", &[]), ("hint.cmd.show_table.concept", &[]), ("hint.cmd.show_tables.what", &[]), ("hint.cmd.show_tables.example", &[]), ("hint.cmd.show_relationships.what", &[]), ("hint.cmd.show_relationships.example", &[]), ("hint.cmd.show_relationships.concept", &[]), ("hint.cmd.show_indexes.what", &[]), ("hint.cmd.show_indexes.example", &[]), ("hint.cmd.show_indexes.concept", &[]), ("hint.cmd.seed.what", &[]), ("hint.cmd.seed.example", &[]), ("hint.cmd.seed.concept", &[]), ("hint.cmd.explain.what", &[]), ("hint.cmd.explain.example", &[]), ("hint.cmd.explain.concept", &[]), ("hint.cmd.replay.what", &[]), ("hint.cmd.replay.example", &[]), ("hint.cmd.replay.concept", &[]), // Phase C batch 4 — advanced-mode SQL command hints. ("hint.cmd.sql_create_table.what", &[]), ("hint.cmd.sql_create_table.example", &[]), ("hint.cmd.sql_create_table.concept", &[]), ("hint.cmd.sql_alter_table.what", &[]), ("hint.cmd.sql_alter_table.example", &[]), ("hint.cmd.sql_alter_table.concept", &[]), ("hint.cmd.sql_create_index.what", &[]), ("hint.cmd.sql_create_index.example", &[]), ("hint.cmd.sql_create_index.concept", &[]), ("hint.cmd.sql_drop_index.what", &[]), ("hint.cmd.sql_drop_index.example", &[]), ("hint.cmd.sql_drop_index.concept", &[]), ("hint.cmd.sql_drop_table.what", &[]), ("hint.cmd.sql_drop_table.example", &[]), ("hint.cmd.sql_drop_table.concept", &[]), ("hint.cmd.sql_insert.what", &[]), ("hint.cmd.sql_insert.example", &[]), ("hint.cmd.sql_insert.concept", &[]), ("hint.cmd.sql_update.what", &[]), ("hint.cmd.sql_update.example", &[]), ("hint.cmd.sql_update.concept", &[]), ("hint.cmd.sql_delete.what", &[]), ("hint.cmd.sql_delete.example", &[]), ("hint.cmd.sql_delete.concept", &[]), ("hint.cmd.select.what", &[]), ("hint.cmd.select.example", &[]), ("hint.cmd.select.concept", &[]), ("hint.cmd.with.what", &[]), ("hint.cmd.with.example", &[]), ("hint.cmd.with.concept", &[]), ("hint.cmd.explain_sql.what", &[]), ("hint.cmd.explain_sql.example", &[]), ("hint.cmd.explain_sql.concept", &[]), ( "hint.ambient_invalid_ident", &["kind", "found"], ), ("hint.ambient_typing_name", &[]), // Issue #4: introduce the advanced-mode CREATE TABLE element // slot (`create table T (`) so the otherwise-invisible // column-name role reads as the dominant first move. ("hint.create_table_element", &[]), ("hint.seed_count", &[]), ("hint.value_literal_slot", &[]), ( "hint.ambient_typing_name_then", &["next"], ), // Per-column-type value-slot hints (ADR-0024 §Phase D). ("hint.value_slot_blob", &[]), ("hint.value_slot_bool", &[]), ("hint.value_slot_date", &[]), ("hint.value_slot_datetime", &[]), ("hint.value_slot_decimal", &[]), ("hint.value_slot_int", &[]), ("hint.value_slot_real", &[]), ("hint.value_slot_serial", &[]), ("hint.value_slot_shortid", &[]), ("hint.value_slot_text", &[]), ("hint.value_slot_for_column", &["column", "detail"]), ("hint.value_slot_autogen_skipped", &["columns"]), // ---- Parse error rendering ---- ("parse.available_commands", &["commands"]), ("parse.caret", &["padding"]), // Custom (try_map / source-slice) error messages raised // by the DSL parser. See `parse.custom.*` in the catalog. ("parse.custom.alter_add_primary_key", &[]), ("parse.custom.alter_named_unique", &[]), ("parse.custom.bind_type_mismatch", &["found", "expected"]), ("parse.custom.change_column_flags_exclusive", &[]), ("parse.custom.constraint_redundant_on_pk", &["column", "constraint"]), ("parse.custom.create_table_needs_pk", &[]), ("parse.custom.expression_too_deep", &[]), ("parse.custom.insert_form_a_missing_values", &["columns"]), ("parse.custom.on_action_specified_twice", &["target"]), ("parse.custom.replay_path_expected", &[]), ("parse.custom.unknown_action", &["found", "expected"]), ("parse.custom.unknown_type", &["found", "expected"]), ("parse.empty", &[]), ("parse.error", &["detail"]), ("parse.error_wrapper", &["detail"]), // Per-command usage templates (ADR-0021 §1). One key per // command. Multi-entry families (`add`, `drop`, `show`) // each have multiple keys. Templates are pure prose with // no placeholders — the renderer prepends "usage: " in // code, not the catalog, because spacing is alignment- // sensitive in the multi-entry case. ("parse.usage.add_column", &[]), ("parse.usage.add_constraint", &[]), ("parse.usage.add_index", &[]), ("parse.usage.add_relationship", &[]), ("parse.usage.change_column", &[]), ("parse.usage.create_table", &[]), ("parse.usage.create_m2n", &[]), ("parse.usage.sql_create_table", &[]), ("parse.usage.sql_drop_table", &[]), ("parse.usage.sql_create_index", &[]), ("parse.usage.sql_drop_index", &[]), ("parse.usage.sql_alter_table", &[]), ("parse.usage.delete", &[]), ("parse.usage.drop_column", &[]), ("parse.usage.drop_constraint", &[]), ("parse.usage.drop_index", &[]), ("parse.usage.drop_relationship", &[]), ("parse.usage.drop_table", &[]), ("parse.usage.explain", &[]), ("parse.usage.insert", &[]), ("parse.usage.rename_column", &[]), ("parse.usage.export", &[]), ("parse.usage.help", &[]), ("parse.usage.hint", &[]), ("parse.usage.import", &[]), ("parse.usage.copy", &[]), ("parse.usage.load", &[]), ("parse.usage.messages", &[]), ("parse.usage.mode", &[]), ("parse.usage.new", &[]), ("parse.usage.quit", &[]), ("parse.usage.rebuild", &[]), ("parse.usage.redo", &[]), ("parse.usage.replay", &[]), ("parse.usage.undo", &[]), ("parse.usage.save", &[]), ("parse.usage.select", &[]), ("parse.usage.seed", &[]), ("parse.usage.show_data", &[]), ("parse.usage.show_table", &[]), ("parse.usage.show_tables", &[]), ("parse.usage.show_relationships", &[]), ("parse.usage.show_indexes", &[]), ("parse.usage.show_relationship", &[]), ("parse.usage.show_index", &[]), ("parse.usage.update", &[]), ("parse.usage.with", &[]), ("parse.expect.select_projection", &[]), ("parse.cross_join_no_on", &[]), // ---- Project lifecycle event notes ---- ("project.export_failed", &["error"]), ("project.export_ok", &["path"]), ("project.export_usage", &[]), ("project.import_empty_target", &[]), ("project.import_usage", &[]), ("project.import_zip_missing", &["path"]), ("persistence.csv.empty", &[]), ("persistence.csv.invalid_utf8", &[]), ("persistence.csv.unterminated_quote", &[]), ("persistence.encode", &["kind", "path", "message"]), ("persistence.io", &["operation", "path", "source"]), ("persistence.migrate.bad_output", &["detail"]), ("persistence.migrate.io", &["path", "source"]), ( "persistence.migrate.newer_than_supported", &["file", "latest"], ), ("persistence.migrate.no_migrator", &["version"]), ("persistence.migrate.step_failed", &["from", "to", "source"]), ("persistence.migrate.version_parse", &["detail"]), ("persistence.yaml.syntax", &["detail"]), ("persistence.yaml.unknown_action", &["raw"]), ("persistence.yaml.unknown_type", &["table", "column", "raw"]), ("persistence.yaml.unsupported_version", &["version"]), ("project.already_exists", &["path"]), ("project.data_root_unavailable", &[]), ("project.io", &["path", "source"]), ("project.load_path_missing", &["path"]), ("project.lock.already_held", &["pid", "hostname", "path"]), ("project.lock.read", &["path", "source"]), ("project.lock.write", &["path", "source"]), ("project.naming.too_many_collisions", &["attempts"]), ("project.naming.wordlist_too_small", &["count"]), ("project.not_a_project", &["path"]), ("project.path_not_found", &["path"]), ("project.safe_delete.io", &["path", "source"]), ("project.safe_delete.refused", &["path", "reason"]), ("project.user_name.empty", &[]), ("project.user_name.invalid_char", &["ch"]), ("project.user_name.leading_dot", &[]), ("project.resume_no_previous", &["data_root"]), ("project.resume_recorded_missing", &["path"]), ("project.saveas_target_exists", &["path"]), ("project.rebuild_failed", &["error"]), ("project.rebuild_ok", &["summary"]), ("project.switch_failed", &["error"]), ("project.switched_ok", &["display_name"]), // ---- Advanced-mode placeholder ---- ("advanced_mode.not_implemented", &["input"]), // ---- Advanced-mode SQL surface (ADR-0030) ---- ("advanced_mode.sql_in_simple", &["command"]), ("advanced_mode.also_valid_sql", &[]), // Education note appended to a simple-mode INSERT Form B // parse error when the user supplied more values than the // non-auto-generated columns expect (issue #1 sub-task 2). ( "insert.form_b_extra_values_note", &["table", "expected_phrase", "auto_phrase", "all_cols"], ), // Pre-flight teaching note for advanced-mode positional // `INSERT INTO T VALUES (…)` (no column list) when the value // count doesn't match the column count (issue #1 sub-task 3). ( "insert.form_b_positional_count_mismatch_note", &["table", "col_count", "col_list", "supplied", "non_auto_csv"], ), ("select.internal_table", &["table"]), ( "cli.invalid_value", &["flag", "value", "expected"], ), ("cli.missing_value", &["flag"]), ("cli.multiple_paths", &["first", "second"]), ("cli.resume_with_path", &[]), ("cli.unknown_argument", &["arg"]), ( "archive.export_sequence_exhausted", &["project", "target_dir", "limit"], ), ("archive.import_collision_exhausted", &["path", "limit"]), ("archive.invalid_zip", &["detail"]), ("archive.io", &["path", "source"]), ("archive.multiple_top_folders", &[]), ("archive.not_a_project_archive", &[]), ("archive.unsafe_entry", &["entry"]), ("archive.zip", &["path", "message"]), // ---- DSL failure wrapper / running echo ---- ("dsl.failed", &["rendered"]), ("dsl.running", &["input"]), // ---- Persistence-fatal banner ---- ("fatal.persistence", &["operation", "path", "message"]), // ---- Modal labels ---- ("modal.generic_cancelled", &["title"]), ("modal.load_cancelled", &[]), ("modal.load_picker_empty", &[]), ("modal.load_picker_nothing", &[]), ("modal.load_picker_path_prompt", &[]), ("modal.load_picker_title", &[]), ("modal.path_entry_empty_name", &[]), ("modal.path_entry_empty_path", &[]), ("modal.rebuild_cancelled", &[]), ("modal.rebuild_confirm_prompt", &[]), ("modal.rebuild_confirm_title", &[]), ("modal.redo_cancelled", &[]), ("modal.redo_confirm_command", &[]), ("modal.redo_confirm_title", &[]), ("modal.undo_cancelled", &[]), ("modal.undo_confirm_command", &[]), ("modal.undo_confirm_prompt", &[]), ("modal.undo_confirm_title", &[]), ("modal.undo_confirm_when", &["timestamp"]), // ---- Undo / redo command surfaces (ADR-0006 Amendment 1) ---- ("undo.disabled", &[]), ("undo.nothing_to_undo", &[]), ("undo.nothing_to_redo", &[]), ("undo.undone_ok", &["command"]), ("undo.redone_ok", &["command"]), ("undo.undo_failed", &["error"]), ("undo.redo_failed", &["error"]), // ---- Status bar + panels ---- ("panel.hint_empty", &[]), ("panel.hint_mode_advanced", &[]), ("panel.hint_title", &[]), ("panel.output_title", &[]), ("panel.relationships_empty", &[]), ("panel.relationships_title", &[]), ("panel.tables_empty", &[]), ("panel.tables_title", &[]), ("status.no_project", &[]), ("status.project_label", &[]), ("value.format", &["column", "message"]), ("value.type_mismatch", &["column", "expected_human", "got"]), // ---- Save / save-as surfaces ---- ("save.already_saved", &[]), ("save.path_prompt", &[]), ("save.title_as", &[]), ("save.title_save", &[]), // ---- Shortcut hint labels ---- ("shortcut.back_to_list", &[]), ("shortcut.browse", &[]), ("shortcut.browse_path", &[]), ("shortcut.cancel", &[]), ("shortcut.clear", &[]), ("shortcut.complete", &[]), ("shortcut.confirm", &[]), ("shortcut.cycle", &[]), ("shortcut.del_word", &[]), ("shortcut.hint", &[]), ("shortcut.history", &[]), ("shortcut.home_end", &[]), ("shortcut.load", &[]), ("shortcut.nav", &[]), ("shortcut.next_pane", &[]), ("shortcut.no", &[]), ("shortcut.run", &[]), ("shortcut.scroll", &[]), ("shortcut.select", &[]), ("shortcut.to_input", &[]), ("shortcut.yes", &[]), // ---- mode / messages banners ---- ("messages.set_short", &[]), ("messages.set_verbose", &[]), ("messages.show", &["current"]), ("messages.unknown", &["value"]), ("mode.label_advanced", &[]), ("mode.label_advanced_one_shot", &[]), ("mode.label_simple", &[]), ("mode.set_advanced", &[]), ("mode.set_simple", &[]), ("mode.show_advanced", &[]), ("mode.show_simple", &[]), ("mode.unknown", &["value"]), ("mode.usage", &[]), // ---- copy (ADR-0041) ---- ("copy.done", &["count"]), ("copy.nothing", &[]), ("copy.unknown", &["value"]), // ---- DbError Display fallback ---- ("db.error.invalid_value", &["detail"]), ("db.error.io", &["detail"]), ( "db.error.persistence_fatal", &["operation", "path", "message"], ), ( "db.error.rebuild_row_failed", &["row_number", "csv_path", "table", "detail"], ), ("db.error.sqlite", &["message"]), ("db.error.unsupported", &["detail"]), ("db.error.worker_gone", &[]), // ---- Cascade-effect summaries (per ADR-0014) ---- ("db.cascade.action_blocked", &[]), ("db.cascade.action_deleted", &[]), ("db.cascade.action_set_null", &[]), ( "db.cascade.summary", &["count", "action", "child_table", "rel", "on_delete"], ), // ---- change-column dry-run diagnostics (per ADR-0017) ---- // ---- add-constraint dry-run diagnostics (per ADR-0029 §5) ---- ( "db.diagnostic.add_check_summary", &["table", "column", "total", "rule"], ), ( "db.diagnostic.add_not_null_summary", &["table", "column", "total"], ), ( "db.diagnostic.add_unique_summary", &["table", "column", "total"], ), ("db.diagnostic.force_conversion_hint", &[]), ("db.diagnostic.header_becomes", &[]), ("db.diagnostic.header_from", &[]), ("db.diagnostic.header_reason", &[]), ("db.diagnostic.header_source_rows", &["pk_label"]), ("db.diagnostic.header_source_values", &[]), ("db.diagnostic.header_to", &[]), ("db.diagnostic.header_value", &[]), ( "db.diagnostic.incompatible_summary", &["table", "column", "src_ty", "target_ty", "total"], ), ( "db.diagnostic.lossy_summary", &["table", "column", "src_ty", "target_ty", "total"], ), ( "db.diagnostic.uniqueness_summary", &["table", "column", "src_ty", "target_ty", "total"], ), // ---- DSL command success summaries (ADR-0019 §9 sweep) ---- ("ok.index_dropped_with_column", &["index"]), ("ok.rows_deleted", &["count"]), ("ok.rows_inserted", &["count"]), ("ok.rows_seeded", &["count", "table"]), ("ok.rows_updated", &["count"]), ("seed.capped", &["requested"]), ("seed.advisory_generic", &["columns", "column", "table"]), // ---- Client-side success notes (ADR-0017 §6, ADR-0018 §9) ---- ("client_side.auto_fill_add_serial", &["count"]), ("client_side.auto_fill_add_shortid", &["count"]), ("client_side.auto_fill_transition", &["count", "kind"]), ("client_side.dont_convert_caveat", &[]), ("client_side.transformed", &["count"]), ("client_side.transformed_lossy", &["count", "lossy"]), // ---- Replay command surfaces (ADR-0019 §9 sweep) ---- ("replay.command_echo", &["command"]), ("replay.completed", &["path", "count"]), ("replay.error_could_not_open", &["path", "detail"]), ("replay.error_parse", &["detail"]), ("replay.failed_at_line", &["path", "line_number", "error"]), ("replay.failed_open", &["path", "error"]), ("replay.skipped_import", &["line", "command"]), ("replay.skipped_replay", &["line", "command"]), // ---- UNIQUE violations (anchor: "already has the value") ---- ( "error.unique.insert.headline", &["table", "column", "value"], ), ("error.unique.insert.hint", &["table", "column"]), ("error.unique.pk.insert.headline", &["table", "value"]), ("error.unique.pk.insert.hint", &[]), ("error.unique.pk.update.headline", &["table", "value"]), ("error.unique.pk.update.hint", &[]), ( "error.unique.update.headline", &["table", "column", "value"], ), ("error.unique.update.hint", &["table", "column"]), ]; #[cfg(test)] mod tests { use super::KEYS_AND_PLACEHOLDERS; use crate::friendly::format::catalog; use std::collections::HashSet; // The pre-Phase-F `keyword_and_punct_have_complete_token_vocabulary` // test cross-checked the `Keyword` / `Punct` enums against // `parse.token.keyword.*` / `parse.token.punct.*` catalog // keys. With those enums deleted (ADR-0024 §migration Phase F) // and the walker rendering keyword wording via // `format!("`{word}`")`, the catalog entries survive only as // historic vocabulary; the `keys_validate_against_catalog` // test below still asserts every key in `KEYS_AND_PLACEHOLDERS` // resolves and vice versa, which keeps the catalog itself // honest. The dead entries collapse in ADR-0024 §cleanup-pass. /// Walks `KEYS_AND_PLACEHOLDERS` and verifies every entry /// matches the catalog. ADR-0019 §8.6. /// /// Checks: /// 1. every declared key exists in the catalog; /// 2. every declared placeholder appears in the template; /// 3. every placeholder used is declared (catches typos); /// 4. every catalog key (outside `_test.*`) is declared /// (catches dead YAML entries); /// 5. no template contains a format specifier /// (`{name:...}`); ADR-0019 §8.4 forbids these; /// 6. no template contains forbidden engine vocabulary /// (ADR-0002 user-facing posture; same forbidden list /// as `tests/engine_vocabulary_audit.rs`). #[test] fn keys_validate_against_catalog() { let cat = catalog(); let mut errors: Vec = Vec::new(); for (key, expected) in KEYS_AND_PLACEHOLDERS { let Some(template) = cat.get(key) else { errors.push(format!("catalog missing key `{key}`")); continue; }; // Placeholder set check (declared ↔ used). let actual = collect_placeholders(template); let expected_set: HashSet<&str> = expected.iter().copied().collect(); for name in &expected_set { if !actual.contains(*name) { errors.push(format!( "key `{key}`: declared placeholder `{{{name}}}` is not used in template:\n{template}" )); } } for name in &actual { if !expected_set.contains(name.as_str()) { errors.push(format!( "key `{key}`: template uses `{{{name}}}` but it isn't declared in keys.rs:\n{template}" )); } } // Format-specifier check (ADR-0019 §8.4). Look for // `{name:...}` shapes — the substitute helper would // panic at runtime, but catching it at test time // means we never ship a binary that can hit that // panic. if has_format_specifier(template) { errors.push(format!( "key `{key}`: template contains a `{{name:...}}` format specifier:\n{template}" )); } // Engine-vocabulary check (ADR-0002 user-facing // posture, regression-tested in // tests/engine_vocabulary_audit.rs). for needle in FORBIDDEN_ENGINE_VOCABULARY { if template.contains(needle) { errors.push(format!( "key `{key}`: template contains forbidden token `{needle}`:\n{template}" )); } } } let declared: HashSet<&str> = KEYS_AND_PLACEHOLDERS.iter().map(|(k, _)| *k).collect(); for key in cat.keys() { if key.starts_with("_test.") { continue; } if !declared.contains(key) { errors.push(format!( "catalog has key `{key}` but it isn't declared in keys::KEYS_AND_PLACEHOLDERS" )); } } assert!( errors.is_empty(), "catalog validation failed:\n {}", errors.join("\n ") ); } /// Mirror of `tests/engine_vocabulary_audit.rs::FORBIDDEN`, /// duplicated here so the catalog validator is self-contained /// (no dependency on the integration-test binary). const FORBIDDEN_ENGINE_VOCABULARY: &[&str] = &[ "SQLite", "sqlite", "rusqlite", "STRICT", "PRAGMA", ]; /// Detect a `{name:...}` format-specifier placeholder. /// Doubled braces `{{` / `}}` are escapes — must skip them. fn has_format_specifier(template: &str) -> bool { let mut chars = template.chars().peekable(); while let Some(c) = chars.next() { if c == '{' { if chars.peek() == Some(&'{') { chars.next(); continue; } while let Some(&nc) = chars.peek() { if nc == '}' { break; } if nc == ':' { return true; } chars.next(); } } else if c == '}' && chars.peek() == Some(&'}') { chars.next(); } } false } /// Walk `template` and pull out every `{name}` placeholder. /// Mirrors the substitution helper's parse — if the helper /// accepts a placeholder, this collects it. fn collect_placeholders(template: &str) -> HashSet { let mut out = HashSet::new(); let mut chars = template.chars().peekable(); while let Some(c) = chars.next() { if c == '{' { if chars.peek() == Some(&'{') { chars.next(); continue; } let mut name = String::new(); while let Some(&nc) = chars.peek() { if nc == '}' { chars.next(); break; } chars.next(); name.push(nc); } if !name.is_empty() && !name.contains(':') { out.insert(name); } } else if c == '}' && chars.peek() == Some(&'}') { chars.next(); } } out } }