Skip to content

STINGS MIGRATIONS SASHI and also add some to scope one#2

Open
rhit-sashiks wants to merge 2 commits into
masterfrom
stings
Open

STINGS MIGRATIONS SASHI and also add some to scope one#2
rhit-sashiks wants to merge 2 commits into
masterfrom
stings

Conversation

@rhit-sashiks
Copy link
Copy Markdown
Contributor

@rhit-sashiks rhit-sashiks commented May 11, 2026

LGTM

Summary by CodeRabbit

  • Refactor
    • Enhanced database operation type definitions with explicit parameter declarations to improve type safety and code reliability.
    • Reorganized database migration execution sequence to better coordinate operational phases and improve system consistency.
    • Improved code structure for enhanced maintainability across the codebase.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

📝 Walkthrough

Walkthrough

This PR updates Luau database type signatures to require explicit self parameters, adds a data migration that converts legacy stings records to a new format, and reorders migrations to ensure the new Luau migration executes after other Luau migrations are loaded.

Changes

Database Type Signatures

Layer / File(s) Summary
Method Signatures
luau/twshell/common/db.luau
Db, DbTx, DbValue, and PgRow methods now explicitly require self as the first parameter instead of implicit receiver handling. All 12 method signatures updated to match this pattern.

Stings Data Migration

Layer / File(s) Summary
Migration Constants
luau/twshell/migrations/stings.luau
Defines legacy scope builtins.stings, target scope __kexsbuiltins.stings, and a far-future expiry constant.
Load Legacy Data
luau/twshell/migrations/stings.luau
Opens a database transaction and loads all legacy stings rows from tenant_kv, extracting userid, modid, reason, and stings into an in-memory map indexed by (owner_id, owner_type, key).
Update Existing Kex Rows
luau/twshell/migrations/stings.luau
Fetches existing kex rows and updates their stored value to the new structure containing expiresat and nested data fields with the migrated legacy data.
Insert New Kex Rows
luau/twshell/migrations/stings.luau
For legacy entries without a matching kex row, inserts new rows with far-future expiry using ON CONFLICT upsert logic.
Cleanup and Commit
luau/twshell/migrations/stings.luau
Deletes all legacy rows, wraps the transaction in pcall for rollback on error, and commits on success.

Migration Orchestration

Layer / File(s) Summary
Ordering Constants
src/migrations/mod.rs
Splits Rust migrations into RUST_MIGRATIONS_BEFORE_LUAU (11 migrations) and RUST_MIGRATIONS_AFTER_LUAU (1 migration), moving kv_scope_unnest to the post-Luau phase.
Migration Builder and Cleanup
src/migrations/mod.rs, src/migrations/kv_scope_unnest.rs
Updates migrations() builder to construct the execution list as pre-Luau Rust, then all Luau, then post-Luau Rust. Includes code reformatting to kv_scope_unnest.rs without logic changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Explicit self, a clearer way,
Stings migrate from old to new each day,
Luau runs first, then Rust takes flight—
The migration order set just right!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The pull request title is vague and unclear. While it mentions 'STINGS MIGRATIONS' which relates to the Luau migration for stings conversion, the phrases 'SASHI' (appears to be unrelated or a name fragment) and 'also add some to scope one' do not clearly convey the purpose of the changes. The title does not effectively summarize the main technical change. Revise the title to clearly describe the migration work, e.g., 'Add STINGS migration to convert legacy builtins.stings to new __kexsbuiltins.stings format' or similar that accurately reflects the primary changes.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch stings
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch stings

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@rhit-sashiks
Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

Co-authored-by: rhit-sashiks <186237416+rhit-sashiks@users.noreply.github.com>
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: 3

🤖 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 `@luau/twshell/migrations/stings.luau`:
- Around line 39-43: The migration is skipping rows when userid is missing (see
the missing_userid increment and continue) but later unconditionally deletes all
rows in OLD_SCOPE (lines around the OLD_SCOPE deletion), causing data loss;
change the deletion logic so only rows that were actually migrated are
removed—e.g., accumulate migrated keys (or mark processed rows) when handling
key/owner_type/owner_id/scopes and then run DELETE only for those keys (or add
WHERE userid IS NOT NULL and match the processed key set) instead of deleting
all OLD_SCOPE rows; ensure references to missing_userid, key, owner_type,
owner_id, scopes, and OLD_SCOPE are used to identify which rows to keep vs
delete.
- Around line 25-29: The PgRow.get calls are using 0-based indexes but the API
is 1-based; update the row:get(...) calls for the migration tuple unpacking (the
locals owner_id, owner_type, key, scopes, value) from 0..4 to 1..5 (and do the
same for the second occurrence later around the other unpack block at lines
68-72) so each call uses the correct 1-based column index; search for any other
row:get(...) in this migration and adjust similarly to ensure all column
accesses use 1-based indices.

