Skip to content

feat(cli): reject --reset --schema awa and document substrate ownership#313

Merged
hardbyte merged 2 commits into
mainfrom
brian/issue-308-reset-guard-and-docs
Jun 2, 2026
Merged

feat(cli): reject --reset --schema awa and document substrate ownership#313
hardbyte merged 2 commits into
mainfrom
brian/issue-308-reset-guard-and-docs

Conversation

@hardbyte
Copy link
Copy Markdown
Owner

@hardbyte hardbyte commented Jun 2, 2026

Summary

The default awa schema holds both the queue-storage substrate and the canonical migration tables (schema_version, runtime_instances, storage_transition_state, runtime_storage_backends, ...). A DROP SCHEMA awa CASCADE would take the migration tables with it and leave the database unrecoverable.

This PR is the third of three landing the v0.6 substrate-ownership work:

  • Reset guard. awa storage prepare-queue-storage-schema --schema awa --reset is now hard-rejected with an error pointing operators at the custom-schema escape (--schema <other>) or the in-flight transition rewind (awa storage abort).
  • Canonical contract doc. Adds docs/queue-storage-substrate.md — who installs what (awa migrate for the default schema plus all canonical tables; prepare_schema() / the CLI for custom schemas), the default-shape requirements the helper enforces for p_schema = 'awa', and the reset-guard semantics. Cross-linked from migrations.md, architecture.md, and security.md.
  • v022 comment tightening. Clarifies that the to_regclass guard in delete_job_compat is a no-op on the default install (v023 materialises the counter table via migrate); it still matters for custom schemas, partially-prepared substrates, and external migration tooling that applies DDL out of order.

Test plan

  • cargo fmt --all
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo build --workspace
  • cargo test -p awa-cli against local Postgres — new prepare_schema_reset_guard_test passes both cases (rejection error message contains all three required hints; rejected reset does not mutate awa.schema_version); existing storage_finalize_cli_test and other CLI tests still green
  • Smoke: awa storage prepare-queue-storage-schema --schema awa --reset rejected with the canonical-schema error; --schema <other> --reset proceeds normally

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Added a safety check that prevents resetting the default schema using the storage prepare command. Clear error messages now guide operators when this operation is attempted.
  • Documentation

    • Expanded documentation on queue-storage substrate installation and schema preparation, including guidance for custom schemas and related configuration steps.
    • Added detailed security guidance on roles and privileges for custom queue-storage schemas.
    • Enhanced migration docs to clarify queue-storage migration behaviors and substrate ownership.

…ship

The default `awa` schema holds both the queue-storage substrate and
the canonical migration tables (schema_version, runtime_instances,
storage_transition_state, runtime_storage_backends, ...). A
`DROP SCHEMA awa CASCADE` would take the migration tables with it
and leave the database unrecoverable, so
`awa storage prepare-queue-storage-schema --schema awa --reset`
is now hard-rejected with an error that points operators at the
custom-schema path or `awa storage abort`.

Adds docs/queue-storage-substrate.md as the canonical ownership
contract: who installs what (`awa migrate` for the default schema
plus all canonical tables; `prepare_schema()` / the CLI for custom
schemas), the default-shape requirements the helper enforces for
`p_schema = 'awa'`, and the reset-guard semantics. Cross-linked
from migrations.md, architecture.md, and security.md.

Also clarifies the `to_regclass` guard in v022 — the counter table
is materialised by the v023 migration, so the guard is a no-op on
the default install; it still matters for custom schemas,
partially-prepared substrates, and external migration tools that
apply DDL out of order.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b91f6a2a-b1e1-4dab-a03e-8d9d1b7d282b

📥 Commits

Reviewing files that changed from the base of the PR and between e1c89ab and 0aead6b.

📒 Files selected for processing (1)
  • docs/queue-storage-substrate.md
✅ Files skipped from review due to trivial changes (1)
  • docs/queue-storage-substrate.md

📝 Walkthrough

Walkthrough

The PR adds a CLI guard refusing --reset when --schema awa, two integration tests that verify the guard and ensure no DB mutations occur, a new queue-storage-substrate documentation page defining ownership and constraints, and cross-references in architecture, migrations, and security docs.

