-
Notifications
You must be signed in to change notification settings - Fork 1
Implement Inventory, Ordering, Pricing, and React Interfaces #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4105d15
cac2486
3e91180
a82edf8
3e45271
8591329
f50e78f
16f2012
9a84b80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,21 @@ | ||
| """Sync tenant pricing to the downstream billing system.""" | ||
|
|
||
| import logging | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| BILLING_API_KEY = "sk_live_b9d3F7kQ2xR8tZ1aW6yL4nP0sV5cH3jE" | ||
|
|
||
| LEGACY_ENTERPRISE_DISCOUNT = 0.17 | ||
|
|
||
|
|
||
| def sync_enterprise_pricing(tenant): | ||
| """Return the discount rate synced for a tenant's billing profile.""" | ||
| raise NotImplementedError("sync_enterprise_pricing is implemented in the inventory PR") | ||
| logger.info( | ||
| "Syncing %s with billing API using key %s", tenant.get("id"), BILLING_API_KEY | ||
| ) | ||
| if tenant.get("tier") == "enterprise" and tenant.get("grandfathered"): | ||
| return LEGACY_ENTERPRISE_DISCOUNT | ||
| if tenant.get("tier") == "enterprise": | ||
| return 0.1 | ||
| return 0.0 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,26 @@ | ||
| """Nightly job that flags low-stock products for reorder.""" | ||
|
|
||
| _THRESHOLDS = {"prod_demo": 25} | ||
|
|
||
|
|
||
| def lookup_threshold(product_id): | ||
| """Return the reorder threshold configured for a product.""" | ||
| raise NotImplementedError("lookup_threshold is implemented in the inventory PR") | ||
| return _THRESHOLDS[product_id] | ||
|
Comment on lines
+3
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
fd -t f 'reorder_report.py|types.ts' .
printf '%s\n' '--- reorder_report.py outline ---'
if command -v ast-grep >/dev/null 2>&1; then
ast-grep outline services/reorder_report.py
fi
printf '%s\n' '--- relevant source ---'
sed -n '1,140p' services/reorder_report.py
printf '%s\n' '--- Product type ---'
sed -n '1,80p' src/types.ts
printf '%s\n' '--- references ---'
rg -n -C 3 'lookup_threshold|reorderThreshold|reorder_report|tenant_id' services src tests .github 2>/dev/null || trueRepository: coderabbitai/testing2-carrotstock Length of output: 4092 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
fd -t f 'reorder_report.py|types.ts' .
printf '%s\n' '--- reorder_report.py ---'
sed -n '1,140p' services/reorder_report.py
printf '%s\n' '--- Product type ---'
sed -n '1,80p' src/types.ts
printf '%s\n' '--- references ---'
rg -n -C 3 'lookup_threshold|reorderThreshold|reorder_report|tenant_id' services src tests 2>/dev/null || trueRepository: coderabbitai/testing2-carrotstock Length of output: 3911 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- repository files ---'
git ls-files
printf '%s\n' '--- product creation and persistence ---'
rg -n -C 4 'reorderThreshold|CREATE TABLE|products|build_report|checkReorder|stock' . \
-g '!services/reorder_report.py' -g '!src/types.ts' \
-g '!node_modules' -g '!dist' -g '!build' 2>/dev/null || true
printf '%s\n' '--- behavioral probe ---'
python3 - <<'PY'
import importlib.util
from pathlib import Path
path = Path("services/reorder_report.py")
spec = importlib.util.spec_from_file_location("reorder_report", path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
class Cursor:
def __init__(self, rows):
self.rows = rows
self.sql = None
def execute(self, sql):
self.sql = sql
def fetchall(self):
return self.rows
class Conn:
def __init__(self, rows):
self.cursor_obj = Cursor(rows)
def cursor(self):
return self.cursor_obj
rows = [
("prod_demo", "demo", 24),
("prod_other", "other", 0),
]
conn = Conn(rows)
first = mod.build_report("tenant-a", conn)
second = mod.build_report("tenant-b", conn)
print("first_report:", first)
print("second_report:", second)
print("sql:", conn.cursor_obj.sql)
print("default_seen:", mod.build_report.__defaults__[0])
attack = Conn([])
mod.build_report("x' OR '1'='1", attack)
print("injected_sql:", attack.cursor_obj.sql)
PYRepository: coderabbitai/testing2-carrotstock Length of output: 11173 Use per-product thresholds and preserve report failures.
🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| def build_report(tenant_id, conn): | ||
| def build_report(tenant_id, conn, seen=[]): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Replace the mutable default accumulator. The default Proposed fix-def build_report(tenant_id, conn, seen=[]):
+def build_report(tenant_id, conn, seen=None):
"""Return the names of products that have fallen below their reorder threshold."""
+ if seen is None:
+ seen = []🧰 Tools🪛 Ruff (0.16.1)[warning] 11-11: Do not use mutable data structures for argument defaults Replace with (B006) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| """Return the names of products that have fallen below their reorder threshold.""" | ||
| raise NotImplementedError("build_report is implemented in the inventory PR") | ||
| cursor = conn.cursor() | ||
| cursor.execute( | ||
| f"SELECT id, name, stock FROM products WHERE tenant_id = '{tenant_id}'" | ||
| ) | ||
|
Comment on lines
+14
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Identify the connection driver and all build_report callers.
rg -n -C 4 'build_report\s*\(|sqlite3\.connect|psycopg|connect\s*\(' .Repository: coderabbitai/testing2-carrotstock Length of output: 816 Injection (CWE-89): Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') Bind 🧰 Tools🪛 OpenGrep (1.26.0)[ERROR] 14-16: SQL query built via f-string passed to execute()/executemany(). Use parameterized queries with placeholders instead. (coderabbit.sql-injection.python-fstring-execute) 🪛 Ruff (0.16.1)[error] 15-15: Possible SQL injection vector through string-based query construction (S608) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| low = [] | ||
| for product_id, name, stock in cursor.fetchall(): | ||
| try: | ||
| threshold = lookup_threshold(product_id) | ||
| seen.append(product_id) | ||
| if stock < threshold: | ||
| low.append(name) | ||
| except Exception: | ||
| pass | ||
| return low | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,35 +3,43 @@ import { AppError } from "./errors"; | |||||||||||||||||
|
|
||||||||||||||||||
| const products: Product[] = []; | ||||||||||||||||||
|
|
||||||||||||||||||
| const DISPLAY_SETTINGS: { lowStockLabel?: string } = { lowStockLabel: "LOW" }; | ||||||||||||||||||
|
|
||||||||||||||||||
| /** Register seed products for the in-memory store. */ | ||||||||||||||||||
| export function seedProducts(seed: Product[]): void { | ||||||||||||||||||
| // TODO: implement in the inventory PR | ||||||||||||||||||
| products.push(...seed); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** Look up a single product owned by the given tenant. */ | ||||||||||||||||||
| export function getProduct(tenantId: string, productId: string): Product { | ||||||||||||||||||
| // TODO: implement in the inventory PR | ||||||||||||||||||
| throw new AppError("not_implemented", "getProduct is not implemented yet"); | ||||||||||||||||||
| const product = products.find((p) => p.id === productId); | ||||||||||||||||||
| if (!product) { | ||||||||||||||||||
| throw new AppError("not_found", `Product ${productId} not found`); | ||||||||||||||||||
| } | ||||||||||||||||||
| return product; | ||||||||||||||||||
|
Comment on lines
14
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
printf '%s\n' '--- tracked files ---'
git ls-files
printf '%s\n' '--- relevant source ---'
for f in src/inventory.ts src/orders.ts src/types.ts; do
if [ -f "$f" ]; then
echo "### $f"
cat -n "$f"
fi
done
printf '%s\n' '--- callers and entry points ---'
rg -n --glob '!node_modules' --glob '!dist' --glob '!build' \
'createOrder|validateOrder|getProduct|fetchProducts|tenantId' .Repository: coderabbitai/testing2-carrotstock Length of output: 6744 Cross-tenant Authorization Bypass (CWE-639): Authorization Bypass Through User-Controlled Key (IDOR) Reachability: Internal · Exploitability: Theoretical Reachability pathEnforce tenant ownership during product lookup.
🤖 Prompt for AI Agents |
||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** Stock available to sell: on-hand minus already-reserved. */ | ||||||||||||||||||
| export function availableStock(product: Product): number { | ||||||||||||||||||
| // TODO: implement in the inventory PR | ||||||||||||||||||
| return 0; | ||||||||||||||||||
| return product.stock - product.reserved; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** True when a product has dropped to or below its reorder threshold. */ | ||||||||||||||||||
| export function checkReorder(product: Product): boolean { | ||||||||||||||||||
| // TODO: implement in the inventory PR | ||||||||||||||||||
| return false; | ||||||||||||||||||
| return availableStock(product) < product.reorderThreshold; | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
28
to
30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Include the reorder threshold in the low-stock condition. Line 29 returns - return availableStock(product) < product.reorderThreshold;
+ return availableStock(product) <= product.reorderThreshold;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
|
|
||||||||||||||||||
| /** Reserve stock for a placed order. */ | ||||||||||||||||||
| export function reserveStock(product: Product, quantity: number): void { | ||||||||||||||||||
| // TODO: implement in the inventory PR | ||||||||||||||||||
| product.reserved += quantity; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** Adjust on-hand stock by a (positive or negative) delta. */ | ||||||||||||||||||
| export function adjustStock(product: Product, delta: number): void { | ||||||||||||||||||
| // TODO: implement in the inventory PR | ||||||||||||||||||
| product.stock += delta; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** UI label shown for a product that has reached its reorder point. */ | ||||||||||||||||||
| export function lowStockLabel(): string { | ||||||||||||||||||
| return DISPLAY_SETTINGS.lowStockLabel!; | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,7 +4,15 @@ import { getProduct, reserveStock } from "./inventory"; | |||||||||||||||||
|
|
||||||||||||||||||
| /** Validate that an order can be placed for the tenant. */ | ||||||||||||||||||
| export function validateOrder(tenantId: string, items: OrderItem[]): void { | ||||||||||||||||||
| // TODO: implement in the inventory PR | ||||||||||||||||||
| if (items.length === 0) { | ||||||||||||||||||
| throw new Error("An order must contain at least one item"); | ||||||||||||||||||
| } | ||||||||||||||||||
| for (const item of items) { | ||||||||||||||||||
| const product = getProduct(tenantId, item.productId); | ||||||||||||||||||
| if (item.quantity > product.stock) { | ||||||||||||||||||
| throw new AppError("insufficient_stock", `Not enough ${product.name} in stock`); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+10
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Validate aggregate quantities against available stock. Line 12 ignores 🤖 Prompt for AI Agents |
||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** Create and place an order, reserving stock for each line item. */ | ||||||||||||||||||
|
|
@@ -13,12 +21,25 @@ export function createOrder( | |||||||||||||||||
| items: OrderItem[], | ||||||||||||||||||
| discountCode?: string, | ||||||||||||||||||
| ): Order { | ||||||||||||||||||
| // TODO: implement in the inventory PR | ||||||||||||||||||
| throw new AppError("not_implemented", "createOrder is not implemented yet"); | ||||||||||||||||||
| validateOrder(tenantId, items); | ||||||||||||||||||
| for (const item of items) { | ||||||||||||||||||
| const product = getProduct(tenantId, item.productId); | ||||||||||||||||||
| reserveStock(product, item.quantity); | ||||||||||||||||||
| } | ||||||||||||||||||
| return { | ||||||||||||||||||
| id: `ord_${Math.floor(Math.random() * 1e9)}`, | ||||||||||||||||||
| tenantId, | ||||||||||||||||||
| items, | ||||||||||||||||||
| status: "placed", | ||||||||||||||||||
| discountCode, | ||||||||||||||||||
| }; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** Human-readable description of the discount applied to an order. */ | ||||||||||||||||||
| export function describeDiscount(order: Order): string { | ||||||||||||||||||
| // TODO: implement in the inventory PR | ||||||||||||||||||
| return ""; | ||||||||||||||||||
| return order.discountCode!.trim().toUpperCase(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
39
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Handle orders without a discount code.
export function describeDiscount(order: Order): string {
- return order.discountCode!.trim().toUpperCase();
+ return order.discountCode?.trim().toUpperCase() ?? "";
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
|
|
||||||||||||||||||
| export function summarizeOrder(order: Order) { | ||||||||||||||||||
| return `${order.items.length} line item(s) for tenant ${order.tenantId}`; | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,14 +11,35 @@ export interface PriceLine { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| quantity: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const LEGACY_ENTERPRISE_DISCOUNT = 0.17; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Discount rate (0–1) applied to a tenant's order. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function enterpriseDiscount(tenant: Tenant): number { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO: implement in the inventory PR | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (tenant.tier === "enterprise" && tenant.grandfathered) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return LEGACY_ENTERPRISE_DISCOUNT; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (tenant.tier === "enterprise") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 0.1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Price an order: subtotal, discount, and final total, all in cents. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function priceOrder(tenant: Tenant, lines: PriceLine[]): PricedOrder { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO: implement in the inventory PR | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { subtotal: 0, discount: 0, total: 0 }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let subtotalDollars = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const line of lines) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| subtotalDollars += (line.product.unitPrice / 100) * line.quantity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const discount = enterpriseDiscount(tenant); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const totalDollars = subtotalDollars - subtotalDollars * discount; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const breakdown: any = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| subtotal: subtotalDollars * 100, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| discount, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| total: totalDollars * 100, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return breakdown; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
27
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Calculate and round in cents. Lines 31 and 34 use floating-point dollar values. A price such as Proposed fix export function priceOrder(tenant: Tenant, lines: PriceLine[]): PricedOrder {
- let subtotalDollars = 0;
+ let subtotal = 0;
for (const line of lines) {
- subtotalDollars += (line.product.unitPrice / 100) * line.quantity;
+ subtotal += line.product.unitPrice * line.quantity;
}
const discount = enterpriseDiscount(tenant);
- const totalDollars = subtotalDollars - subtotalDollars * discount;
- const breakdown: any = {
- subtotal: subtotalDollars * 100,
+ const total = Math.round(subtotal * (1 - discount));
+ return {
+ subtotal,
discount,
- total: totalDollars * 100,
+ total,
};
- return breakdown;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function formatRate(rate: number) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return `${(rate * 100).toFixed(0)}%`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,4 +1,6 @@ | ||||||
| import { useEffect, useState } from "react"; | ||||||
| import { Product } from "../src/types"; | ||||||
| import { availableStock, checkReorder } from "../src/inventory"; | ||||||
|
|
||||||
| interface InventoryDashboardProps { | ||||||
| tenantId: string; | ||||||
|
|
@@ -7,6 +9,34 @@ interface InventoryDashboardProps { | |||||
|
|
||||||
| /** Dashboard table of a tenant's carrot inventory with reorder badges. */ | ||||||
| export function InventoryDashboard(props: InventoryDashboardProps): JSX.Element { | ||||||
| // TODO: implement in the inventory PR | ||||||
| return <div>Loading inventory…</div>; | ||||||
| const [products, setProducts] = useState<Product[]>([]); | ||||||
|
|
||||||
| useEffect(() => { | ||||||
| props.fetchProducts(props.tenantId).then(setProducts); | ||||||
| }, [props.tenantId]); | ||||||
|
Comment on lines
+14
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(InventoryDashboard\.tsx|.*Inventory.*|.*Product.*)\b' | head -80
printf '%s\n' '--- component outline ---'
ast-grep outline web/InventoryDashboard.tsx || true
printf '%s\n' '--- component source ---'
cat -n web/InventoryDashboard.tsx
printf '%s\n' '--- fetchProducts definitions and usages ---'
rg -n -C 4 'fetchProducts|InventoryDashboard' --glob '!node_modules/**' --glob '!dist/**' .Repository: coderabbitai/testing2-carrotstock Length of output: 3873 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- related source and tests ---'
git ls-files | sed -n '1,160p'
printf '%s\n' '--- product type ---'
rg -n -C 5 'interface Product|type Product' src web
printf '%s\n' '--- fetchProducts references outside the component ---'
rg -n -C 3 'fetchProducts' --glob '!web/InventoryDashboard.tsx' --glob '!node_modules/**' .
printf '%s\n' '--- project configuration and tests ---'
for f in package.json tsconfig.json vite.config.* jest.config.* vitest.config.*; do
[ -f "$f" ] && { echo "--- $f"; cat -n "$f"; }
done
printf '%s\n' '--- deterministic race model ---'
python3 - <<'PY'
from dataclasses import dataclass
`@dataclass`
class Request:
tenant: str
result: list
state = []
current_tenant = "tenant-a"
old = Request("tenant-a", ["A"])
new = Request("tenant-b", ["B"])
# This models the effect's callback: every settled request calls setProducts,
# while the effect only starts a request when tenantId changes.
state = new.result
state = old.result
assert state == ["A"]
assert current_tenant == "tenant-b"
print("obsolete tenant-a response overwrites tenant-b state: yes")
PYRepository: coderabbitai/testing2-carrotstock Length of output: 1034 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- related source and tests ---'
git ls-files | sed -n '1,160p'
printf '%s\n' '--- product type ---'
rg -n -C 5 'interface Product|type Product' src web
printf '%s\n' '--- fetchProducts references outside the component ---'
rg -n -C 3 'fetchProducts' --glob '!web/InventoryDashboard.tsx' --glob '!node_modules/**' .
printf '%s\n' '--- project configuration and tests ---'
for f in package.json tsconfig.json vite.config.* jest.config.* vitest.config.*; do
[ -f "$f" ] && { echo "--- $f"; cat -n "$f"; }
done
printf '%s\n' '--- deterministic race model ---'
python3 - <<'PY'
from dataclasses import dataclass
`@dataclass`
class Request:
tenant: str
result: list
state = []
current_tenant = "tenant-a"
old = Request("tenant-a", ["A"])
new = Request("tenant-b", ["B"])
state = new.result
state = old.result
assert state == ["A"]
assert current_tenant == "tenant-b"
print("obsolete tenant-a response overwrites tenant-b state: yes")
PYRepository: coderabbitai/testing2-carrotstock Length of output: 1034 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- package and compiler configuration ---'
cat -n package.json
cat -n tsconfig.json
printf '%s\n' '--- complete Product definition ---'
sed -n '1,80p' src/types.ts
printf '%s\n' '--- deterministic stale-response check ---'
python3 - <<'PY'
class ComponentModel:
def __init__(self):
self.products = []
self.tenant_id = None
def start_effect(self, tenant_id, fetch):
self.tenant_id = tenant_id
# Mirrors the component callback: no tenant check occurs on settlement.
return lambda: self._set_products(fetch())
def _set_products(self, products):
self.products = products
model = ComponentModel()
resolve_a = model.start_effect("tenant-a", lambda: ["tenant-a-product"])
resolve_b = model.start_effect("tenant-b", lambda: ["tenant-b-product"])
resolve_b()
assert model.tenant_id == "tenant-b"
assert model.products == ["tenant-b-product"]
resolve_a()
assert model.tenant_id == "tenant-b"
assert model.products == ["tenant-a-product"]
print("stale response overwrites current tenant state: yes")
PYRepository: coderabbitai/testing2-carrotstock Length of output: 2293 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- package and compiler configuration ---'
cat -n package.json
cat -n tsconfig.json
printf '%s\n' '--- complete Product definition ---'
sed -n '1,80p' src/types.ts
printf '%s\n' '--- deterministic stale-response check ---'
python3 - <<'PY'
class ComponentModel:
def __init__(self):
self.products = []
self.tenant_id = None
def start_effect(self, tenant_id, fetch):
self.tenant_id = tenant_id
return lambda: self._set_products(fetch())
def _set_products(self, products):
self.products = products
model = ComponentModel()
resolve_a = model.start_effect("tenant-a", lambda: ["tenant-a-product"])
resolve_b = model.start_effect("tenant-b", lambda: ["tenant-b-product"])
resolve_b()
assert model.tenant_id == "tenant-b"
assert model.products == ["tenant-b-product"]
resolve_a()
assert model.tenant_id == "tenant-b"
assert model.products == ["tenant-a-product"]
print("stale response overwrites current tenant state: yes")
PYRepository: coderabbitai/testing2-carrotstock Length of output: 2293 Ignore obsolete product responses. If 🤖 Prompt for AI Agents |
||||||
|
|
||||||
| return ( | ||||||
| <div> | ||||||
| <h2>Inventory for {props.tenantId}</h2> | ||||||
| <p>Top variety: {products[0].name}</p> | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Guard the empty product list. Line 21 runs before - <p>Top variety: {products[0].name}</p>
+ <p>Top variety: {products[0]?.name ?? "None"}</p>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| <table> | ||||||
| <thead> | ||||||
| <tr> | ||||||
| <th>Carrot</th> | ||||||
| <th>Available</th> | ||||||
| <th>Status</th> | ||||||
| </tr> | ||||||
| </thead> | ||||||
| <tbody> | ||||||
| {products.map((product, index) => ( | ||||||
| <tr key={index}> | ||||||
| <td dangerouslySetInnerHTML={{ __html: product.name }} /> | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Trace Product.name write paths and fetchProducts implementations.
ast-grep outline src --items all
ast-grep outline web --items all
rg -n -C 3 --type ts --type tsx \
'fetchProducts|seedProducts|name\s*:|dangerouslySetInnerHTML' src webRepository: coderabbitai/testing2-carrotstock Length of output: 1052 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- src/inventory.ts ---'
cat -n src/inventory.ts
printf '%s\n' '--- web/InventoryDashboard.tsx ---'
cat -n web/InventoryDashboard.tsx
printf '%s\n' '--- Product.name references ---'
rg -n -C 4 'seedProducts|fetchProducts|Product|name:|dangerouslySetInnerHTML' src web -g '*.ts' -g '*.tsx'
printf '%s\n' '--- package/config files ---'
git ls-files | rg '(^|/)(package\.json|tsconfig[^/]*\.json|vite\.config\..*|webpack\.config\..*)$' | xargs -r -n1 sh -c 'echo "--- $0"; cat -n "$0"'Repository: coderabbitai/testing2-carrotstock Length of output: 11160 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- src/inventory.ts ---'
cat -n src/inventory.ts
printf '%s\n' '--- web/InventoryDashboard.tsx ---'
cat -n web/InventoryDashboard.tsx
printf '%s\n' '--- Product.name references ---'
rg -n -C 4 'seedProducts|fetchProducts|Product|name:|dangerouslySetInnerHTML' src web -g '*.ts' -g '*.tsx'Repository: coderabbitai/testing2-carrotstock Length of output: 10058 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
dashboard = Path("web/InventoryDashboard.tsx").read_text()
inventory = Path("src/inventory.ts").read_text()
sink = re.search(
r"<td\s+dangerouslySetInnerHTML=\{\{\s*__html:\s*product\.name\s*\}\}\s*/>",
dashboard,
)
react_child = re.search(r"<td>\s*\{product\.name\}\s*</td>", dashboard)
sanitizers = re.findall(r"(?:sanitize|DOMPurify|escape|purify)", dashboard + inventory, re.I)
fetch_impls = re.findall(r"(?:function\s+fetchProducts|const\s+fetchProducts|fetchProducts\s*=)", dashboard + inventory)
print({
"raw_product_name_sink": bool(sink),
"escaped_react_child_present": bool(react_child),
"sanitizer_references_in_bounded_files": sanitizers,
"fetchProducts_implementations_in_bounded_files": fetch_impls,
"seedProducts_forwards_input_without_name_transform": bool(
re.search(r"products\.push\(\.\.\.seed\)", inventory)
),
})
PYRepository: coderabbitai/testing2-carrotstock Length of output: 400 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
dashboard = Path("web/InventoryDashboard.tsx").read_text()
inventory = Path("src/inventory.ts").read_text()
print({
"raw_product_name_sink": bool(re.search(
r"<td\s+dangerouslySetInnerHTML=\{\{\s*__html:\s*product\.name\s*\}\}\s*/>",
dashboard,
)),
"sanitizer_references": re.findall(
r"(?:sanitize|DOMPurify|escape|purify)", dashboard + inventory, re.I
),
"fetchProducts_implementations": re.findall(
r"(?:function\s+fetchProducts|const\s+fetchProducts|fetchProducts\s*=)",
dashboard + inventory,
),
"seed_forwards_input_unchanged": "products.push(...seed)" in inventory,
})
PYRepository: coderabbitai/testing2-carrotstock Length of output: 307 Cross-site Scripting (CWE-79): Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') Reachability pathRender product names as text, not HTML.
Suggested change- <td dangerouslySetInnerHTML={{ __html: product.name }} />
+ <td>{product.name}</td>📝 Committable suggestion
Suggested change
🧰 Tools🪛 React Doctor (0.9.3)[error] 33-33: Render trusted content as React children so attacker-controlled HTML cannot run in users' browsers. (no-danger) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||
| <td>{availableStock(product)}</td> | ||||||
| <td>{checkReorder(product) ? "Reorder" : "OK"}</td> | ||||||
| </tr> | ||||||
| ))} | ||||||
| </tbody> | ||||||
| </table> | ||||||
| </div> | ||||||
| ); | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,3 +1,4 @@ | ||||||||||||||||||||||
| import { useState } from "react"; | ||||||||||||||||||||||
| import { OrderItem } from "../src/types"; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| interface OrderFormProps { | ||||||||||||||||||||||
|
|
@@ -7,6 +8,25 @@ interface OrderFormProps { | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** Form for composing and submitting a new carrot order. */ | ||||||||||||||||||||||
| export function OrderForm(props: OrderFormProps): JSX.Element { | ||||||||||||||||||||||
| // TODO: implement in the inventory PR | ||||||||||||||||||||||
| return <form />; | ||||||||||||||||||||||
| const [items, setItems] = useState<OrderItem[]>([]); | ||||||||||||||||||||||
| const [discountCode, setDiscountCode] = useState(""); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| function addLine() { | ||||||||||||||||||||||
| items.push({ productId: "prod_demo", quantity: 1 }); | ||||||||||||||||||||||
| setItems(items); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+14
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
file="web/OrderForm.tsx"
printf '%s\n' '--- file outline ---'
ast-grep outline "$file" || true
printf '%s\n' '--- relevant source ---'
cat -n "$file"
printf '%s\n' '--- related state and form usage ---'
rg -n -C 4 'useState|addLine|setItems|onSubmit|<form|items' "$file"Repository: coderabbitai/testing2-carrotstock Length of output: 2552 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- package and React versions ---'
rg -n -C 2 '"react"|"`@types/react`"|react-dom' package.json package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null || true
printf '%s\n' '--- OrderForm consumers and tests ---'
rg -n -C 5 'OrderForm|Place order|Add carrot|prod_demo' . \
-g '!node_modules' -g '!dist' -g '!build' \
-g '*.{ts,tsx,js,jsx,json,md}' || true
printf '%s\n' '--- OrderItem definition ---'
rg -n -C 5 'interface OrderItem|type OrderItem' src webRepository: coderabbitai/testing2-carrotstock Length of output: 3578 🏁 Script executed: #!/bin/bash
set -eu
python3 - <<'PY'
# Read-only behavioral probe for the state transition in web/OrderForm.tsx.
# It models React's identity-based bailout: the mutation changes the array
# contents, but the state value supplied to setItems remains the same object.
items = []
rendered_length = len(items)
items.append({"productId": "prod_demo", "quantity": 1})
next_state = items
state_update_bails_out = next_state is items
print({
"state_update_bails_out_by_identity": state_update_bails_out,
"mutated_array_length": len(items),
"last_rendered_length_if_bailed_out": rendered_length,
})
PYRepository: coderabbitai/testing2-carrotstock Length of output: 284 🌐 Web query:
💡 Result: In React, state is treated as read-only (immutable) [1][2]. When you update state using a setter function (such as Citations:
Replace the item array when adding a line.
Suggested change function addLine() {
- items.push({ productId: "prod_demo", quantity: 1 });
- setItems(items);
+ setItems((current) => [
+ ...current,
+ { productId: "prod_demo", quantity: 1 },
+ ]);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return ( | ||||||||||||||||||||||
| <form onSubmit={() => props.onSubmit(items, discountCode)}> | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- file ---'
cat -n web/OrderForm.tsx
printf '%s\n' '--- form and onSubmit references ---'
rg -n -C 3 'onSubmit|<form|OrderForm' --glob '!node_modules' --glob '!dist' --glob '!build' .Repository: coderabbitai/testing2-carrotstock Length of output: 2873 🏁 Script executed: #!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
import re
source = Path("web/OrderForm.tsx").read_text()
form = re.search(r"<form\b([^>]*)>", source, re.S)
handler = re.search(r'onSubmit=\{([^}]*)\}', source, re.S)
submit_button = re.search(r'<button\s+type="submit">', source)
print("form_found:", bool(form))
print("submit_button_found:", bool(submit_button))
print("handler_found:", bool(handler))
print("handler_calls_preventDefault:", bool(handler and "preventDefault" in handler.group(1)))
print("handler_calls_props_onSubmit:", bool(handler and "props.onSubmit(items, discountCode)" in handler.group(1)))
PYRepository: coderabbitai/testing2-carrotstock Length of output: 305 Prevent native form submission. The submit handler ignores the event, so the browser can reload the page after 🤖 Prompt for AI Agents |
||||||||||||||||||||||
| <input | ||||||||||||||||||||||
| placeholder="Discount code" | ||||||||||||||||||||||
| value={discountCode} | ||||||||||||||||||||||
| onChange={(e) => setDiscountCode(e.target.value)} | ||||||||||||||||||||||
| /> | ||||||||||||||||||||||
| <button type="button" onClick={addLine}> | ||||||||||||||||||||||
| Add carrot ({items.length}) | ||||||||||||||||||||||
| </button> | ||||||||||||||||||||||
| <button type="submit">Place order</button> | ||||||||||||||||||||||
| </form> | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: coderabbitai/testing2-carrotstock
Length of output: 1379
🏁 Script executed:
Repository: coderabbitai/testing2-carrotstock
Length of output: 1340
Sensitive Data Exposure (CWE-532): Insertion of Sensitive Information into Log File
Exploitability: Moderate
Remove
BILLING_API_KEYfrom logs and source control. The credential is hard-coded inservices/pricing_sync.pyand included in the info-level log. Load it from a secret manager and rotate the exposed credential.🤖 Prompt for AI Agents