In `@src/migrations/mod.rs`:
- Around line 125-130: The Luau migrations pulled from MigrationsFolder::iter()
are not ordered, causing nondeterministic migration sequences; collect the
iterator results into a Vec, sort them deterministically (e.g.,
lexicographically by the embedded file name), then map them to
MigrationType::Luau and append to base_migrations (which already contains
RUST_MIGRATIONS_BEFORE_LUAU mapped to MigrationType::Rust) so that the Luau
entries are always applied in a stable, reproducible order.
🪄 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: 3397a90a-f481-4b17-bfd5-c076bf1fdf43

📥 Commits

Reviewing files that changed from the base of the PR and between 264f7a8 and 8e62657.

📒 Files selected for processing (4)
  • luau/twshell/common/db.luau
  • luau/twshell/migrations/stings.luau
  • src/migrations/kv_scope_unnest.rs
  • src/migrations/mod.rs

Comment on lines +25 to +29
local owner_id = row:get(0, "string"):take() :: string
local owner_type = row:get(1, "string"):take() :: string
local key = row:get(2, "string"):take() :: string
local scopes = row:get(3, "{string}"):take() :: {string}
local value = row:get(4, "custom@khronosvalue"):take() :: any
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify API contract and call-site mismatch (read-only).
rg -n 'get:\s*\(self: PgRow, index: number' luau/twshell/common/db.luau
rg -n 'row:get\([0-9]+' luau/twshell/migrations/stings.luau

Repository: Anti-Raid/template-worker

Length of output: 880


🏁 Script executed:

# Read context around the PgRow.get function definition in db.luau
cat -n luau/twshell/common/db.luau | sed -n '25,45p'

Repository: Anti-Raid/template-worker

Length of output: 852


🏁 Script executed:

# Search for all usages of PgRow.get across the codebase to understand indexing patterns
rg -A 2 -B 2 'row:get\(' luau/twshell/ --type lua

Repository: Anti-Raid/template-worker

Length of output: 51


PgRow.get indexes are off by one (0-based used, API is 1-based).

Lines 25-29 and 68-72 use row:get(0, ...) through row:get(4, ...), but PgRow.get is documented as 1-based. This will read from wrong columns during migration.

🔧 Proposed fix
-                local owner_id = row:get(0, "string"):take() :: string
-                local owner_type = row:get(1, "string"):take() :: string
-                local key = row:get(2, "string"):take() :: string
-                local scopes = row:get(3, "{string}"):take() :: {string}
-                local value = row:get(4, "custom@khronosvalue"):take() :: any
+                local owner_id = row:get(1, "string"):take() :: string
+                local owner_type = row:get(2, "string"):take() :: string
+                local key = row:get(3, "string"):take() :: string
+                local scopes = row:get(4, "{string}"):take() :: {string}
+                local value = row:get(5, "custom@khronosvalue"):take() :: any
@@
-                local id = row:get(0, "string"):take() :: string
-                local owner_id = row:get(1, "string"):take() :: string
-                local owner_type = row:get(2, "string"):take() :: string
-                local key = row:get(3, "string"):take() :: string
-                local value = row:get(4, "custom@khronosvalue"):take() :: any
+                local id = row:get(1, "string"):take() :: string
+                local owner_id = row:get(2, "string"):take() :: string
+                local owner_type = row:get(3, "string"):take() :: string
+                local key = row:get(4, "string"):take() :: string
+                local value = row:get(5, "custom@khronosvalue"):take() :: any
📝 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
local owner_id = row:get(0, "string"):take() :: string
local owner_type = row:get(1, "string"):take() :: string
local key = row:get(2, "string"):take() :: string
local scopes = row:get(3, "{string}"):take() :: {string}
local value = row:get(4, "custom@khronosvalue"):take() :: any
local owner_id = row:get(1, "string"):take() :: string
local owner_type = row:get(2, "string"):take() :: string
local key = row:get(3, "string"):take() :: string
local scopes = row:get(4, "{string}"):take() :: {string}
local value = row:get(5, "custom@khronosvalue"):take() :: any
🤖 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 `@luau/twshell/migrations/stings.luau` around lines 25 - 29, The PgRow.get
calls are using 0-based indexes but the API is 1-based; update the row:get(...)
calls for the migration tuple unpacking (the locals owner_id, owner_type, key,
scopes, value) from 0..4 to 1..5 (and do the same for the second occurrence
later around the other unpack block at lines 68-72) so each call uses the
correct 1-based column index; search for any other row:get(...) in this
migration and adjust similarly to ensure all column accesses use 1-based
indices.