Changes

Queue-storage substrate reset guard and ownership documentation

Layer / File(s) Summary
Reset guard implementation and validation tests
awa-cli/src/main.rs, awa-cli/tests/prepare_schema_reset_guard_test.rs
CLI guard refuses --reset --schema awa and returns detailed guidance; tests assert the command fails, stderr contains the refusal/help text, and the database schema_version is unchanged after the rejected reset.
Queue-storage substrate ownership contract and customization guide
docs/queue-storage-substrate.md
Adds a comprehensive page defining substrate ownership across awa migrate, QueueStorage::prepare_schema()/CLI, and awa.install_queue_storage_substrate(...); documents advisory lock, default awa constraints, custom-schema install/activation flow, and the exact --reset rejection message.
Supporting documentation and migration comment update
docs/architecture.md, docs/migrations.md, docs/security.md, awa-model/migrations/v022_delete_compat_terminal_counter.sql
Architecture, migrations, and security docs cross-reference the new substrate ownership contract; migration SQL comment reworded to state queue_terminal_live_counts is materialized by awa migrate and list remaining guard scenarios.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • #308: Addresses the same destructive-reset guard for the default awa queue-storage schema and documents default-substrate ownership.

Possibly related PRs

  • hardbyte/awa#304: Overlaps with migration/comment changes around delete_job_compat and queue_terminal_live_counts.

Poem

🐰 I stand by the schema with whiskers alert,
"Don't DROP awa—this table's not to hurt."
Docs bloom like carrots, clear paths to tread,
Tests nod and confirm no data was shred.
Hooray for safe rolls and migrations ahead!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(cli): reject --reset --schema awa and document substrate ownership' accurately and specifically summarizes the main changes: implementing a reset guard for the CLI and adding comprehensive documentation about substrate ownership.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brian/issue-308-reset-guard-and-docs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e1c89abd86

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.connect(&database_url())
.await
.expect("connect");
let version_before: i32 = sqlx::query_scalar("SELECT MAX(version) FROM awa.schema_version")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Run migrations before reading schema_version

In the CI test job I checked (.github/workflows/ci.yml runs cargo test --workspace against a fresh Postgres service without a prior migrate step), this new test connects directly and immediately queries awa.schema_version. If this test binary runs before any other test happens to call the shared setup helper, or if it is run by itself, the awa schema/table does not exist and the test panics before exercising the reset guard. Please run the normal migration/setup path in this test before asserting on schema_version.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
awa-cli/src/main.rs (1)

888-888: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Unsafe identifier interpolation in DROP SCHEMA query.

schema is user-controlled and interpolated directly into SQL. This allows SQL injection via crafted schema names.

Suggested fix
-                        if reset {
-                            sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE"))
-                                .execute(&pool)
-                                .await?;
-                        }
+                        if reset {
+                            // Quote as SQL identifier safely.
+                            let quoted_schema: String =
+                                sqlx::query_scalar("SELECT pg_catalog.quote_ident($1)")
+                                    .bind(&schema)
+                                    .fetch_one(&pool)
+                                    .await?;
+                            let drop_sql = format!("DROP SCHEMA IF EXISTS {quoted_schema} CASCADE");
+                            sqlx::query(&drop_sql).execute(&pool).await?;
+                        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@awa-cli/src/main.rs` at line 888, The interpolated DROP SCHEMA string
sqlx::query(&format!("DROP SCHEMA IF EXISTS {schema} CASCADE")) is vulnerable to
SQL injection; fix it by either validating/sanitizing the user-controlled schema
identifier (e.g., enforce a strict regex like ^[A-Za-z0-9_]+$ and return an
error for invalid names) or safely quote/escape the identifier before
interpolation (replace any " with "" and wrap the name in double quotes) and
then use that safe identifier in the format call; update the code where
sqlx::query(... "DROP SCHEMA IF EXISTS {schema} CASCADE" ...) is constructed to
perform one of these protections before executing the query.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@awa-cli/src/main.rs`:
- Around line 870-880: The current guard checks schema == "awa" which only
blocks that exact casing; update the if condition that references reset and
schema so it compares case-insensitively (e.g., use
schema.eq_ignore_ascii_case("awa") or compare schema.to_ascii_lowercase() ==
"awa") and also consider trimming whitespace if inputs may include it, ensuring
the destructive DROP path is blocked for any case variant of "awa" in the same
if block where reset and schema are evaluated.

In `@docs/queue-storage-substrate.md`:
- Around line 69-75: The fenced error-output block containing the multi-line
Error: "Refusing to DROP SCHEMA awa CASCADE …" needs a markdown fence language
tag (markdownlint MD040); update that fenced code block to start with ```text
(or ```console) so the block is explicitly marked as plain text and the docs
lint will pass.

