@vibe-interviewing/scenarios 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +5 -3
- package/package.json +7 -5
- package/patch-data-loss/scenario.yaml +127 -0
- package/registry.yaml +17 -5
- package/storage-adapter-refactor/scenario.yaml +84 -0
- package/webhook-notifications/scenario.yaml +128 -0
- package/rate-limiter-boundary/scenario.yaml +0 -71
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vibe-interviewing contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -4,9 +4,11 @@ Built-in interview scenarios for [vibe-interviewing](https://github.com/cpaczek/
|
|
|
4
4
|
|
|
5
5
|
## Available Scenarios
|
|
6
6
|
|
|
7
|
-
| Scenario
|
|
8
|
-
|
|
|
9
|
-
| `
|
|
7
|
+
| Scenario | Type | Difficulty | Time | Description |
|
|
8
|
+
| -------------------------- | -------- | ---------- | ------- | ------------------------------------------------------- |
|
|
9
|
+
| `patch-data-loss` | Debug | Hard | ~30-45m | PATCH requests silently drop fields from records |
|
|
10
|
+
| `storage-adapter-refactor` | Refactor | Medium | ~45-60m | Refactor tightly-coupled storage for pluggable backends |
|
|
11
|
+
| `webhook-notifications` | Feature | Hard | ~45-60m | Build a webhook notification system for a REST API |
|
|
10
12
|
|
|
11
13
|
## Creating Custom Scenarios
|
|
12
14
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibe-interviewing/scenarios",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Built-in interview scenarios for vibe-interviewing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -9,13 +9,15 @@
|
|
|
9
9
|
"index.js",
|
|
10
10
|
"index.d.ts",
|
|
11
11
|
"registry.yaml",
|
|
12
|
-
"
|
|
12
|
+
"patch-data-loss/",
|
|
13
|
+
"storage-adapter-refactor/",
|
|
14
|
+
"webhook-notifications/"
|
|
13
15
|
],
|
|
14
|
-
"scripts": {},
|
|
15
16
|
"license": "MIT",
|
|
16
17
|
"repository": {
|
|
17
18
|
"type": "git",
|
|
18
19
|
"url": "https://github.com/cpaczek/vibe-interviewing.git",
|
|
19
20
|
"directory": "packages/scenarios"
|
|
20
|
-
}
|
|
21
|
-
}
|
|
21
|
+
},
|
|
22
|
+
"scripts": {}
|
|
23
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
name: patch-data-loss
|
|
2
|
+
description: 'PATCH requests silently drop fields from records'
|
|
3
|
+
type: debug
|
|
4
|
+
difficulty: hard
|
|
5
|
+
estimated_time: '30-45m'
|
|
6
|
+
tags:
|
|
7
|
+
- node
|
|
8
|
+
- rest-api
|
|
9
|
+
- json-server
|
|
10
|
+
- data-integrity
|
|
11
|
+
|
|
12
|
+
repo: 'https://github.com/typicode/json-server'
|
|
13
|
+
commit: '6ad7562b6aba175bec616d342e1319e8dbbad1af'
|
|
14
|
+
|
|
15
|
+
setup:
|
|
16
|
+
- 'npm install'
|
|
17
|
+
|
|
18
|
+
patch:
|
|
19
|
+
# Replace the fixture data with richer records so field loss is observable
|
|
20
|
+
- file: 'fixtures/db.json'
|
|
21
|
+
find: |
|
|
22
|
+
{
|
|
23
|
+
"posts": [
|
|
24
|
+
{ "id": "1", "title": "a title" },
|
|
25
|
+
{ "id": "2", "title": "another title" }
|
|
26
|
+
],
|
|
27
|
+
"comments": [
|
|
28
|
+
{ "id": "1", "text": "a comment about post 1", "postId": "1" },
|
|
29
|
+
{ "id": "2", "text": "another comment about post 1", "postId": "1" }
|
|
30
|
+
],
|
|
31
|
+
"profile": {
|
|
32
|
+
"name": "typicode"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
replace: |
|
|
36
|
+
{
|
|
37
|
+
"products": [
|
|
38
|
+
{ "id": "1", "name": "Wireless Headphones", "price": 79.99, "category": "electronics", "description": "Noise-cancelling over-ear headphones", "inStock": true, "rating": 4.5 },
|
|
39
|
+
{ "id": "2", "name": "Running Shoes", "price": 129.99, "category": "footwear", "description": "Lightweight trail running shoes", "inStock": true, "rating": 4.2 },
|
|
40
|
+
{ "id": "3", "name": "Coffee Maker", "price": 49.99, "category": "kitchen", "description": "12-cup programmable coffee maker", "inStock": false, "rating": 3.8 },
|
|
41
|
+
{ "id": "4", "name": "Backpack", "price": 89.99, "category": "accessories", "description": "Water-resistant laptop backpack", "inStock": true, "rating": 4.7 }
|
|
42
|
+
],
|
|
43
|
+
"orders": [
|
|
44
|
+
{ "id": "1", "productId": "1", "quantity": 2, "status": "shipped", "customerEmail": "alice@example.com" },
|
|
45
|
+
{ "id": "2", "productId": "3", "quantity": 1, "status": "pending", "customerEmail": "bob@example.com" }
|
|
46
|
+
],
|
|
47
|
+
"settings": {
|
|
48
|
+
"storeName": "Demo Shop",
|
|
49
|
+
"currency": "USD",
|
|
50
|
+
"taxRate": 0.08
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Break PATCH merge for array resources (collections like /products/:id)
|
|
55
|
+
# Remove the spread of existing item, making PATCH behave like PUT
|
|
56
|
+
- file: 'src/service.ts'
|
|
57
|
+
find: 'const nextItem = isPatch ? { ...item, ...body, id } : { ...body, id }'
|
|
58
|
+
replace: 'const nextItem = { ...body, id }'
|
|
59
|
+
|
|
60
|
+
delete_files:
|
|
61
|
+
- 'src/*.test.ts'
|
|
62
|
+
|
|
63
|
+
briefing: |
|
|
64
|
+
We've got a P1 from the support team. Multiple customers are reporting data loss
|
|
65
|
+
in our product catalog API. They say they'll update a single field on a product —
|
|
66
|
+
like changing the price — and when they fetch it back, all the other fields are gone.
|
|
67
|
+
Just the field they sent plus the ID.
|
|
68
|
+
|
|
69
|
+
It's not happening on every endpoint, and some customers aren't seeing it at all.
|
|
70
|
+
Nobody's sure when this started but it's been in the queue for a few days now.
|
|
71
|
+
|
|
72
|
+
The API is built on json-server. You can start it locally with:
|
|
73
|
+
|
|
74
|
+
node --experimental-strip-types src/bin.ts fixtures/db.json
|
|
75
|
+
|
|
76
|
+
Then hit it with curl on port 3000. The data is in fixtures/db.json.
|
|
77
|
+
Start by reproducing the issue, then trace through the code to find the root cause.
|
|
78
|
+
|
|
79
|
+
ai_rules:
|
|
80
|
+
role: |
|
|
81
|
+
You are a senior engineer helping a candidate debug a data loss issue
|
|
82
|
+
in a REST API built on json-server. Act as a thoughtful colleague —
|
|
83
|
+
available for questions but you don't volunteer the answer.
|
|
84
|
+
rules:
|
|
85
|
+
- 'Never reveal the exact location or nature of the bug directly'
|
|
86
|
+
- 'If asked, confirm whether the candidate is looking in the right area'
|
|
87
|
+
- 'Encourage the candidate to reproduce the issue first with curl requests'
|
|
88
|
+
- 'If the candidate is stuck, suggest they compare what happens with PUT vs PATCH'
|
|
89
|
+
- 'If stuck for more than 15 minutes, hint that the issue might be in how updates merge with existing data'
|
|
90
|
+
- 'Praise good debugging methodology (reproducing first, reading source, adding logging)'
|
|
91
|
+
knowledge: |
|
|
92
|
+
The bug is in src/service.ts in the #updateOrPatchById method.
|
|
93
|
+
The original code was:
|
|
94
|
+
const nextItem = isPatch ? { ...item, ...body, id } : { ...body, id }
|
|
95
|
+
It was changed to:
|
|
96
|
+
const nextItem = { ...body, id }
|
|
97
|
+
This removes the ternary that differentiated PATCH from PUT. Now PATCH
|
|
98
|
+
behaves like PUT — it replaces the entire object with just the body fields
|
|
99
|
+
plus the ID, instead of merging the body into the existing object.
|
|
100
|
+
The fix is to restore the ternary:
|
|
101
|
+
const nextItem = isPatch ? { ...item, ...body, id } : { ...body, id }
|
|
102
|
+
Note: the non-array #updateOrPatch method (for singular resources like /settings)
|
|
103
|
+
is NOT affected — it still correctly merges with { ...item, ...body }.
|
|
104
|
+
|
|
105
|
+
solution: |
|
|
106
|
+
In src/service.ts, in the #updateOrPatchById method (around line 178), change:
|
|
107
|
+
const nextItem = { ...body, id }
|
|
108
|
+
back to:
|
|
109
|
+
const nextItem = isPatch ? { ...item, ...body, id } : { ...body, id }
|
|
110
|
+
|
|
111
|
+
The bug made PATCH identical to PUT for collection items. PATCH should merge
|
|
112
|
+
the request body into the existing item (preserving fields not in the body),
|
|
113
|
+
while PUT should replace the entire item. The fix restores the spread of the
|
|
114
|
+
existing item when isPatch is true.
|
|
115
|
+
|
|
116
|
+
evaluation:
|
|
117
|
+
criteria:
|
|
118
|
+
- 'Reproduced the data loss issue with curl requests'
|
|
119
|
+
- 'Identified that PATCH is dropping fields not included in the request body'
|
|
120
|
+
- 'Located the bug in the service layer (src/service.ts)'
|
|
121
|
+
- 'Understood the difference between PATCH (merge) and PUT (replace) semantics'
|
|
122
|
+
- 'Applied the correct fix restoring the merge behavior'
|
|
123
|
+
- 'Verified the fix works by re-testing with curl'
|
|
124
|
+
- 'Used AI effectively as a debugging partner'
|
|
125
|
+
expected_fix: 'Restore the ternary in #updateOrPatchById: isPatch ? { ...item, ...body, id } : { ...body, id }'
|
|
126
|
+
|
|
127
|
+
license: MIT
|
package/registry.yaml
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
scenarios:
|
|
2
|
-
- name:
|
|
3
|
-
description:
|
|
4
|
-
difficulty:
|
|
2
|
+
- name: patch-data-loss
|
|
3
|
+
description: 'PATCH requests silently drop fields from records'
|
|
4
|
+
difficulty: hard
|
|
5
5
|
estimated_time: '30-45m'
|
|
6
|
-
repo: 'https://github.com/
|
|
7
|
-
commit: '
|
|
6
|
+
repo: 'https://github.com/typicode/json-server'
|
|
7
|
+
commit: '6ad7562b6aba175bec616d342e1319e8dbbad1af'
|
|
8
|
+
- name: storage-adapter-refactor
|
|
9
|
+
description: 'Refactor tightly-coupled storage layer to support pluggable backends'
|
|
10
|
+
difficulty: medium
|
|
11
|
+
estimated_time: '45-60m'
|
|
12
|
+
repo: 'https://github.com/typicode/json-server'
|
|
13
|
+
commit: '6ad7562b6aba175bec616d342e1319e8dbbad1af'
|
|
14
|
+
- name: webhook-notifications
|
|
15
|
+
description: 'Build a webhook notification system for a REST API'
|
|
16
|
+
difficulty: hard
|
|
17
|
+
estimated_time: '45-60m'
|
|
18
|
+
repo: 'https://github.com/typicode/json-server'
|
|
19
|
+
commit: '6ad7562b6aba175bec616d342e1319e8dbbad1af'
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
name: storage-adapter-refactor
|
|
2
|
+
description: 'Refactor tightly-coupled storage layer to support pluggable backends'
|
|
3
|
+
type: refactor
|
|
4
|
+
difficulty: medium
|
|
5
|
+
estimated_time: '45-60m'
|
|
6
|
+
tags:
|
|
7
|
+
- node
|
|
8
|
+
- rest-api
|
|
9
|
+
- json-server
|
|
10
|
+
- architecture
|
|
11
|
+
- design-patterns
|
|
12
|
+
|
|
13
|
+
repo: 'https://github.com/typicode/json-server'
|
|
14
|
+
commit: '6ad7562b6aba175bec616d342e1319e8dbbad1af'
|
|
15
|
+
|
|
16
|
+
setup:
|
|
17
|
+
- 'npm install'
|
|
18
|
+
|
|
19
|
+
delete_files:
|
|
20
|
+
- 'src/*.test.ts'
|
|
21
|
+
|
|
22
|
+
briefing: |
|
|
23
|
+
We're turning json-server into a more flexible tool. Right now the entire data
|
|
24
|
+
layer is hardcoded to lowdb — the Service class directly imports and uses Low<Data>,
|
|
25
|
+
reads/writes through this.#db.data, and calls this.#db.write(). This coupling means
|
|
26
|
+
anyone who wants to use a different storage backend (in-memory for tests, SQLite for
|
|
27
|
+
persistence, Redis for shared state) has to fork the whole thing.
|
|
28
|
+
|
|
29
|
+
Your job: refactor the data layer so storage is pluggable. Define a clean storage
|
|
30
|
+
interface, wrap the existing lowdb usage in an adapter that implements it, and
|
|
31
|
+
refactor the Service class to depend on the interface instead of lowdb directly.
|
|
32
|
+
Then create an in-memory adapter as a second implementation to prove the
|
|
33
|
+
interface works.
|
|
34
|
+
|
|
35
|
+
The server runs locally:
|
|
36
|
+
|
|
37
|
+
node --experimental-strip-types src/bin.ts fixtures/db.json
|
|
38
|
+
|
|
39
|
+
Make sure the server still works exactly the same with the JSON file adapter
|
|
40
|
+
after your refactoring. You can test with curl on port 3000.
|
|
41
|
+
|
|
42
|
+
ai_rules:
|
|
43
|
+
role: |
|
|
44
|
+
You are a senior engineer helping a candidate refactor a storage layer.
|
|
45
|
+
Guide them on design decisions but don't dictate the architecture.
|
|
46
|
+
rules:
|
|
47
|
+
- 'Let the candidate drive the interface design — there are multiple valid approaches'
|
|
48
|
+
- 'If asked, discuss trade-offs between different abstraction levels'
|
|
49
|
+
- 'Encourage them to keep the interface minimal — only what Service actually needs'
|
|
50
|
+
- 'Suggest they start by identifying all the places Service touches the database'
|
|
51
|
+
- 'If they over-engineer (too many methods, complex generics), gently suggest simplifying'
|
|
52
|
+
- 'Remind them to verify the server still works after each refactoring step'
|
|
53
|
+
knowledge: |
|
|
54
|
+
The Service class in src/service.ts directly depends on Low<Data> from lowdb.
|
|
55
|
+
It accesses data through this.#db.data (a Record<string, Item[] | Item>) and
|
|
56
|
+
persists changes with this.#db.write(). The key coupling points are:
|
|
57
|
+
- Constructor takes Low<Data>
|
|
58
|
+
- #get reads from this.#db.data[name]
|
|
59
|
+
- create/update/patch/destroy all call this.#db.write() after mutations
|
|
60
|
+
- The embed/nullifyForeignKey/deleteDependents functions also access db.data
|
|
61
|
+
|
|
62
|
+
A good refactoring would:
|
|
63
|
+
1. Define a StorageAdapter interface with methods like get(name), set(name, data), write()
|
|
64
|
+
2. Create a LowdbAdapter that wraps the current Low<Data> behavior
|
|
65
|
+
3. Create an InMemoryAdapter that stores data in a plain object
|
|
66
|
+
4. Update Service to accept StorageAdapter instead of Low<Data>
|
|
67
|
+
5. Update app.ts/bin.ts to create the adapter and pass it through
|
|
68
|
+
|
|
69
|
+
The candidate should notice that the helper functions (embed, nullifyForeignKey,
|
|
70
|
+
deleteDependents) also access db.data directly, which is a design decision —
|
|
71
|
+
either move them into Service or make them work through the adapter.
|
|
72
|
+
|
|
73
|
+
evaluation:
|
|
74
|
+
criteria:
|
|
75
|
+
- 'Identified all coupling points between Service and lowdb'
|
|
76
|
+
- 'Designed a clean, minimal storage interface'
|
|
77
|
+
- 'Implemented a lowdb adapter that preserves existing behavior'
|
|
78
|
+
- 'Implemented an in-memory adapter'
|
|
79
|
+
- 'Refactored Service to use the interface instead of lowdb directly'
|
|
80
|
+
- 'Server still works correctly after refactoring (verified with curl)'
|
|
81
|
+
- 'Made thoughtful design decisions about abstraction level'
|
|
82
|
+
- 'Used AI effectively as an architecture partner'
|
|
83
|
+
|
|
84
|
+
license: MIT
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
name: webhook-notifications
|
|
2
|
+
description: 'Build a webhook notification system for a REST API'
|
|
3
|
+
type: feature
|
|
4
|
+
difficulty: hard
|
|
5
|
+
estimated_time: '45-60m'
|
|
6
|
+
tags:
|
|
7
|
+
- node
|
|
8
|
+
- rest-api
|
|
9
|
+
- json-server
|
|
10
|
+
- webhooks
|
|
11
|
+
- event-driven
|
|
12
|
+
|
|
13
|
+
repo: 'https://github.com/typicode/json-server'
|
|
14
|
+
commit: '6ad7562b6aba175bec616d342e1319e8dbbad1af'
|
|
15
|
+
|
|
16
|
+
setup:
|
|
17
|
+
- 'npm install'
|
|
18
|
+
|
|
19
|
+
patch:
|
|
20
|
+
# Replace fixture data with richer records for more interesting webhook payloads
|
|
21
|
+
- file: 'fixtures/db.json'
|
|
22
|
+
find: |
|
|
23
|
+
{
|
|
24
|
+
"posts": [
|
|
25
|
+
{ "id": "1", "title": "a title" },
|
|
26
|
+
{ "id": "2", "title": "another title" }
|
|
27
|
+
],
|
|
28
|
+
"comments": [
|
|
29
|
+
{ "id": "1", "text": "a comment about post 1", "postId": "1" },
|
|
30
|
+
{ "id": "2", "text": "another comment about post 1", "postId": "1" }
|
|
31
|
+
],
|
|
32
|
+
"profile": {
|
|
33
|
+
"name": "typicode"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
replace: |
|
|
37
|
+
{
|
|
38
|
+
"products": [
|
|
39
|
+
{ "id": "1", "name": "Wireless Headphones", "price": 79.99, "category": "electronics", "inStock": true },
|
|
40
|
+
{ "id": "2", "name": "Running Shoes", "price": 129.99, "category": "footwear", "inStock": true },
|
|
41
|
+
{ "id": "3", "name": "Coffee Maker", "price": 49.99, "category": "kitchen", "inStock": false }
|
|
42
|
+
],
|
|
43
|
+
"orders": [
|
|
44
|
+
{ "id": "1", "productId": "1", "quantity": 2, "status": "shipped" },
|
|
45
|
+
{ "id": "2", "productId": "3", "quantity": 1, "status": "pending" }
|
|
46
|
+
],
|
|
47
|
+
"webhooks": []
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
delete_files:
|
|
51
|
+
- 'src/*.test.ts'
|
|
52
|
+
|
|
53
|
+
briefing: |
|
|
54
|
+
We need to add webhook support to our product catalog API so that external
|
|
55
|
+
integrations can react to changes in real time. Right now if a third-party system
|
|
56
|
+
wants to know when a product is updated or an order is created, they have to poll.
|
|
57
|
+
|
|
58
|
+
Build a webhook notification system:
|
|
59
|
+
|
|
60
|
+
1. Clients register webhooks by POSTing to /webhooks with a URL and optional
|
|
61
|
+
event filter (e.g., only notify on "products" changes)
|
|
62
|
+
2. When any resource is created, updated, or deleted, the server sends a POST
|
|
63
|
+
request to all matching registered webhook URLs
|
|
64
|
+
3. Clients can list their webhooks (GET /webhooks) and delete them (DELETE /webhooks/:id)
|
|
65
|
+
|
|
66
|
+
The webhook payload should include what happened: the event type (create/update/delete),
|
|
67
|
+
which resource, and the affected data.
|
|
68
|
+
|
|
69
|
+
The server runs locally:
|
|
70
|
+
|
|
71
|
+
node --experimental-strip-types src/bin.ts fixtures/db.json
|
|
72
|
+
|
|
73
|
+
Test with curl on port 3000. You can use a simple listener (like a second
|
|
74
|
+
HTTP server or a request-catching service) to verify webhook delivery.
|
|
75
|
+
|
|
76
|
+
acceptance_criteria:
|
|
77
|
+
- 'POST /webhooks with { "url": "...", "resource": "products" } registers a webhook'
|
|
78
|
+
- 'POST /webhooks with { "url": "..." } (no resource filter) registers for all events'
|
|
79
|
+
- 'GET /webhooks returns all registered webhooks'
|
|
80
|
+
- 'DELETE /webhooks/:id removes a webhook'
|
|
81
|
+
- 'When a resource is created (POST /products), matching webhooks receive a notification'
|
|
82
|
+
- 'When a resource is updated (PUT or PATCH /products/:id), matching webhooks receive a notification'
|
|
83
|
+
- 'When a resource is deleted (DELETE /products/:id), matching webhooks receive a notification'
|
|
84
|
+
- 'Webhook payload includes: event type, resource name, affected data, and timestamp'
|
|
85
|
+
- 'Failed webhook deliveries do not crash the server or block the API response'
|
|
86
|
+
- 'Existing CRUD endpoints still work correctly'
|
|
87
|
+
|
|
88
|
+
ai_rules:
|
|
89
|
+
role: |
|
|
90
|
+
You are a senior engineer helping a candidate build a webhook notification
|
|
91
|
+
system. Guide their design decisions but let them drive the implementation.
|
|
92
|
+
rules:
|
|
93
|
+
- 'Help the candidate understand the existing codebase structure when asked'
|
|
94
|
+
- 'If asked about design choices, discuss trade-offs (sync vs async delivery, retry strategies, etc.)'
|
|
95
|
+
- 'Encourage starting with the simplest working implementation, then iterating'
|
|
96
|
+
- 'Suggest they write a small test listener to verify webhook delivery'
|
|
97
|
+
- 'If they get bogged down in edge cases (retries, backoff, ordering), redirect to core functionality first'
|
|
98
|
+
- 'Praise clean integration with the existing codebase patterns'
|
|
99
|
+
knowledge: |
|
|
100
|
+
The json-server codebase has a clean separation:
|
|
101
|
+
- src/app.ts defines routes and delegates to Service
|
|
102
|
+
- src/service.ts handles CRUD logic with lowdb
|
|
103
|
+
- The "webhooks" array is already seeded in db.json as an empty array
|
|
104
|
+
|
|
105
|
+
A good implementation would:
|
|
106
|
+
1. Use the existing Service class to manage webhook registrations (CRUD on /webhooks)
|
|
107
|
+
— json-server already handles any resource in db.json, so GET/POST/DELETE /webhooks
|
|
108
|
+
works out of the box. The candidate might realize this or build custom handlers.
|
|
109
|
+
2. Add middleware or post-response hooks in app.ts that fire after mutations
|
|
110
|
+
3. Use fetch() to deliver webhook payloads asynchronously (fire-and-forget)
|
|
111
|
+
4. Structure the payload as: { event: "create"|"update"|"delete", resource: "products", data: {...}, timestamp: "..." }
|
|
112
|
+
|
|
113
|
+
Key insight: since "webhooks" is already a resource in db.json, the candidate
|
|
114
|
+
gets CRUD for webhook registrations for free via json-server's existing routing.
|
|
115
|
+
They just need to add the notification delivery logic.
|
|
116
|
+
|
|
117
|
+
evaluation:
|
|
118
|
+
criteria:
|
|
119
|
+
- 'Webhook registration works (create, list, delete)'
|
|
120
|
+
- 'Notifications are delivered on create, update, and delete operations'
|
|
121
|
+
- 'Webhook payload contains event type, resource, data, and timestamp'
|
|
122
|
+
- 'Resource filtering works (webhooks can subscribe to specific resources)'
|
|
123
|
+
- 'Failed deliveries are handled gracefully (no crashes, no blocking)'
|
|
124
|
+
- 'Clean integration with existing codebase patterns'
|
|
125
|
+
- 'Tested end-to-end with a webhook listener'
|
|
126
|
+
- 'Used AI effectively as a development partner'
|
|
127
|
+
|
|
128
|
+
license: MIT
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
name: rate-limiter-boundary
|
|
2
|
-
description: "Off-by-one in express-rate-limit's sliding window lets one extra request through"
|
|
3
|
-
difficulty: medium
|
|
4
|
-
estimated_time: '30-45m'
|
|
5
|
-
tags:
|
|
6
|
-
- node
|
|
7
|
-
- express
|
|
8
|
-
- middleware
|
|
9
|
-
- rate-limiting
|
|
10
|
-
- off-by-one
|
|
11
|
-
|
|
12
|
-
repo: 'https://github.com/express-rate-limit/express-rate-limit'
|
|
13
|
-
commit: '4e8b18bf972eff2890ed67bd11d8a08a2c6502d5'
|
|
14
|
-
|
|
15
|
-
setup:
|
|
16
|
-
- 'npm install --ignore-scripts'
|
|
17
|
-
|
|
18
|
-
patch:
|
|
19
|
-
- file: 'source/rate-limit.ts'
|
|
20
|
-
find: 'if (totalHits > limit) {'
|
|
21
|
-
replace: 'if (totalHits > limit + 1) {'
|
|
22
|
-
|
|
23
|
-
briefing: |
|
|
24
|
-
Hey — we're getting reports from a few customers that our rate limiting isn't working correctly. They're saying they can make one more request than the configured limit before getting a 429.
|
|
25
|
-
|
|
26
|
-
For example, if the limit is set to 5, clients can make 6 successful requests before being blocked. It's not a huge deal but it's inconsistent and a couple of enterprise customers have flagged it in their security audits.
|
|
27
|
-
|
|
28
|
-
We're using express-rate-limit. I've already cloned the repo and set it up locally — can you dig into the source and figure out what's going on?
|
|
29
|
-
|
|
30
|
-
To run the tests: `npx jest --no-coverage`
|
|
31
|
-
|
|
32
|
-
The main source is in `source/` and tests are in `test/library/`. Good luck!
|
|
33
|
-
|
|
34
|
-
ai_rules:
|
|
35
|
-
role: |
|
|
36
|
-
You are a senior engineer helping a candidate debug an off-by-one error
|
|
37
|
-
in the express-rate-limit middleware. Act as a patient but not overly
|
|
38
|
-
helpful colleague — you're available for questions but you don't
|
|
39
|
-
volunteer the answer.
|
|
40
|
-
rules:
|
|
41
|
-
- 'Never reveal the exact location or nature of the bug directly'
|
|
42
|
-
- 'If asked, confirm whether the candidate is looking in the right area'
|
|
43
|
-
- 'Encourage the candidate to write or run tests to reproduce the issue'
|
|
44
|
-
- 'If the candidate is stuck for more than 10 minutes, suggest looking at where totalHits is compared to the limit'
|
|
45
|
-
- 'Praise good debugging methodology (reading tests, adding logging, bisecting)'
|
|
46
|
-
knowledge: |
|
|
47
|
-
The bug is on line 507 of source/rate-limit.ts. The comparison
|
|
48
|
-
`if (totalHits > limit)` was changed to `if (totalHits > limit + 1)`.
|
|
49
|
-
This means the rate limiter allows limit+1 requests instead of limit
|
|
50
|
-
requests before returning 429. The fix is to change it back to
|
|
51
|
-
`if (totalHits > limit)`.
|
|
52
|
-
|
|
53
|
-
solution: |
|
|
54
|
-
In source/rate-limit.ts line 507, change:
|
|
55
|
-
`if (totalHits > limit + 1) {`
|
|
56
|
-
back to:
|
|
57
|
-
`if (totalHits > limit) {`
|
|
58
|
-
|
|
59
|
-
The off-by-one was introduced by adding `+ 1` to the limit comparison,
|
|
60
|
-
allowing one extra request through before rate limiting kicks in.
|
|
61
|
-
|
|
62
|
-
evaluation:
|
|
63
|
-
criteria:
|
|
64
|
-
- 'Identified the bug location in source/rate-limit.ts'
|
|
65
|
-
- 'Understood the off-by-one nature of the bug'
|
|
66
|
-
- 'Used tests or manual testing to reproduce the issue'
|
|
67
|
-
- 'Applied the correct fix'
|
|
68
|
-
- 'Used AI effectively as a debugging partner'
|
|
69
|
-
expected_fix: 'Change `totalHits > limit + 1` back to `totalHits > limit`'
|
|
70
|
-
|
|
71
|
-
license: MIT
|