@vantageos/backend-doctor 0.1.14 → 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/README.md +19 -2
- package/dist/args.d.ts +16 -0
- package/dist/args.js +54 -0
- package/dist/build-stamp.d.ts +13 -0
- package/dist/build-stamp.js +76 -0
- package/dist/build-stamp.json +4 -0
- package/dist/caller-resolution.d.ts +222 -0
- package/dist/caller-resolution.js +1195 -0
- package/dist/cli.js +91 -8
- package/dist/coverage-cli.js +8 -0
- package/dist/detectors/oracle-axis1.d.ts +5 -3
- package/dist/detectors/oracle-axis1.js +24 -15
- package/dist/detectors/oracle-axis2.js +5 -6
- package/dist/detectors/predicates.d.ts +3 -0
- package/dist/detectors/predicates.js +81 -16
- package/dist/detectors/shared.js +5 -5
- package/dist/detectors/source/backfill.js +9 -1
- package/dist/detectors/source/guard-helper-flow.d.ts +112 -0
- package/dist/detectors/source/guard-helper-flow.js +1173 -0
- package/dist/detectors/source/identity.d.ts +51 -2
- package/dist/detectors/source/identity.js +815 -14
- package/dist/detectors/source/isolation.d.ts +11 -1
- package/dist/detectors/source/isolation.js +106 -25
- package/dist/detectors/source/jobs.js +165 -2
- package/dist/detectors/source/readbudget.d.ts +1 -0
- package/dist/detectors/source/readbudget.js +67 -4
- package/dist/detectors/source/security.d.ts +16 -0
- package/dist/detectors/source/security.js +225 -7
- package/dist/detectors/source/write-isolation.d.ts +8 -1
- package/dist/detectors/source/write-isolation.js +64 -30
- package/dist/detectors/standard/r46-returns-validator.d.ts +8 -0
- package/dist/detectors/standard/r46-returns-validator.js +45 -14
- package/dist/detectors/structural.js +14 -10
- package/dist/oracle.d.ts +92 -3
- package/dist/oracle.js +229 -12
- package/dist/registry.d.ts +7 -0
- package/dist/registry.js +48 -6
- package/dist/report.d.ts +11 -1
- package/dist/report.js +15 -2
- package/dist/state-rank.d.ts +5 -0
- package/dist/state-rank.js +9 -0
- package/dist/tally.js +1 -7
- package/dist/target.d.ts +32 -0
- package/dist/target.js +173 -0
- package/fixtures/cr1-classes/_generated/api.d.ts +24 -0
- package/fixtures/cr1-classes/surface.ts +206 -0
- package/fixtures/cr1-empty/nothing.ts +1 -0
- package/fixtures/cr1-refusal-on-resolved/_generated/api.d.ts +24 -0
- package/fixtures/cr1-refusal-on-resolved/doors.ts +180 -0
- package/fixtures/cr1-refusal-on-resolved/lib/auth.ts +104 -0
- package/fixtures/r34-vp-auth-30e821b/auth-805-812.ts.txt +8 -0
- package/fixtures/r34-vp-auth-30e821b/auth-878-890.ts.txt +13 -0
- package/fixtures/vp-writer-tier-1e4ab36.csv +55 -0
- package/package.json +2 -2
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// CR-1 fixture — the RESOLVER and REFUSER helpers, shaped after vantage-peers
|
|
2
|
+
// 58ed649 `convex/lib/auth.ts` (withOrgScope, requireResolvedCaller,
|
|
3
|
+
// isNamespaceAllowedForScope, requireTenantNamespace, resolveOrgScopeForAction).
|
|
4
|
+
// Each helper below is a pole for tests/cr1-refusal-on-resolved.test.ts. Do not
|
|
5
|
+
// "tidy" it: a line that looks redundant is a discriminator.
|
|
6
|
+
|
|
7
|
+
/** RESOLVER (not a gate): an absent identity returns an anonymous scope. */
|
|
8
|
+
export async function withOrgScope(ctx: Ctx, opts?: Opts): Promise<Scope> {
|
|
9
|
+
const identity = await ctx.auth.getUserIdentity();
|
|
10
|
+
if (identity === null || identity === undefined) {
|
|
11
|
+
return { isMaster: false, orgSlug: null, anonymous: true, refused: true };
|
|
12
|
+
}
|
|
13
|
+
const row = await ctx.db
|
|
14
|
+
.query("client_org_mapping")
|
|
15
|
+
.withIndex("by_clerk_slug", (q) => q.eq("clerkSlug", identity.org_slug))
|
|
16
|
+
.unique();
|
|
17
|
+
if (row === null || opts?.refuseWithoutThrow) {
|
|
18
|
+
return { isMaster: false, orgSlug: null, anonymous: false, refused: true };
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
isMaster: false,
|
|
22
|
+
orgSlug: row.orgSlug,
|
|
23
|
+
anonymous: false,
|
|
24
|
+
refused: false,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** VALUE REFUSER, direct: refuses an anonymous scope by raising. */
|
|
29
|
+
export function requireResolvedCaller(
|
|
30
|
+
scope: Pick<Scope, "isMaster" | "orgSlug" | "anonymous" | "refused">,
|
|
31
|
+
registration: string,
|
|
32
|
+
opts?: { masterOnly?: boolean },
|
|
33
|
+
): void {
|
|
34
|
+
if (scope.isMaster) return;
|
|
35
|
+
if (scope.anonymous) {
|
|
36
|
+
throw new ConvexError(
|
|
37
|
+
`RBAC_DENIED: no credential presented to "${registration}"`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (opts?.masterOnly) {
|
|
41
|
+
throw new ConvexError(
|
|
42
|
+
`RBAC_DENIED: "${registration}" admits the fleet master only`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** a PREDICATE on the resolved scope — returns a boolean, refuses nothing. */
|
|
48
|
+
export function isNamespaceAllowedForScope(
|
|
49
|
+
scope: Pick<Scope, "isMaster" | "orgSlug">,
|
|
50
|
+
namespace: string,
|
|
51
|
+
): boolean {
|
|
52
|
+
if (scope.isMaster) return true;
|
|
53
|
+
if (scope.orgSlug === null) return false;
|
|
54
|
+
return namespace.startsWith(`team/${scope.orgSlug}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** VALUE REFUSER, TRANSITIVE: no throw of its own — it allows on a test of the
|
|
58
|
+
* RESOLVED scope and otherwise hands that scope to requireResolvedCaller. */
|
|
59
|
+
export function requireTenantNamespace(
|
|
60
|
+
scope: Pick<Scope, "isMaster" | "orgSlug" | "anonymous" | "refused">,
|
|
61
|
+
namespace: string,
|
|
62
|
+
door: string,
|
|
63
|
+
): void {
|
|
64
|
+
if (isNamespaceAllowedForScope(scope, namespace)) return;
|
|
65
|
+
requireResolvedCaller(scope, door, { masterOnly: true });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** VALUE REFUSER, direct, one level: throws on a test of the resolved scope. */
|
|
69
|
+
export function requireMasterDirect(scope: Scope, door: string): void {
|
|
70
|
+
if (!scope.isMaster) {
|
|
71
|
+
throw new ConvexError(
|
|
72
|
+
`RBAC_DENIED: "${door}" admits the fleet master only`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** NOT a refuser of `scope`: it PROCEEDS on a test of a caller-supplied argument
|
|
78
|
+
* before any test of the resolved value — every principal, none included, whose
|
|
79
|
+
* namespace starts `public/` is waved through. */
|
|
80
|
+
export function requireNamespaceSometimes(
|
|
81
|
+
scope: Pick<Scope, "isMaster" | "orgSlug" | "anonymous" | "refused">,
|
|
82
|
+
namespace: string,
|
|
83
|
+
door: string,
|
|
84
|
+
): void {
|
|
85
|
+
if (namespace.startsWith("public/")) return;
|
|
86
|
+
requireResolvedCaller(scope, door, { masterOnly: true });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** a refuser of its OWN argument (a namespace), never of a principal. */
|
|
90
|
+
export function requireNonEmpty(namespace: string, door: string): void {
|
|
91
|
+
if (namespace.length === 0) {
|
|
92
|
+
throw new Error(`OKF_NAMESPACE_INVALID at "${door}"`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** the ONE way an action resolves its caller — reached as
|
|
97
|
+
* `internal.lib.auth.resolveOrgScopeForAction`, never by bare name. */
|
|
98
|
+
export const resolveOrgScopeForAction = internalQuery({
|
|
99
|
+
args: {},
|
|
100
|
+
handler: async (ctx) => {
|
|
101
|
+
const scope = await withOrgScope(ctx, { refuseWithoutThrow: true });
|
|
102
|
+
return scope;
|
|
103
|
+
},
|
|
104
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// verified token here is forwarded verbatim to Convex as `clerkJwt` below,
|
|
2
|
+
// and Convex's own auth.config already requires aud === "convex", so binding
|
|
3
|
+
// the same value here (Critical Rule 14 element 5: aud + iss binding) closes
|
|
4
|
+
// the cross-tenant/cross-resource replay gap without rejecting any token a
|
|
5
|
+
// correctly-configured Clerk "convex" JWT template mints.
|
|
6
|
+
const CLERK_JWT_AUDIENCE = process.env.CLERK_JWT_AUDIENCE ?? "convex";
|
|
7
|
+
|
|
8
|
+
// Lazy singleton — createRemoteJWKSet caches JWKS in-process (10-min TTL).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
* that DOES require the fallback).
|
|
2
|
+
*/
|
|
3
|
+
async function tryVerifyClerkJwt(token: string): Promise<ClerkJwtResult> {
|
|
4
|
+
try {
|
|
5
|
+
const { payload } = await jwtVerify(token, clerkJwks(), {
|
|
6
|
+
issuer: CLERK_DOMAIN,
|
|
7
|
+
audience: CLERK_JWT_AUDIENCE,
|
|
8
|
+
});
|
|
9
|
+
// Verification succeeded — this IS the caller's own Clerk identity.
|
|
10
|
+
// `sub`/`exp` absence here would be a session-token shape violation
|
|
11
|
+
// (not the org concern this function types), so it is still
|
|
12
|
+
// "not-clerk" (try next layer) rather than "no-org".
|
|
13
|
+
const sub = payload.sub;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
outil,table,crud,writer_tier,writer_gate,write_doors
|
|
2
|
+
import_okf_bundle,_storage+briefingNotes+memories+tasks,CREATE,org-member,orgResolver,okfBundleNode:importOkfBundle=orgResolver
|
|
3
|
+
store_document_chunked,_storage+kbUploads+memories,UPSERT,org-member,orgResolver,kb:storeDocumentChunked=orgResolver
|
|
4
|
+
delete_recurring_task,,DELETE,master,masterOnly,recurringTasks:remove=masterOnly
|
|
5
|
+
pause_recurring_task,,UPDATE,master,masterOnly,recurringTasks:pause=masterOnly
|
|
6
|
+
resume_recurring_task,,UPDATE,master,masterOnly,recurringTasks:resume=masterOnly
|
|
7
|
+
soft_delete_memory,,UPDATE,org-member,orgResolver,memories:softDeleteMemory=orgResolver
|
|
8
|
+
create_bu,businessUnits,CREATE,org-member,orgResolver,businessUnits:create=orgResolver
|
|
9
|
+
delete_bu,businessUnits,DELETE,master,masterOnly,businessUnits:remove=masterOnly
|
|
10
|
+
update_bu,businessUnits,UPDATE,org-member,orgResolver,businessUnits:update=orgResolver
|
|
11
|
+
update_task,client_org_mapping+messageReceipts+messages+missions+oauth_access_tokens+taskClosureConfig+tasks,UPSERT,org-member,orgResolver,tasks:update=orgResolver
|
|
12
|
+
send_message,client_org_mapping+messageReceipts+messages+profiles,CREATE,org-member,orgResolver,messages:sendMessage=orgResolver
|
|
13
|
+
update_recurring_task,client_org_mapping+oauth_access_tokens,UPDATE,org-member,orgResolver,recurringTasks:update=orgResolver
|
|
14
|
+
create_recurring_task,client_org_mapping+oauth_access_tokens+recurringTasks,CREATE,org-member,orgResolver,recurringTasks:create=orgResolver
|
|
15
|
+
create_task,client_org_mapping+oauth_access_tokens+tasks,CREATE,org-member,orgResolver,tasks:create=orgResolver
|
|
16
|
+
write_diary,diary,UPSERT,org-member,orgResolver,diary:write=orgResolver
|
|
17
|
+
add_fix_attempt,fixAttempts+fixPatterns,UPSERT,master,masterOnly,fixPatterns:addAttempt=masterOnly
|
|
18
|
+
create_fix_pattern,fixPatterns,CREATE,master,masterOnly,fixPatterns:create=masterOnly
|
|
19
|
+
link_issue_to_pattern,fixPatterns,UPDATE,master,masterOnly,fixPatterns:linkIssue=masterOnly
|
|
20
|
+
validate_fix,fixPatterns,UPDATE,master,masterOnly,fixPatterns:validate=masterOnly
|
|
21
|
+
complete_task,fixPatterns+githubRepoMapping+issues+messageReceipts+messages+taskClosureConfig+tasks,UPSERT,org-member,orgResolver,tasks:complete=orgResolver
|
|
22
|
+
add_repo_mapping,githubRepoMapping,UPSERT,master,masterOnly,githubRepoMapping:add=masterOnly
|
|
23
|
+
remove_repo_mapping,githubRepoMapping,DELETE,master,masterOnly,githubRepoMapping:remove=masterOnly
|
|
24
|
+
link_commit_to_issue,issues,UPDATE,master,masterOnly,issues:linkCommit=masterOnly
|
|
25
|
+
update_issue_status,issues,TRANSITION,master,masterOnly,issues:updateStatus=masterOnly
|
|
26
|
+
verify_issue,issues,TRANSITION,master,masterOnly,issues:verify=masterOnly
|
|
27
|
+
accept_mandate,mandates,TRANSITION,master,masterOnly,mandates:accept=masterOnly
|
|
28
|
+
create_mandate,mandates,CREATE,master,masterOnly,mandates:create=masterOnly
|
|
29
|
+
settle_mandate,mandates,TRANSITION,master,masterOnly,mandates:settle=masterOnly
|
|
30
|
+
update_mandate,mandates+tasks,UPDATE,master,masterOnly,mandates:update=masterOnly
|
|
31
|
+
soft_delete_document,memories,UPDATE,org-member,orgResolver,kb:softDeleteDocument=orgResolver
|
|
32
|
+
store_episode,memories,CREATE,org-member,orgResolver,episodes:storeEpisode=orgResolver
|
|
33
|
+
store_memory,memories,UPSERT,org-member,orgResolver,memories:storeMemory=orgResolver
|
|
34
|
+
mark_as_read,messageReceipts,UPDATE,org-member,orgResolver,messages:markAsRead=orgResolver
|
|
35
|
+
delete_message,messageReceipts+messages,DELETE,org-member,orgResolver,messages:deleteMessage=orgResolver
|
|
36
|
+
add_task_dependency,messageReceipts+messages+missions+taskClosureConfig+tasks,UPSERT,org-member,orgResolver,tasks:update=orgResolver
|
|
37
|
+
block_task,messageReceipts+messages+missions+taskClosureConfig+tasks,UPSERT,org-member,orgResolver,tasks:blockTask=orgResolver; tasks:update=orgResolver
|
|
38
|
+
fail_task,messageReceipts+messages+tasks,UPSERT,org-member,orgResolver,tasks:failTask=orgResolver
|
|
39
|
+
create_mission,missions,CREATE,org-member,orgResolver,missions:create=orgResolver
|
|
40
|
+
update_mission,missions,UPDATE,org-member,orgResolver,missions:update=orgResolver
|
|
41
|
+
update_mission_status,missions,TRANSITION,org-member,orgResolver,missions:updateStatus=orgResolver
|
|
42
|
+
soft_delete_mission_template,missionTemplates,UPDATE,master,masterOnly,missionTemplates:softDelete=masterOnly
|
|
43
|
+
update_mission_template,missionTemplates,UPSERT,master,masterOnly,missionTemplates:upsert=masterOnly
|
|
44
|
+
instantiate_template_into_mission,missionTemplates+missions+tasks,UPSERT,org-member,orgResolver,missionTemplates:instantiateTemplateIntoMission=orgResolver
|
|
45
|
+
add_deployment,monitoredDeployments,UPSERT,master,masterOnly,errorMonitor:addDeployment=masterOnly
|
|
46
|
+
remove_deployment,monitoredDeployments,UPDATE,master,masterOnly,errorMonitor:removeDeployment=masterOnly
|
|
47
|
+
set_summary,profiles,UPSERT,master,masterOnly,profiles:updateDynamic=masterOnly
|
|
48
|
+
update_profile,profiles,UPSERT,master,masterOnly,profiles:upsertProfile=masterOnly
|
|
49
|
+
bulk_complete_tasks,taskClosureConfig+tasks,TRANSITION,org-member,orgResolver,tasks:bulkComplete=orgResolver
|
|
50
|
+
correct_task_segment,taskClosureConfig+tasks,UPDATE,org-member,orgResolver,tasks:correctSegment=orgResolver
|
|
51
|
+
checkout_task,tasks,TRANSITION,org-member,orgResolver,tasks:checkout=orgResolver
|
|
52
|
+
delete_task,tasks,DELETE,org-member,orgResolver,tasks:deleteTask=orgResolver
|
|
53
|
+
pause_task,tasks,TRANSITION,org-member,orgResolver,tasks:pause=orgResolver
|
|
54
|
+
resume_task,tasks,TRANSITION,org-member,orgResolver,tasks:resume=orgResolver
|
|
55
|
+
start_task,tasks,UPDATE,org-member,orgResolver,tasks:start=orgResolver
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vantageos/backend-doctor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A conformance instrument that measures the VantagePeers backend against the pinned backend standard. The THIRD doctor built ON the @vantageos/mcp-doctor engine — never a third engine. It consumes runDoctor/renderTable and adds only detectors.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cli.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
|
-
"build": "tsc",
|
|
19
|
+
"build": "tsc && node scripts/stamp-build.mjs",
|
|
20
20
|
"coverage": "tsx src/coverage-cli.ts",
|
|
21
21
|
"doctor": "tsx src/cli.ts",
|
|
22
22
|
"test": "vitest run",
|