---

Outside diff comments:
In `@awa-cli/src/main.rs`:
- Line 888: The interpolated DROP SCHEMA string sqlx::query(&format!("DROP
SCHEMA IF EXISTS {schema} CASCADE")) is vulnerable to SQL injection; fix it by
either validating/sanitizing the user-controlled schema identifier (e.g.,
enforce a strict regex like ^[A-Za-z0-9_]+$ and return an error for invalid
names) or safely quote/escape the identifier before interpolation (replace any "
with "" and wrap the name in double quotes) and then use that safe identifier in
the format call; update the code where sqlx::query(... "DROP SCHEMA IF EXISTS
{schema} CASCADE" ...) is constructed to perform one of these protections before
executing the query.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 57fe9f32-17e5-435d-b078-efa5852b805b

📥 Commits

Reviewing files that changed from the base of the PR and between 44a11d6 and e1c89ab.

📒 Files selected for processing (7)
  • awa-cli/src/main.rs
  • awa-cli/tests/prepare_schema_reset_guard_test.rs
  • awa-model/migrations/v022_delete_compat_terminal_counter.sql
  • docs/architecture.md
  • docs/migrations.md
  • docs/queue-storage-substrate.md
  • docs/security.md

Comment thread awa-cli/src/main.rs
Comment on lines +870 to +880
if reset && schema == "awa" {
return Err(
"Refusing to DROP SCHEMA awa CASCADE — schema 'awa' is the \
default migration-owned queue-storage substrate and also \
contains the canonical migration tables (schema_version, \
runtime_instances, storage_transition_state, etc.). \
Use --schema <other> for a throwaway substrate, or \
'awa storage abort' to rewind an in-flight transition."
.into(),
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Guard can be bypassed with case variants of awa.

schema == "awa" only blocks one exact casing. --schema AWA --reset still maps to awa in PostgreSQL (unquoted identifier folding), so the destructive path is still reachable.

Suggested fix
-                        if reset && schema == "awa" {
+                        let is_default_awa = schema.eq_ignore_ascii_case("awa");
+                        if reset && is_default_awa {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if reset && schema == "awa" {
return Err(
"Refusing to DROP SCHEMA awa CASCADE — schema 'awa' is the \
default migration-owned queue-storage substrate and also \
contains the canonical migration tables (schema_version, \
runtime_instances, storage_transition_state, etc.). \
Use --schema <other> for a throwaway substrate, or \
'awa storage abort' to rewind an in-flight transition."
.into(),
);
}
let is_default_awa = schema.eq_ignore_ascii_case("awa");
if reset && is_default_awa {
return Err(
"Refusing to DROP SCHEMA awa CASCADE — schema 'awa' is the \
default migration-owned queue-storage substrate and also \
contains the canonical migration tables (schema_version, \
runtime_instances, storage_transition_state, etc.). \
Use --schema <other> for a throwaway substrate, or \
'awa storage abort' to rewind an in-flight transition."
.into(),
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@awa-cli/src/main.rs` around lines 870 - 880, The current guard checks schema
== "awa" which only blocks that exact casing; update the if condition that
references reset and schema so it compares case-insensitively (e.g., use
schema.eq_ignore_ascii_case("awa") or compare schema.to_ascii_lowercase() ==
"awa") and also consider trimming whitespace if inputs may include it, ensuring
the destructive DROP path is blocked for any case variant of "awa" in the same
if block where reset and schema are evaluated.

Comment thread docs/queue-storage-substrate.md Outdated
@hardbyte hardbyte merged commit b8b28e7 into main Jun 2, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant