@checkstack/backend 0.4.4 → 0.4.5
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/CHANGELOG.md +19 -0
- package/package.json +1 -1
- package/src/plugin-manager/plugin-loader.ts +68 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @checkstack/backend
|
|
2
2
|
|
|
3
|
+
## 0.4.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8a87cd4: Added startup validation for unregistered access rules
|
|
8
|
+
|
|
9
|
+
The backend now throws an error at startup if a procedure contract references an access rule that isn't registered with the plugin system. This prevents silent runtime failures.
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [8a87cd4]
|
|
12
|
+
- Updated dependencies [8a87cd4]
|
|
13
|
+
- Updated dependencies [8a87cd4]
|
|
14
|
+
- @checkstack/auth-common@0.5.2
|
|
15
|
+
- @checkstack/backend-api@0.4.1
|
|
16
|
+
- @checkstack/common@0.5.0
|
|
17
|
+
- @checkstack/queue-api@0.1.3
|
|
18
|
+
- @checkstack/signal-backend@0.1.5
|
|
19
|
+
- @checkstack/api-docs-common@0.1.3
|
|
20
|
+
- @checkstack/signal-common@0.1.3
|
|
21
|
+
|
|
3
22
|
## 0.4.4
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -424,6 +424,35 @@ export async function loadPlugins({
|
|
|
424
424
|
}
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
// Phase 2.5: Validate that all access rules used in contracts are registered
|
|
428
|
+
// This catches bugs where access rules are used in procedures but not added to
|
|
429
|
+
// the plugin's accessRules registration array.
|
|
430
|
+
rootLogger.debug("🔍 Validating access rules in contracts...");
|
|
431
|
+
const registeredRuleIds = new Set(
|
|
432
|
+
deps.registeredAccessRules.map((r) => r.id),
|
|
433
|
+
);
|
|
434
|
+
const validationErrors: string[] = [];
|
|
435
|
+
|
|
436
|
+
for (const [pluginId, contract] of deps.pluginContractRegistry) {
|
|
437
|
+
validateContractAccessRules({
|
|
438
|
+
pluginId,
|
|
439
|
+
contract,
|
|
440
|
+
registeredRuleIds,
|
|
441
|
+
validationErrors,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (validationErrors.length > 0) {
|
|
446
|
+
rootLogger.error("❌ Unregistered access rules found in contracts:");
|
|
447
|
+
for (const error of validationErrors) {
|
|
448
|
+
rootLogger.error(` • ${error}`);
|
|
449
|
+
}
|
|
450
|
+
throw new Error(
|
|
451
|
+
`Unregistered access rules in contracts:\n${validationErrors.join("\n")}`,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
rootLogger.debug("✅ All access rules in contracts are registered");
|
|
455
|
+
|
|
427
456
|
// Phase 3: Run afterPluginsReady callbacks
|
|
428
457
|
rootLogger.debug("🔄 Running afterPluginsReady callbacks...");
|
|
429
458
|
|
|
@@ -507,3 +536,42 @@ export async function loadPlugins({
|
|
|
507
536
|
}
|
|
508
537
|
rootLogger.debug("✅ All afterPluginsReady callbacks complete");
|
|
509
538
|
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Validate that all access rules used in a contract are registered with the plugin system.
|
|
542
|
+
* Recursively traverses the contract to find all procedures and their access metadata.
|
|
543
|
+
*/
|
|
544
|
+
function validateContractAccessRules({
|
|
545
|
+
pluginId,
|
|
546
|
+
contract,
|
|
547
|
+
registeredRuleIds,
|
|
548
|
+
validationErrors,
|
|
549
|
+
}: {
|
|
550
|
+
pluginId: string;
|
|
551
|
+
contract: AnyContractRouter;
|
|
552
|
+
registeredRuleIds: Set<string>;
|
|
553
|
+
validationErrors: string[];
|
|
554
|
+
}): void {
|
|
555
|
+
for (const [procedureName, procedure] of Object.entries(
|
|
556
|
+
contract as Record<string, unknown>,
|
|
557
|
+
)) {
|
|
558
|
+
if (!procedure || typeof procedure !== "object") continue;
|
|
559
|
+
|
|
560
|
+
// Check if this is a procedure with oRPC metadata
|
|
561
|
+
const orpcData = (procedure as Record<string, unknown>)["~orpc"] as
|
|
562
|
+
| { meta?: { access?: Array<{ id: string }> } }
|
|
563
|
+
| undefined;
|
|
564
|
+
|
|
565
|
+
if (orpcData?.meta?.access) {
|
|
566
|
+
for (const accessRule of orpcData.meta.access) {
|
|
567
|
+
const qualifiedId = `${pluginId}.${accessRule.id}`;
|
|
568
|
+
if (!registeredRuleIds.has(qualifiedId)) {
|
|
569
|
+
validationErrors.push(
|
|
570
|
+
`Plugin "${pluginId}" procedure "${procedureName}" uses unregistered access rule "${accessRule.id}" (qualified: "${qualifiedId}"). ` +
|
|
571
|
+
`Add it to the plugin's accessRules registration array.`,
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|