Comment on lines +39 to +43
if not userid then
print(string.format("[stings] sting key=%s owner=%s/%s has no userid in scopes %s; skipping", key, owner_type, owner_id, table.concat(scopes, ",")))
missing_userid += 1
continue
end
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

Skipped rows are still deleted, causing data loss.

Line 42 skips rows with missing userid, but Line 138 deletes all OLD_SCOPE rows anyway. That drops records you explicitly did not migrate.

🔧 Proposed fix
-            local deleted = tx:execute(
-                "DELETE FROM tenant_kv WHERE $1 = ANY(scopes)",
-                { db:cast(OLD_SCOPE, "string") }
-            )
+            local deleted = tx:execute(
+                [[DELETE FROM tenant_kv
+                  WHERE $1 = ANY(scopes)
+                  AND EXISTS (
+                      SELECT 1 FROM unnest(scopes) AS s WHERE s <> $1
+                  )]],
+                { db:cast(OLD_SCOPE, "string") }
+            )

Also applies to: 138-141

🤖 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 `@luau/twshell/migrations/stings.luau` around lines 39 - 43, The migration is
skipping rows when userid is missing (see the missing_userid increment and
continue) but later unconditionally deletes all rows in OLD_SCOPE (lines around
the OLD_SCOPE deletion), causing data loss; change the deletion logic so only
rows that were actually migrated are removed—e.g., accumulate migrated keys (or
mark processed rows) when handling key/owner_type/owner_id/scopes and then run
DELETE only for those keys (or add WHERE userid IS NOT NULL and match the
processed key set) instead of deleting all OLD_SCOPE rows; ensure references to
missing_userid, key, owner_type, owner_id, scopes, and OLD_SCOPE are used to
identify which rows to keep vs delete.

Comment thread src/migrations/mod.rs
Comment on lines +125 to 130
let mut base_migrations = RUST_MIGRATIONS_BEFORE_LUAU.into_iter().map(MigrationType::Rust).collect::<Vec<_>>();

for migration in RUST_MIGRATIONS {
base_migrations.push(MigrationType::Rust(migration));
}

// Add luau migrations from MigrationsFolder after all base rust once have finished
// Add luau migrations from MigrationsFolder
for entry in MigrationsFolder::iter() {
base_migrations.push(MigrationType::Luau(entry));
}
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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does rust-embed (v8.x) guarantee sorted/deterministic order for RustEmbed::iter(), or is explicit sorting recommended for stable execution order?

💡 Result:

No. rust-embed (v8.x) does not explicitly guarantee a sorted/deterministic iteration order for RustEmbed::iter; it only documents that, depending on build mode, iter uses either a static array of relative file paths or lists files from the file system at runtime.[1][2] What can be inferred from the docs/source: - In release builds (or with the debug-embed feature), iter uses a “static array containing the list of relative file paths” and then iterates it via a slice iterator.[3] - In debug builds without debug-embed, iter “lists from the file system on each call,” so the order is whatever the underlying filesystem iteration returns for that platform/config.[1][2] - The public documentation you can rely on does not state that the embedded/compiled static array is sorted (e.g., lexicographically), nor does it promise deterministic ordering across builds/runs. Recommendation for stable execution order: - If you need stable ordering for any reason (tests, cache keys, reproducible outputs), explicitly sort the results yourself (e.g., collect into a Vec and sort by the path string) rather than relying on the order produced by iter. This is the safe approach given the documented “file system” case and the lack of a stated ordering guarantee for the static array.[1][2] Confidence: Medium (docs confirm dynamic vs static-array behavior, but do not explicitly state sorting/ordering guarantees for the static array; code inspection would be needed to confirm how the array is produced).

Citations:


🏁 Script executed:

cd src/migrations && head -n 150 mod.rs | tail -n 30

Repository: Anti-Raid/template-worker

Length of output: 1337


🏁 Script executed:

# Find MigrationsFolder definition
rg "MigrationsFolder" --type rust -B 2 -A 2

Repository: Anti-Raid/template-worker

Length of output: 721


🏁 Script executed:

# Check how migrations are used downstream
rg "base_migrations" src/ --type rust -A 3 -B 1

Repository: Anti-Raid/template-worker

Length of output: 1129


🏁 Script executed:

# Get more context around apply_migrations
rg "apply_migrations|for.*base_migrations" src/migrations/mod.rs -A 15 -B 2

Repository: Anti-Raid/template-worker

Length of output: 659


🏁 Script executed:

# Get the full apply_migrations function to see how migrations are processed
sed -n '/^pub async fn apply_migrations/,/^}/p' src/migrations/mod.rs | head -n 80

Repository: Anti-Raid/template-worker

Length of output: 2754


🏁 Script executed:

# Verify the exact location and syntax of the code being reviewed
sed -n '120,135p' src/migrations/mod.rs

Repository: Anti-Raid/template-worker

Length of output: 754


🏁 Script executed:

# Check the type returned by MigrationsFolder::iter() and its traits
rg "impl.*Embed|impl.*Display" src/ -A 5 | grep -A 10 "MigrationsFolder\|DirEntry"

Repository: Anti-Raid/template-worker

Length of output: 51


🏁 Script executed:

# Look for how Luau migrations (which are Cow<'static, str>) are used elsewhere
rg "MigrationType::Luau" src/ -B 2 -A 2

Repository: Anti-Raid/template-worker

Length of output: 714


Sort Luau migrations for deterministic and reproducible execution order.

The before/after Rust migration split is sound, but Luau migrations should be sorted to ensure consistent ordering across builds. Since rust-embed::iter() provides no ordering guarantees (behaves differently between debug/release builds and filesystem-dependent during development), explicitly sorting prevents nondeterministic migration sequencing in databases where order matters.

🔧 Proposed fix
-    // Add luau migrations from MigrationsFolder
-    for entry in MigrationsFolder::iter() {
+    // Add luau migrations from MigrationsFolder (deterministic order)
+    let mut luau_migrations = MigrationsFolder::iter().collect::<Vec<_>>();
+    luau_migrations.sort();
+    for entry in luau_migrations {
         base_migrations.push(MigrationType::Luau(entry));
     }
📝 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
let mut base_migrations = RUST_MIGRATIONS_BEFORE_LUAU.into_iter().map(MigrationType::Rust).collect::<Vec<_>>();
for migration in RUST_MIGRATIONS {
base_migrations.push(MigrationType::Rust(migration));
}
// Add luau migrations from MigrationsFolder after all base rust once have finished
// Add luau migrations from MigrationsFolder
for entry in MigrationsFolder::iter() {
base_migrations.push(MigrationType::Luau(entry));
}
let mut base_migrations = RUST_MIGRATIONS_BEFORE_LUAU.into_iter().map(MigrationType::Rust).collect::<Vec<_>>();
// Add luau migrations from MigrationsFolder (deterministic order)
let mut luau_migrations = MigrationsFolder::iter().collect::<Vec<_>>();
luau_migrations.sort();
for entry in luau_migrations {
base_migrations.push(MigrationType::Luau(entry));
}
🤖 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 `@src/migrations/mod.rs` around lines 125 - 130, The Luau migrations pulled
from MigrationsFolder::iter() are not ordered, causing nondeterministic
migration sequences; collect the iterator results into a Vec, sort them
deterministically (e.g., lexicographically by the embedded file name), then map
them to MigrationType::Luau and append to base_migrations (which already
contains RUST_MIGRATIONS_BEFORE_LUAU mapped to MigrationType::Rust) so that the
Luau entries are always applied in a stable, reproducible order.

Copy link
Copy Markdown

Copilot AI commented May 11, 2026

@copilot resolve the merge conflicts in this pull request

Resolved in commit a7e0d3a by merging origin/master into this branch and fixing the migration-file conflicts so the PR is mergeable. No UI changes were made.

@rhit-sashiks
Copy link
Copy Markdown
Contributor Author

@jack-e-tanner actually, can you fix the migration stuff to be deterministic order too with stings

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.

3 participants